1JWT
1.1 JWT是什么?
JSON Web令牌(JWT)是一种开放的标准(RFC 7519),它定义了一种紧凑而独立的方式在各方之间安全地传输信息为JSON对象。该信息可以被验证和信任,因为它是数字签名的。JWT可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公开/私有密钥类型签名。 虽然JWT可以被加密以提供各方之间的保密,但我们将重点关注签名的令牌。被签名的令牌可以验证包含在其中的声明的完整性,而加密的令牌对其他各方隐藏这些声明。当使用公钥/私钥对签名时,签名还证明只有持有私钥的一方才是签名方。
1.2 JWT主要使用场景
授权(Authorization):这是使用JWT最常见的场景。一旦用户登录,每个后续请求都将包括JWT,允许用户访问该令牌允许的路由、服务和资源。
单点登录(Single Sign On ):单点登录是当今广泛使用的JWT特性,因为它的小规模和易于跨不同领域使用的能力。
信息交换(lnformation Exchange):信息交换在通信的双方之间使用JWT对数据进行编码是一种非常安全的方式,由于它的信息是经过签名的,可以确保发送者发送的信息是没有经过伪造的。
传输信息(transmitting information):在各方之间传输信息。由于JWT可以签名--例如,使用公共/私钥对--您可以确保发件人是他们所说的发送者。此外,由于签名是使用头和有效载荷计算的,您还可以验证内容没有被篡改。
1.3 JWT请求流程
- 用户使用账号和密码发出post请求;
- 服务器使用私钥创建一个jwt;
- 服务器返回这个jwt给浏览器;
- 浏览器将该jwt串在请求头中像服务器发送请求;
- 服务器验证该jwt;
- 返回响应的资源给浏览器。
1.4 JWT结构
JWT包含了三部分:
Header 头部(标题包含了令牌的元数据,并且包含签名和/或加密算法的类型)
Payload 负载 (类似于飞机上承载的物品)
Signature 签名/签证
2,SpringBoot集成JWT具体实现过程
2.1添加相关依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>
2.2编写JWT工具类,用于生成Token令牌
package com.wxz.utils;
import cn.hutool.core.date.DateUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import java.util.Date;
/**
* @author wxz
* @description: 整合JWT生成token
*/
public class JwtTokenUtils {
/**
* 生成token
* @param userId
* @param sign
* @return
*/
public static String getToken(String userId,String sign){
return JWT.create()
//签收者
.withAudience(userId)
//2小时候token过期
.withExpiresAt(DateUtil.offsetHour(new Date(),2))
//以password作为token的密钥
.sign(Algorithm.HMAC256(sign));
}
}
Algorithm.HMAC256():使用HS256生成token,密钥则是用户的密码,唯一密钥的话可以保存在服务端。
withAudience():存入需要保存在token的信息,这里我们把用户ID存入token中
2.3自定义跳出拦截器的注解(不加这个注解.所有的请求路径都要校验token)
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @description: 自定义通过token注解,如果不加该注解直接拦截
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
boolean required() default true;
}
2.4编写拦截器并注入容器
package com.wxz.Config.inteceptor;
import cn.hutool.core.text.CharSequenceUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.yy.enums.ResponseEnum;
import com.yy.admin.pojo.Admin;
import com.yy.admin.service.Impl.AdminServiceImpl;
import com.yy.utils.MyException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
/**
* @author wxz
* @date 2022/9/12 15:37
* @description: 获取token并验证
*/
@Component
public class MyJwtInterceptor implements HandlerInterceptor {
@Autowired
private UserService userService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("token");
// 如果不是映射到方法直接通过
if (!(handler instanceof HandlerMethod)) {
return true;
}
某些请求路径如果加了PassToken这个注解,就进行放行,不校验token
// HandlerMethod handlerMethod = (HandlerMethod) handler;
// Method method = handlerMethod.getMethod();
// //检查是否通过有PassToken注解
// if (method.isAnnotationPresent(PassToken.class)) {
// //如果有则跳过认证检查
// PassToken passToken = method.getAnnotation(PassToken.class);
// if (passToken.required()) {
// return true;
// }
// }
//进行token检查
if (StrUtils.isBlank(token)) {
throw new ServiceException(Constants.CODE_401, "无token,请重新登录");
}
//有token,获取token中的用户id
String userId;
try {
userId = JWT.decode(token).getAudience().get(0);
} catch (JWTDecodeException j) {
throw new ServiceException(401, "token验证失败,请重新登录");
}
//根据token中的userId查询数据库
User user = userService.getById(userId);
if (user == null) {
throw new ServiceException(401, "用户不存在,请重新登录");
}
//验证token
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPwd())).build();
try {
jwtVerifier.verify(token);
} catch (JWTVerificationException e) {
throw new ServiceException(401, "token验证失败,请重新登录");
}
return true;
}
}
拦截器的主要流程:
从 http 请求头中取出 token,
判断是否映射到方法
检查是否有passtoken注解,有则跳过认证
检查有没有需要用户登录的注解,有则需要取出并验证
认证通过则可以访问,不通过会报相关错误信息
boolean preHandle ():
预处理回调方法,实现处理器的预处理,第三个参数为响应的处理器,自定义Controller,返回值为true表示继续流程(如调用下一个拦截器或处理器)或者接着执行postHandle()和afterCompletion();false表示流程中断,不会继续调用其他的拦截器或处理器,中断执行。
2.5然后通过配置类将我们自定义的拦截类注入到spring容器中,并进行拦截配置
package com.wxz.config;
import com.wxz.interceptor.JwtInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 拦截器的配置类
* @author wxz
* @date
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
/**
* 重写addInterceptors()实现拦截器
* 配置:要拦截的路径以及不拦截的路径
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor())
.addPathPatterns("/**") // 拦截所有请求,通过判断token是否合法来决定是否需要登录
.excludePathPatterns(
"/login/doLogin", //登录
"/login/register", //注册
"/file/uploadFile",//文件上传
"/user/sendMsg/*" //发送邮件
);//放行的请求
}
@Bean
public JwtInterceptor jwtInterceptor() {
return new JwtInterceptor();
}
}