一、问题描述
1.背景阐述
在jjwt老版本0.9.1之前,只需要引入jjwt即可生成认证token,至于maven仓库显示为什么在0.9.1版本后jjwt就移动到了jjwt-api,官网并没有详细阐述
jjwt官网阐述了使用方法如下:
2.使用0.9.1及之前的jjwt版本
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
此时只需要编写工具类
package com.juhe.digital.util;
import com.alibaba.fastjson.JSONObject;
import com.juhe.digital.config.JwtConfig;
import com.juhe.digital.pojo.bo.UserBO;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
/**
* @author kiki
* @date 2023/5/5
* @description jwt工具类
*/
@Component
@ConfigurationProperties(prefix = "jwt")
@Slf4j
@Data
public class JwtTokenUtil {
private static final String CLAIM_KEY_USERNAME = "sub";
private static final String CLAIM_KEY_CREATED = "created";
private String secret;
private Integer expiration;
private String tokenHeader;
private Integer expireVerifyCode;
private Integer loginFailRange;
private Integer loginFailMaxNum;
/**
* 从token中获取JWT中的负载
*/
public Claims getClaimsFromToken(String token, String secret) {
Claims claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
return claims;
}
/**
* 根据负责生成JWT的token
*/
private String generateToken(Map<String, Object> claims) {
return Jwts.builder()
.setClaims(claims)
.setExpiration(generateExpirationDate())
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
/**
* 从token中获取JWT中的负载
*/
public Claims getClaimsFromToken(String token) {
Claims claims = null;
try {
claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
log.info("JWT格式验证失败:{}", token);
}
return claims;
}
/**
* 生成token的过期时间
*/
private Date generateExpirationDate() {
return new Date(System.currentTimeMillis() + expiration * 1000);
}
/**
* 从token中获取登录用户名
*/
public String getUserNameFromToken(String token) {
String username;
try {
Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
}
/**
* 验证token是否还有效
*
* @param token 客户端传入的token
* @param userDetails 从数据库中查询出来的用户信息
*/
public boolean validateToken(String token, UserDetails userDetails) {
String username = getUserNameFromToken(token);
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
}
/**
* 判断token是否已经失效
*/
private boolean isTokenExpired(String token) {
Date expiredDate = getExpiredDateFromToken(token);
return expiredDate.before(new Date());
}
/**
* 从token中获取过期时间
*/
public Date getExpiredDateFromToken(String token) {
Claims claims = getClaimsFromToken(token);
return claims.getExpiration();
}
/**
* 根据用户信息生成token
*/
public String generateToken(String userEmail) {
Map<String, Object> claims = new HashMap<>(16);
claims.put(CLAIM_KEY_USERNAME, userEmail);
claims.put(CLAIM_KEY_CREATED, new Date());
return generateToken(claims);
}
/**
* 判断token是否可以被刷新
*/
public boolean canRefresh(String token) {
return !isTokenExpired(token);
}
/**
* 刷新token
*/
public String refreshToken(String token) {
Claims claims = getClaimsFromToken(token);
claims.put(CLAIM_KEY_CREATED, new Date());
return generateToken(claims);
}
/**
* 刷新token
*/
public String refreshToken(Claims claims) {
claims.put(CLAIM_KEY_CREATED, new Date());
return generateToken(claims);
}
public static String getToken() {
String token = "";
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
String th = request.getHeader(JwtConfig.tokenHeader);
if (null != JwtConfig.tokenHeader && JwtConfig.tokenHeader.startsWith(JwtConfig.tokenPrefix)) {
// 截取JWT前缀
token = th.replace(JwtConfig.tokenPrefix, "");
}
return token;
}
}
是这样生成和解析token的
如此便可直接使用。
1.2 项目升级
近期由于项目在对接第三方平台获取互认信息的时候,出现了问题,原因是我们的项目采用的旧版的jjwt,也就是0.9.1之前的版本。在对接第三方进行加解密认证的时候,引入了如下依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.2</version>
</dependency>
大概业务流程是,对方提供了sm4加密的token,首先通过sm4解密获取初始的token信息,再通过jjwt提供的方法来进行解析
Jwt jwt = Jwts.parserBuilder().setSigningKey(key).build().parse(split[1]);
DefaultClaims obj = (DefaultClaims) jwt.getBody();
String token = obj.getSubject();
如此获取到加密的信息。
但是如上述说的,jjwt 0.9.1不是这种写法,在内部实现里边高版本已经摒弃了0.9.1的做法。采用如下方式
public Key generateKey(){
String encode = Base64.encode(secret);
byte[] keyBytes = Decoders.BASE64.decode(encode);
Key key = Keys.hmacShaKeyFor(keyBytes);
return key;
}
/**
* 根据负责生成JWT的token
*/
private String generateToken(Map<String, Object> claims) {
return Jwts.builder()
.setClaims(claims)
.setExpiration(generateExpirationDate())
.signWith(generateKey())
.compact();
}
/**
* 从token中获取JWT中的负载
*/
public Claims getClaimsFromToken(String token) {
Claims claims = null;
try {
claims = Jwts.parserBuilder().setSigningKey(generateKey()).build().parseClaimsJws(token).getBody();
} catch (Exception e) {
log.info("JWT格式验证失败:{}", token);
}
return claims;
}
如此完成版本迭代。