环境:JDK17、Springboot3.0.5、jjwt0.12.3、Manve3.8.1
一、jwt 的pom.xml引入
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.12.3</version>
</dependency>
二、jwt操作类
package com.lingyang.system.util.jwt;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SecureDigestAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import static com.fasterxml.jackson.databind.jsonFormatVisitors.JsonValueFormat.UUID;
/**
* @author **文
* @Description:
* @createDate 2024/7/18 17:17
**/
public class JwtUtil {
// //过期时间
// @Value("${jwt.expiration}")
// private Long expiration;
//
// @Value("${jwt.salt}")
// private static String salt;
//
//过期时间
final static Integer expiration = 72000;
final static String tokenKey = "ysyysyysyysyysyysyysyysyysyysyys";
final static String tokenname = "sys-token";
/**
* 1.从本地文件读取秘钥
* 2.工程.yml中配置salt
* 3.最终生成jwt秘钥,秘钥组成:MD5(1+2)
*/
private static SecretKey secret = Keys.hmacShaKeyFor(tokenKey.getBytes());
/**
* 初始化负载内数据
* @param username 用户名
* @return 负载集合
*/
private Map<String,Object> initClaims(String username){
Map<String, Object> claims = new HashMap<>();
//"iss" (Issuer): 代表 JWT 的签发者。在这个字段中填入一个字符串,表示该 JWT 是由谁签发的。例如,可以填入你的应用程序的名称或标识符。
claims.put("iss","jx");
//"sub" (Subject): 代表 JWT 的主题,即该 JWT 所面向的用户。可以是用户的唯一标识符或者其他相关信息。
claims.put("sub",username);
//"exp" (Expiration Time): 代表 JWT 的过期时间。通常以 UNIX 时间戳表示,表示在这个时间之后该 JWT 将会过期。建议设定一个未来的时间点以保证 JWT 的有效性,比如一个小时、一天、一个月后的时间。
claims.put("exp",generatorExpirationDate());
//"aud" (Audience): 代表 JWT 的接收者。这个字段可以填入该 JWT 预期的接收者,可以是单个用户、一组用户、或者某个服务。
claims.put("aud","internal use");
//"iat" (Issued At): 代表 JWT 的签发时间。同样使用 UNIX 时间戳表示。
claims.put("iat",new Date());
//"jti" (JWT ID): JWT 的唯一标识符。这个字段可以用来标识 JWT 的唯一性,避免重放攻击等问题。
claims.put("jti",UUID.toString()); //.randomUUID().toString()
//"nbf" (Not Before): 代表 JWT 的生效时间。在这个时间之前 JWT 不会生效,通常也是一个 UNIX 时间戳。我这里不填,没这个需求
return claims;
}
// /**
// * 根据用户信息生成token
// *
// * @param userDetails 用户信息
// * @return token
// */
// public String generatorToken(UserDetails userDetails)
// {
// Map<String, Object> claims = initClaims(userDetails.getUsername());
// return generatorToken(claims);
// }
/**
* 根据负载生成JWT token
* @param claims 负载
* @return token
*/
public String generatorToken(Map<String, Object> claims){
return Jwts.builder()
.claims(claims)
.signWith(secret,Jwts.SIG.HS256)
.compact();
}
/**
* 生成失效时间,以秒为单位
*
* @return 预计失效时间
*/
private Date generatorExpirationDate()
{
//预计失效时间为:token生成时间+预设期间
return new Date(System.currentTimeMillis() + expiration * 1000);
}
/**
* 从Token中获取用户名
* @param token token
* @return 用户名
*/
public String getUserNameFromToken(String token){
String username;
try
{
username = getPayloadFromToken(token).getSubject();
}catch (Exception e){
username = null;
}
return username;
}
/**
* 从Token中获取负载中的Claims
* @param token token
* @return 负载
*/
public static Claims getPayloadFromToken(String token)
{
return Jwts.parser()
.verifyWith(secret)
.build()
.parseSignedClaims(token)
.getPayload();
}
// /**
// * 验证token是否有效
// * @param token 需要被验证的token
// * @param userDetails true/false
// * @return
// */
// public boolean validateToken(String token,UserDetails userDetails){
// return getUserNameFromToken(token).equals(userDetails.getUsername()) && !isTokenExpired(token);
// }
/**
* 判断token是否有过期
* @param token 需要被验证的token
* @return true/false
*/
private boolean isTokenExpired(String token)
{
//判断预设时间是否在当前时间之前,如果在当前时间之前,就表示过期了,会返回true
return getExpiredDateFromToken(token).before(new Date());
}
/**
* 从token中获取预设的过期时间
* @param token token
* @return 预设的过期时间
*/
private Date getExpiredDateFromToken(String token)
{
return getPayloadFromToken(token).getExpiration();
}
/**
* 判断token是否可以被刷新
* @param token 需要被验证的token
* @return true/false
*/
public boolean canRefresh(String token){
return !isTokenExpired(token);
}
/**
* 刷新token
* @param token 需要被刷新的token
* @return 刷新后的token
*/
public String refreshToken(String token){
Claims claims = getPayloadFromToken(token);
Map<String, Object> initClaims = initClaims(claims.getSubject());
initClaims.put("iat",new Date());
return generatorToken(initClaims);
}
}
三、创建interceptor拦截器
package com.lingyang.system.util.interceptor;
import com.lingyang.system.util.jwt.JwtUtil;
import com.lingyang.system.util.redis.until.RedisUtil;
import io.jsonwebtoken.Claims;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.Objects;
/**
* @author **文
* @Description:
* @createDate 2024/7/18 17:33
**/
@Component
public class JwtAdminTokenInterceptor implements HandlerInterceptor {
//过期时间
final static String expiration = "72000";
final static String tokenKey = "ysyysyysyysyysyysyysyysyysyysyys";
final static String tokenname = "sys-token";
/**
* 拦截器 校验jwt
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{
//判断当前拦截到的是Controller的方法还是其他资源
if(!(handler instanceof HandlerMethod)){
//当前拦截到的不是动态方法,直接放行
return true;
}
//从请求头中获取令牌
String token = request.getHeader(tokenname);
if( Objects.isNull(token) ){
throw new RuntimeException("令牌不能为空");
}
//校验令牌
try{
Claims payloadFromToken = JwtUtil.getPayloadFromToken(token);
RedisUtil redisUtil = new RedisUtil();
String phone = redisUtil.hGet(tokenname,"admin-" + payloadFromToken.get("id"));
if(Objects.isNull(phone)){
throw new RuntimeException("验证错误");
}
if( !phone.equals( payloadFromToken.get("phone").toString() ) ){
throw new RuntimeException("token错误");
}
//放行
return true;
}catch (Exception e){
//不通过,响应401状态码
throw new RuntimeException(e.getMessage());
}
}
}
四、拦截器注册到项目中
package com.lingyang.system.util.config;
import com.lingyang.system.util.interceptor.JwtAdminTokenInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
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 **文
* @Description:
* @createDate 2024/7/18 18:22
**/
@Configuration
public class SystemApiConfig implements WebMvcConfigurer {
@Autowired
private JwtAdminTokenInterceptor jwtAdminTokenInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(jwtAdminTokenInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/")
.excludePathPatterns("/admin/login")
.excludePathPatterns("/v3/api-docs");
}
}
addPathPatterns 拦截的路径 "/**" 所有路径
excludePathPatterns 豁免的路径