1.登录校验—JWT令牌
**JWT:**JSON Web Token(JSON Web 令牌),令牌是用户的合法身份凭证(实际上是一个字符串)
官网:https://jwt.io/
- JWT令牌实际上是
一个包含了用户的某些信息的字符串
,将原始的json数据格式进行了安全的封装。
在浏览器中发送登录请求后,后端要对前端发送的请求中的用户信息进行校验,如果校验成功,那就登录成功,后端会生成一个JWT令牌(存放着用户的信息如id、账号等)用来标识用户的身份,并将该令牌响应给前端,前端将这个令牌存储起来(可以存放在cookie中,也可以存放在其他的存储空间,比如localStorage中),在接下来的前端的每一次请求中都会携带着JWT令牌到后端,后端要对前端的每一次请求进行拦截,得到并校验前端的令牌的有效性,如果令牌是有效的,就说明用户已经执行了登录操作,直接放行进行请求的处理;否则就说明用户未执行有效操作,直接拒绝访问。
1.1JWT令牌的组成
JWT令牌由三部分(头、有效载荷、签名)组成,三个部分之间使用英文的点来分隔。
- 第一部分:Header(头):记录签名算法、令牌类型等。例如:{“alg”:“HS256”,“type”:“JWT”}
- 第二部分Payload(有效载荷):携带一些自定义的信息、默认信息。例如{“id”:“1”,“username”:“Tom”}
- 第三部分Signature(签名):防止JWT被篡改、确保安全性。将header、payload和自定义的密钥,通过指定签名算法计算而来。篡改令牌中的任何一个字符,在对令牌进行解析时都会报错。
在生成JWT令牌时,会对JSON格式的数据进行一次base64编码,注意Base6是编码方式,而不是加密方式。(Base是一种基于64个可打印的字符来表示二进制数据的编码方式)
JWT令牌还可以指定过期时间,过期后该令牌就失效了。
1.2 JWT令牌的生成和校验
1.2.1 引入JWT依赖
JWT令牌使用要引入JWT令牌的依赖,直接在工具包中提供的API来完成JWT令牌的生成和校验
<!-- JWT依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
1.2.2 创建JWT工具类
- JWT工具类里面包含生成JWT令牌、解析JWT令牌的方法
public class JwtUtils {
private static String signKey = "wenyin";//签名密钥为“wenyin”
private static Long expire = 3600000L; //有效时间为1小时
/**
* 生成JWT令牌
* @param claims JWT第二部分负载 payload 中存储的内容
* @return
*/
public static String generateJwt(Map<String, Object> claims){
String jwt = Jwts.builder()
.addClaims(claims)//自定义信息(有效载荷)
.signWith(SignatureAlgorithm.HS256, signKey)//签名算法(头部)
.setExpiration(new Date(System.currentTimeMillis() + expire))//过期时间
.compact();
return jwt;
}
/**
* 解析JWT令牌
* @param jwt JWT令牌
* @return JWT第二部分负载 payload 中存储的内容
*/
public static Claims parseJWT(String jwt){
Claims claims = Jwts.parser()
.setSigningKey(signKey)//指定签名密钥
.parseClaimsJws(jwt)//指定令牌Token
.getBody();
return claims;
}
}
1.2.3 使用JWTUtils在登录成功之后生成JWT令牌
@PostMapping("/login")
public Result login(String username, String password){
//根据用户名查询用户
User user = userService.findByName(username);
if(user == null) {
return Result.error("用户名不存在或账号异常");
}
//使用md5对密码进行加密,与数据库中的已加密的密码进行比较
if(Md5Util.getMD5String(password).equals(user.getPassword())) {
//设置JWT令牌的有效载荷
//claims指的是声明或声明的部分,它们是JWT包含的有效载荷的一部分。这些声明包含有关被认证主体的信息。
Map<String,Object> cliams = new HashMap<>();
cliams.put("id",user.getId());
cliams.put("username", user.getUsername());
String token = JwtUtil.genToken(cliams);
return Result.success(token);
}
return Result.error("密码错误");
}
2. 拦截器
拦截器的作用:
- 拦截请求,在指定方法调用前后,根据业务需要执行预先设定的代码。
自定义拦截器需要实现HandlerInterceptor接口
,重写该接口的三个方法(这三个方法前加上了default关键字,表明这三个方法是接口的默认方法,这些方法带有具体的实现,意味着一个类实现了一个包含default方法的接口的时候,并不需要强制重写这些default方法),你可以根据自己的需要选择性地重写这些方法,而不是全部都要实现。
HandlerInterceptor接口是Spring MVC框架中的一个核心接口,主要用于实现AOP风格的拦截器功能。可以实现对HTTP请求的全面控制和定制处理,包括请求的预处理,请求后处理以及请求完全结束后的清理工作。
HandlerInterceptor接口源码:
public interface HandlerInterceptor {
//preHandle(处理请求前)方法会在请求实际到达控制器(Controller)方法之前被调用,它允许你进行预处理操作,例如、登录拦截、权限验证、参数校验等,如果该方法返回true,则继续执行后继的处理器(如Controller方法),若返回false,则中断请求的进一步处理,并直接返回。
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
//postHandle(处理请求之后)方法在请求处理完毕且视图渲染之前执行(即Controller方法已经执行完毕但响应尚未发送给客户端),在此方法,可以访问或修改模型数据(ModelAndView对象),或者对视图做出一些格外的处理工作。
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
//afterCompletion(完成请求处理之后)方法在整个请求处理完毕之后,包括视图渲染完成之后调用,它可以用于资源清理、记录日志等工作。即使在preHandle()阶段就阻止了请求的处理,afterCompletion()依然会被调用。
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
}
}
2.1 自定义登录拦截器
自定义登录拦截器需要实现HandlerInterceptor接口,并重写其方法
通过拦截器来拦截前端发起的请求,将登录校验的逻辑全部编写在拦截器当中。在校验的过程当中,如发现用户登录了(携带JWT令牌且是合法令牌),就可以直接放行,去访问spring当中的资源。如果校验时发现并没有登录或是非法令牌,就可以直接给前端响应未登录的错误信息。
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 令牌验证,从前端的请求头里面得到Authorization中的jwt令牌判断是否登录成功
String token = request.getHeader("Authorization");
try {
Map<String, Object> claims = JwtUtil.parseToken(token);
return true;
} catch (Exception e) {
/**
* 设置响应状态码为401,表示未登录
*/
response.setStatus(401);
return false;
}
}
}
自定义拦截器之后还需要注册配置拦截器
注册配置拦截器:实现WebMvcConfigurer接口,并重写addInterceptors方法,注册自定义拦截器对象,定义需要拦截的资源和不需要拦截的资源。
@Configuration
public class WebConfig implements WebMvcConfigurer {
//拦截器对象
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册自定义拦截器对象
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/login");
// "/**",表示拦截所有资源,`excludePathPatterns("不拦截路径")`指定哪些资源不需要拦截。
}
}