SpringBoot用户登录与校验功能
登录功能和登录校验
登录功能实质上就是获取前端发送过来的登录信息,与后端中的用户信息进行比较,这个功能只会在登录界面时生效
而登录校验则表示想要进行其他功能的操作时,先要明确当前用户的身份(防止用户跳过登录界面直接进入系统)
登录校验的实现相比登录功能来说更加复杂且多样,登录功能只需要在传统的三层架构中编写查询数据即可
所以主要学习的是登录校验
登录校验思路

登录标记:用户登录成功之后,在后续的每一次请求中,都可以获取到该标记【会话技术】
统一拦截:过滤器Filter、拦截器Interceptor
会话技术
- 会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应
- 会话建立或断开的概念:
- 会话是逻辑概念,不是物理连接
- 会话建立是通过标记进行约束的而建立的,断开就表示标记失效、不存在或非法修改
- 会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自同一浏览器,以便在同一次会话的多次请求间共享数据
- 会话跟踪方案:
- 客户端会话跟踪技术:Cookie
- 服务端会话跟踪技术:Session
- 令牌技术
Cookie
- Cookie存在于请求头、响应头中
- Cookie会自动存储到浏览器中(不需要前端配置)
- Cookie可以由前端手动指定并发送给后端。但大部分情况下,都是发送被自动保存的Cookie
- 前端会自动携带Cookie到请求头中(前提是在同一个网站下,且未过期)
优势:
- Cookie最大的优势就在于是HTTP协议中支持的技术,很多部分都可以自动完成
缺点:
- 移动端APP无法使用Cookie(不包括移动端浏览器)
- 不安全,用户可以自己禁用Cookie
- Cookie不能跨域
示例:
//设置Cookie
@GetMapping("/c1")
public Result cookie1(HttpServletResponse response){
response.addCookie(new Cookie("login_username","itheima")); //设置Cookie/响应Cookie
return Result.success();
}
//获取Cookie
@GetMapping("/c2")
public Result cookie2(HttpServletRequest request){
Cookie[] cookies = request.getCookies();
for (Cookie cookie : cookies) {
if(cookie.getName().equals("login_username")){
System.out.println("login_username: "+cookie.getValue()); //输出name为login_username的cookie
}
}
return Result.success();
}
Session
优势:
- Session是基于Cookie实现的,而且它存储在服务端,更安全
劣势:
- 也由于是存储在服务端,如果存在集群就无法直接使用Session
- 基于Cookie实现,所以也存在Cookie的所有缺点(除了安全性)
示例:
@GetMapping("/s1")
public Result session1(HttpSession session){
log.info("HttpSession-s1: {}", session.hashCode());
session.setAttribute("loginUser", "tom"); //往session中存储数据
return Result.success();
}
@GetMapping("/s2")
public Result session2(HttpSession session){
log.info("HttpSession-s2: {}", session.hashCode());
Object loginUser = session.getAttribute("loginUser"); //从session中获取数据
log.info("loginUser: {}", loginUser);
return Result.success(loginUser);
}
简答理解:客户端持有 Session 的”钥匙”(Session ID),而真正的”保险箱”(HttpSession)保存在服务器端
令牌
优点:
- 支持PC端、移动端
- 解决集群环境下的认证问题
- 减轻服务端存储压力
缺点:
- 服务单令牌的生成、客户端的使用和存储都需要开发人员实现
JWT令牌
- 全称:JSON Web Token(https://jwt.io)/)
- 定义了一种简洁的、自包含的格式,用于在通信双方以JSON数据格式安全的传输信息
- 组成:
- 第一部分:Header(头),记录令牌类型、签名算法等。例如:
{"alg":"HS256", "type":"JWT"} - 第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。例如:
{"id":"1", "username":"Tom"} - 第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、payload融入,并加入指定密钥,通过指定签名算法计算而来
生成/解析
- 引入jjwt的依赖
- 调用官方提供的工具类Jwts来生成或解析jwt令牌
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
生成令牌:
@Test
public void testGenJwt() {
Map<String, Object> claims = new HashMap<>();
claims.put("id",10);
claims.put("username","itheima");
String jwt = Jwts.builder().
signWith(SignatureAlgorithm.HS256,"Norlcyan").// 第一参数指定加密算法,第二个参数指定密钥
addClaims(claims).
setExpiration(new Date(System.currentTimeMillis() + 12*3600*1000)).// 参数表示令牌最大存活时间
compact();
System.out.println(jwt);
}
解析令牌:
@Test
public void testParseJwt() {
String jwtToken = "令牌内容...";
Claims claims = Jwts.parser().
setSigningKey("Norlcyan").// 参数表示密钥,必须和生成令牌时的密钥保持一致
parseClaimsJws(jwtToken).
getBody();
System.out.println(claims);
}
Filter过滤器
- 概念:Filter过滤器,是JavaWeb三大组件之一(Servlet、Filter、Listener)
- 过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能
- 过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等
Filter快速入门
- 定义Filter:定义一个类,实现Filter接口,并实现其所有方法
- 配置Filter:Filter类上加@WebFilter注解,配置拦截路径。引导类上加@ServletComponentScan开启Servlet组件支持
示例:
@Slf4j
@WebFilter(urlPatterns = {"/*"})
public class FilterDemo implements Filter {
// 初始化方法,Web服务器启动时自动执行,只执行一次
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("init 初始化方法 ...");
}
// 销毁方法,web服务器关闭的时候执行,只执行一次
@Override
public void destroy() {
log.info("destroy 销毁方法 ...");
}
// 拦截到对应的请求后执行,执行多次
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.info("拦截到了请求......");
// 放行操作一定要配置,否则后续操作就停止了
filterChain.doFilter(servletRequest,servletResponse); // 放行操作
}
}
别忘了启动项开启Servlet组件支持:
@ServletComponentScan // 开启了SpringBoot对Servlet组件的支持
@SpringBootApplication
public class TliasWebManagementApplication {
public static void main(String[] args) {
SpringApplication.run(TliasWebManagementApplication.class, args);
}
}
令牌校验
先创建令牌的工具类:
public class JwtUtils {
private static final String SECRET_KEY = "Norycan";
public static String generateToken(Integer id,String username) {
Map<String, Object> claims = new HashMap<>();
claims.put("id",id);
claims.put("username",username);
return Jwts.builder().
signWith(SignatureAlgorithm.HS256,SECRET_KEY).
addClaims(claims).
setExpiration(new Date(System.currentTimeMillis() + 12*3600*1000)).
compact();
}
public static Claims parseToken(String token) throws Exception {
return Jwts.parser().
setSigningKey(SECRET_KEY).
parseClaimsJws(token).
getBody();
}
}
拦截器:
@Slf4j
@WebFilter(urlPatterns = {"/*"})
public class TokenFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
// 1. 获取请求路径
String requestURI = request.getRequestURI();
// 2. 判断路径是否为登陆/login,如果是直接放行
if (requestURI.contains("/login")) {
filterChain.doFilter(request,response);
return;
}
// 3. 获取请求头中的token
String token = request.getHeader("Token");
// 4. 判断token是否存在,不存在返回401状态码
if (token == null || token.isEmpty()) {
log.info("令牌为空,响应401状态码");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
// 5. token存在进行校验,校验失败返回401状态码
try {
JwtUtils.parseToken(token);
} catch (Exception e) {
log.info("令牌非法,响应401状态码");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
// 6. 校验成功放行
log.info("令牌合法,放行");
filterChain.doFilter(request,response);
}
}
对于登录页面,是不需要拦截的,否则用户连token都获取不了
Filter执行流程
Filter分为放行前操作、放行操作、放行后操作:
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletR
log.info("拦截到了请求... 放行前 ...");
filterChain.doFilter(servletRequest,servletResponse); // 放行操作
log.info("拦截到了请求... 放行后 ...");
}
放行后访问对应资源,资源访问完成后,还会回到Filter中
回到Filter中,就会执行放行后的操作
拦截路径
- Filter可以根据需求,配置不同的拦截资源路径
| 拦截路径 | urlPatterns值 | 含义 |
|---|---|---|
| 拦截具体路径 | /login | 只有访问/login路径时,才会被拦截 |
| 目录拦截 | /emps/* | 访问/emps下的所有资源,都会被拦截 |
| 拦截所有 | /* | 访问所有资源,都会被拦截 |
过滤器链
- 一个Web应用中,可以配置多个过滤器,这多个过滤器就形成了一个过滤器链

存在多个过滤器,执行的顺序默认是按照过滤器类名(字符串)的自然排序
Interceptor拦截器
- 概念:是一种动态拦截方法调用的机制,类似于过滤器。Spring框架中提供的,主要用于动态拦截控制器方法的执行
- 作用:拦截请求,在指定的方法调用前后,根据业务需求执行预定的代码
快速入门
- 定义拦截器,实现HandlerInterceptor接口,并实现其所有方法
- 注册拦截器
// 定义拦截器
@Component
public class InterceptorDemo implements HandlerInterceptor {
@Override// 目标资源方法执行前执行,返回true放行,返回false不放行
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
@Override// 目标资源方法执行后执行
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("preHandle");
}
@Override// 视图渲染完毕后执行,最后执行(前后端分离用不到这个)
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion");
}
}
// 注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
private final InterceptorDemo interceptorDemo;
@Autowired
public WebConfig(InterceptorDemo interceptorDemo) {
this.interceptorDemo = interceptorDemo;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(interceptorDemo).addPathPatterns("/**");
}
}
令牌校验
- 定义拦截器:
@Slf4j
@Component
public class TokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 获取Token
String token = request.getHeader("Token");
// 2. 判断token是否为空
if (token == null || token.isEmpty()) {
log.info("令牌为空,响应401状态码");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
// 3. 校验token
try {
JwtUtils.parseToken(token);
} catch (Exception e) {
log.info("令牌非法,响应401状态码");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
// 4. 放行
log.info("令牌合法,放行");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
}
整个流程和Filter差不多,不过拦截器不需要自己定义拦截的路径,而是在注册时进行路径判断
- 注册拦截器
package com.norlcyan.config;
import com.norlcyan.interceptor.TokenInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
private final TokenInterceptor tokenInterceptor;
@Autowired
public WebConfig(TokenInterceptor tokenInterceptor) {
this.tokenInterceptor = tokenInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tokenInterceptor).addPathPatterns("/**").excludePathPatterns("/login");
}
}
addPathPatterns("/**"):表示拦截所有路径及其子路径
excludePathPatterns("/login"):表示排除拦截/login路径
拦截路径
- 拦截器可以根据需求,配置不同的拦截路径
| 拦截路径 | 含义 | 举例 |
|---|---|---|
| /* | 一级路径 | 能匹配/depts,/emps等,不能匹配/depts/1 |
| /** | 任意级路径 | 所有路径都能匹配 |
| /depts/* | /depts下的一级路径 | 能匹配/depts/1,不能匹配/depts/1/2,/depts |
| /depts/** | /depts下的任意级路径 | /depts下的所有路径,如/depts/1,/depts/1/2 |
addPathPatterns:指定哪些路径拦截
excludePathPatterns:指定哪些请求不拦截
执行流程
拦截器也可以存在多个,只需要在注册类中进行多次注册即可:
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册第一个拦截器
registry.addInterceptor(tokenInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/login");
// 注册第二个拦截器
registry.addInterceptor(loggingInterceptor)
.addPathPatterns("/**");
// 注册第三个拦截器
registry.addInterceptor(permissionInterceptor
.addPathPatterns("/admin/**");
}
执行顺序:
- 先注册的拦截器先执行 preHandle
- postHandle 和 afterCompletion 按相反顺序执行
如果任何一个拦截器的 preHandle 返回 false,后续拦截器不会执行

Filter和Interceptor的区别
- 接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现HandlerInterceptor接口
- 拦截范围不同:过滤器Filter会拦截所有请求,而Interceptor只会拦截Spring环境中的资源