springboot配置JJWT并存入Redis
在前后端分离项目中,我们的接口不能随便访问。但是我们可以通过token进行认证,认证成功了,我们就可以获取这个接口的数据了。
1、首先我们得导入 jar 包
<!-- JJwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
2、在 application.yaml 里配置 jwt 需要的配置
jwt:
#设置过期时间
expirationDate: 7
# 时间类型,HOUR、DAY
timeUnit: DAYS
# 请求头名字
tokenHeaderName: jzbooks-api
# jey的签名
key: 3686fhahf#%&#h#VD%@S!
# 配置拦截与不拦截的url
interceptor:
includePathPatterns: /**
# 格式 /sys_user,sys_user/l,
excludePathPatterns: /hello
3、创建一个 JwtUtil 类,提供我们创建创建 token、验证 token一系列的方法
package com.jzbooks.utils;
import io.jsonwebtoken.*;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* @author mingPeng
* @date 2021/4/25 21:41
* description : jwt工具类,帮助我们创建jwt、验证jwt
*/
@Component
public class JwtUtil {
/***
* 配置文件中过期时间,默认为30天
*/
@Value("${jwt.expirationDate}")
public Long expirationDate;
/***
* 配置文件中的key
*/
@Value("${jwt.key}")
public String jwtKey;
/**
* 过期时间的时间单位
*/
@Value("${jwt.timeUnit}")
protected TimeUnit timeUnit = TimeUnit.DAYS;
/**
* 请求头
*/
@Value("${jwt.tokenHeaderName}")
private String tokenHeaderName;
@Autowired
@Qualifier("RedisUtil")
private RedisUtil redisUtil;
/**
* @deprecated : 生成 token
* @param userId 如果是用户表,存的userId是学号,管理员表,存的是主键
* @return
*/
public String createToken(String userId){
//创建payload的私有声明
Map<String, Object> claims = new HashMap<String, Object>(2);
claims.put("userId",userId);
//指定签名的时候使用的签名算法
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
//生成签名的时候使用的秘钥secret
SecretKey key = generalKey();
//创建jwt
JwtBuilder builder = Jwts.builder()
.setClaims(claims)
.setId(UUID.randomUUID().toString())
.signWith(signatureAlgorithm,key);
//设置过期时间
Calendar instance=Calendar.getInstance();
instance.add(Calendar.DATE,expirationDate.intValue());
builder.setExpiration(instance.getTime());
//生成token
String token=builder.compact();
//存入redis
redisUtil.set("user_id_token:"+userId+":token",token,expirationDate,timeUnit);
return token;
}
/**
* @deprecated : 解密jwt,得到payload
* @param token
* @return
*/
public Claims parseJWT(String token) {
//签名秘钥,和生成的签名的秘钥一模一样
SecretKey key = generalKey();
//返回的数据
Claims claims;
try {
claims = Jwts.parser() //得到DefaultJwtParser
.setSigningKey(key) //设置签名的秘钥
.parseClaimsJws(token).getBody();
} catch (ExpiredJwtException e) {
// 过期的话从过期的数据里取得 claims
claims = e.getClaims();
}catch (Exception e) {
claims = null;
}//设置需要解析的jwt
return claims;
}
/**
* @deprecated : 拿到前端的token,进行验证
* @param request
* @return
*/
public boolean verifyJWTAndRefreshExpireTime(HttpServletRequest request, HttpServletResponse response){
boolean verifyValue=false;
//前端携带的token
String token=request.getHeader(tokenHeaderName);
//携带token
System.out.println("StringUtils.isEmpty(token): "+!StringUtils.isEmpty(token));
if (!StringUtils.isEmpty(token)) {
//解析jwt,得到claims
Claims claims = parseJWT(token);
if (claims != null) {
// 用户编号
String jwtId = claims.getId();
//得到用户id
String userId = (String) claims.get("userId");
//得到用户类型
// Integer type = (Integer) claims.get("type");
//拼凑redis key
String key = "user_id_token:" + userId + ":token";
Object tokenValue = redisUtil.get(key);
//redis有token
if (tokenValue != null
&& !StringUtils.isEmpty(tokenValue.toString())
&& tokenValue.equals(token)) {
Long times = claims.getExpiration().getTime() - System.currentTimeMillis();
//token已过期,重新生成token并存入redis
if (times < 0) {
String createToken = createToken(userId);
response.addHeader(tokenHeaderName, createToken);
} else {
//重新更新失效时间
redisUtil.expireOfTimeUnit(key, expirationDate, timeUnit);
}
verifyValue = true;
}
}
}
//没有token返回false
return verifyValue;
}
/**
* @deprecated : 使 JWT 失效
* @param null
* @return
*/
public void invalidJwt(){
//得到payload
Claims claims = getClaimsFromJwt();
//取出用户id
String userId=(String) claims.get("userId");
//redis的key
String key="user_id_token:" + userId + ":token";
//删除key
redisUtil.del(key);
}
/**
* 从SpringContext获取Request,然后获取JWT中Claims
*
* @return Claims
*/
public Claims getClaimsFromJwt() {
String token = SpringUtil.getRequest().getHeader(tokenHeaderName);
return parseJWT(token);
}
/**
* 由字符串生成加密key
*
* @return
*/
public SecretKey generalKey() {
//本地配置文件中加密的密文
String stringKey = jwtKey;
//本地的密码解码
byte[] encodedKey = Base64.decodeBase64(stringKey);
// 根据给定的字节数组使用AES加密算法构造一个密钥,使用 encodedKey中的始于且包含 0 到前 length 个字节这是当然是所有。
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
}
4、配置 token 拦截器
@Component
@Slf4j
@Getter
@Setter
public class TokenInterceptor implements HandlerInterceptor {
@Value("${interceptor.includePathPatterns}")
private List<String> includePathPatterns;
@Value("${interceptor.excludePathPatterns}")
private List<String> excludePathPatterns;
/**
* 请求头
*/
@Value("${jwt.tokenHeaderName}")
private String tokenHeaderName;
@Autowired
private JwtUtil jwtUtil;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断是否存在token,没有则拦截请求
if (!jwtUtil.verifyJWTAndRefreshExpireTime(request,response)){
log.info("没有token");
response.setContentType(APPLICATION_JSON.toString());
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
String responseBodyText = JSONObject.toJSONString(ErrorResult.failure(
new BusinessException(ResultCodeEnum.ILLEGAL_TOKEN)));
PrintWriter pw = response.getWriter();
pw.write(responseBodyText);
pw.flush();
pw.close();
return false;
}
return true;
}
}
5、添加到拦截器
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
/**
* token 验证器
*/
@Autowired
private TokenInterceptor tokenInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//添加拦截器
InterceptorRegistration interceptorRegistration = registry.addInterceptor(tokenInterceptor);
if (!CollectionUtils.isEmpty(tokenInterceptor.getIncludePathPatterns())) {
interceptorRegistration.addPathPatterns(tokenInterceptor.getIncludePathPatterns());
}
if (!CollectionUtils.isEmpty(tokenInterceptor.getExcludePathPatterns())) {
interceptorRegistration.excludePathPatterns(tokenInterceptor.getExcludePathPatterns());
}
}
}