SpringBoot项目整合jwt和redis完成登录token校验(含原理解析)
步骤:依次复制粘贴即可
1、引入依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
2、工具类(jwt 原生带的生成token和校验token真伪的工具类)
/**
* 这里是生成token的工具类
*/
@Slf4j
public class JwtUtil {
/**
* 生成jwt
* 使用Hs256算法, 私匙使用固定秘钥
*
* @param secretKey jwt秘钥
* @param ttlMillis jwt过期时间(毫秒)
* @param claims 设置的信息
* @return
*/
public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {
// 指定签名的时候使用的签名算法,也就是header那部分
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 生成JWT的时间
long expMillis = System.currentTimeMillis() + ttlMillis;
Date exp = new Date(expMillis);
// 设置jwt的body
JwtBuilder builder = Jwts.builder()
// 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
.setClaims(claims)
// 设置签名使用的签名算法和签名使用的秘钥
.signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
// 设置过期时间
.setExpiration(exp);
return builder.compact();
}
/**
* Token解密
*
* @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个
* @param token 加密后的token
* @return
*/
public static Claims parseJWT(String secretKey, String token) {
// 得到DefaultJwtParser
Claims claims = Jwts.parser()
// 设置签名的秘钥
.setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
// 设置需要解析的jwt
.parseClaimsJws(token).getBody();
return claims;
}
}
3、登录接口(演示生成token)
/**
* 用户登录方法。
*
* @param userLoginRequest 用户登录请求的数据载体,包含登录所需的信息,如用户名和密码。
* @return 返回登录操作的响应,包含用户信息和JWT令牌。
*/
@PostMapping("/login")
public BaseResponse<LoginUserVo> login(@RequestBody UserLoginRequest userLoginRequest) {
// 1. 检查请求体是否为空。如果是空,则抛出参数错误异常。
if (userLoginRequest == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
// 2. 调用后端服务进行登录操作。该服务将验证用户凭据。
LoginUserVo loginUserVo = userBackendService.login(userLoginRequest);
// 3. 登录成功后,准备生成JWT令牌。首先创建一个用于存放令牌信息的HashMap。
HashMap<String, Object> map = new HashMap<>();
map.put("userId", loginUserVo.getId()); // 将用户ID加入map,用于生成JWT中的claims。
// 4. 调用后端服务的getJwtToken方法,生成JWT令牌。
String jwtToken = userBackendService.getJwtToken(adminSecretKey, adminTtl, map);
// 5. 将生成的JWT令牌设置到登录用户信息对象(LoginUserVo)中。
loginUserVo.setJwt(jwtToken);
// 6. 返回成功的响应,包含用户信息和JWT令牌。
return ResultUtils.success(loginUserVo);
}
4、实现类
String getJwtToken(String adminSecretKey, Long adminTtl, HashMap<String, Object> map);
@Override
public String getJwtToken(String adminSecretKey, Long adminTtl, HashMap<String, Object> map) {
String jwtToken = JwtUtil.createJWT(adminSecretKey, adminTtl, map);
//拿到用户的id
String userId = map.get("userId").toString();
redisTemplate.opsForValue().set(adminTokenKey + "-" + userId, jwtToken, 7200, TimeUnit.SECONDS);
return jwtToken;
}
5、拦截器(拦截器的作用是带token访问其他接口做校验的,利用了redis 和jwt本身的校验)
/**
* JWT验证token是否合法的预处理方法。
*
* @param request 传入的HTTP请求对象,用于获取请求头中的信息。
* @param response HTTP响应对象,用于设置响应状态码。
* @param handler 处理器对象,用于判断请求是否是一个方法处理器。
* @return 返回布尔值,表示是否继续执行下一个拦截器或处理器。
* @throws IOException 可能由于I/O操作引发异常。
*/
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
// 1. 判断处理器是否为方法处理器的实例,如果不是,则直接放行。
if (!(handler instanceof HandlerMethod)) {
return true;
}
// 2. 从请求头中获取"Authorization"字段,此处存放着JWT令牌。
String token = request.getHeader("Authorization");
try {
// 3. 使用JwtUtil的parseJWT方法解析token,并获取其声明(Claims)。
Claims claims = JwtUtil.parseJWT(adminSecretKey, token);
// 4. 从Claims中获取用户ID,这是存放在token中的一个自定义声明。
String userId = claims.get("userId").toString();
// 5. 检查Redis中是否存在以adminTokenKey + "-" + userId为key的数据,以验证token的有效性。
Boolean redisResult = redisTemplate.hasKey(adminTokenKey + "-" + userId);
// 6. 如果在Redis中找不到对应的key,说明token无效。
if (!redisResult) {
response.setStatus(401); // 设置HTTP状态码为401,表示未授权。
throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "Invalid Token"); // 抛出业务异常。
}
// 7. 如果验证通过,则返回true,继续执行请求。
return true;
} catch (Exception e) {
response.setStatus(401); // 验证过程中出现异常也设置HTTP状态码为401。
// 抛出业务异常。
throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "Invalid Token");
}
}
6、配置类(指定哪些路径拦截生效)
@Configuration
@Slf4j
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
@Autowired
JwtTokenAdminHandler jwtTokenAdminHandler;
protected void addInterceptors(InterceptorRegistry registry) {
log.info("自定义拦截器开始");
registry.addInterceptor(jwtTokenAdminHandler)
.addPathPatterns("/user/**")
.addPathPatterns("/order/**")
.excludePathPatterns("/user/login");
}
}
7、解析说明
1、JWT (JSON Web Tokens): JWT是一种用于双方之间传递安全信息的简洁的、URL安全的表达方式。JWT的结构包括三个部分:Header(头部)、Payload(载荷)、Signature(签名)。在这个案例中,JWT用于创建一个用户登录后的令牌,该令牌包含用户的一些基本信息,并且可以被服务器验证其真伪。
2、Redis: Redis是一个高性能的键值对数据库,用于缓存数据。在这个案例中,Redis用来存储JWT令牌,以便于对用户的后续请求进行快速的Token验证。
3、拦截器: 拦截器用于在用户发起请求时拦截请求,并验证JWT Token的有效性。如果Token有效,则请求继续执行;如果无效,则返回错误信息。
8、步骤总结
1、引入JWT依赖: 通过在项目的pom.xml中添加JWT依赖,使得项目能够使用JWT相关的功能。
2、实现JWT工具类: 创建一个JwtUtil类,用于生成和解析JWT。createJWT方法用于创建一个新的JWT,而parseJWT方法用于验证和解析一个已存在的JWT。
3、登录接口实现: 在登录接口中,首先验证用户的登录信息,然后使用JwtUtil生成JWT。这个JWT会被返回给用户,并存储在Redis中,用于后续的请求验证。
4、实现Token生成方法: 在服务类中实现getJwtToken方法,该方法调用JwtUtil生成JWT,并将其存储在Redis中。
5、拦截器实现: 实现一个拦截器,用于检查请求的Token。如果Token存在且有效,请求继续执行;如果无效,则返回错误信息。
6、配置拦截器: 在SpringBoot的配置类中配置拦截器,指定哪些路径需要进行Token验证。
通过这个流程,您的SpringBoot应用能够有效地处理用户身份验证和授权,保障应用的安全性。这种方式既利用了JWT的便捷和安全性,又结合了Redis的高效存储能力,确保了整个认证过程的高效和安全。(图片、解析和原理均由AI生成)