JWT是JSON WEB TOKEN的简写,常用于生成及校验Token。
常见的使用场景为:用户携带name和秘钥访问后端服务器,应用后端在校验通过后使用JWT生成并返回一串Token,后续用户只需要携带此Token就可以访问服务器,在此不多赘述。
本文目的是基于redis实现token自动更新其过期时间,在校验用户姓名和密码后使用JWT工具类生成会过期的Token,当用户携带此Token访问服务器后会自动延长其过期时间。
例如:用户A携带账户名及秘钥获取token,该token过期时间为2小时,过了1小时后用户再次携带该token访问系统,系统会自动将该token过期时间设置为此刻往后2小时候过期。
1、前提
1.1、JWT工具类
public class JwtUtil {
public static final String JWT_ID = "dshsdhsdgjhjdsh";
/**
* jwt 加密解密密钥(可自行填写Base64加密)
*/
private static final String JWT_SECRET = "ahsagsggfTwGGFff";
/**
* 创建JWT
*/
public static String createJwt(Map<String, Object> claims, Long time) {
//指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
Date now = new Date(System.currentTimeMillis());
SecretKey secretKey = generalKey();
//下面就是在为payload添加各种标准声明和私有声明了,new一个JwtBuilder,设置jwt的body
JwtBuilder builder = Jwts.builder()
//如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
.setClaims(claims)
//设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
.setId(JWT_ID)
//iat: jwt的签发时间
.setIssuedAt(now)
//设置过期时间
.setExpiration(new Date(System.currentTimeMillis() + time))
//设置签名使用的签名算法和签名使用的秘钥
.signWith(signatureAlgorithm, secretKey);
return builder.compact();
}
/**
* 验证jwt
*/
public static Claims verifyJwt(String token) {
//签名秘钥,和生成的签名的秘钥一模一样
SecretKey key = generalKey();
Claims claims;
try {
//得到DefaultJwtParser
claims = Jwts.parser()
//设置签名的秘钥
.setSigningKey(key)
.parseClaimsJws(token).getBody();
} catch (Exception e) {
claims = null;
}//设置需要解析的jwt
return claims;
}
/**
* 刷新token并设置过期时间
* @param token 旧的token
* @param newExpirationInMillis 过期时间,单位毫秒
* @return 新的jwt token
*/
public static String updateTokenExpiration(String token, Long newExpirationInMillis) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
Claims claims = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token)
.getBody();
claims.setExpiration(new Date(System.currentTimeMillis()+newExpirationInMillis));
return Jwts.builder()
.setClaims(claims)
.setId(JWT_ID)
.signWith(signatureAlgorithm, secretKey)
.compact();
}
/**
* 由字符串生成加密key
*
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.decodeBase64(JWT_SECRET);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
}
1.2、Maven依赖
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
1.3、配置过期时间及其他常量
private static final long EXPIRE_TIME = 7200 * 1000;
private static final String USER_NAME = "user_name";
private static final String SECRET = "password";
private static final String JWT_TOKEN_USERNAME = "jwt_token:username";
2、思路与流程
2.1、生成Token
步骤一:用户携带userName和Password访问后端接口,当校验通过后使用JWT工具类生成Token;
//校验appId和秘钥
...
//如果校验通过则生成JWT
HashMap<String, Object> jwtMap = new HashMap<>(5);
jwtMap.put(USER_NAME, userName);
jwtMap.put(SECRET, password);
//生成JWT
String jwt = JwtUtil.createJwt(jwtMap, EXPIRE_TIME);
步骤二:将生成Token及过期时间放入redis数据库中
String oldToken = (String) redisClient.get(JWT_TOKEN_USERNAME + USER_NAME);
//判断是否存在旧的Token
if (oldToken != null) {
redisClient.delete(oldToken);
}
//多次获取token只生效最后一次
redisClient.set(jwt, jwt, EXPIRE_TIME);
redisClient.set(JWT_TOKEN_USERNAME + appId, jwt, EXPIRE_TIME);
2.2、校验Token
String valueToken = (String) redisClient.get(token);
if (valueToken == null) {
//输出无效信息
log.error("TOKEN:{}无效", token);
//抛异常
throw new Exception();
} else {
Claims claims = JwtUtil.verifyJwt(valueToken);
if (claims == null) {
log.error("TOKEN:{}已过期", token);
throw new Exception();
}
String newToken = JwtUtil.updateTokenExpiration(valueToken, EXPIRE_TIME);
log.info("刷新后的token为:{}", newToken);
redisClient.set(token, newToken, EXPIRE_TIME);
String appKey = (String) claims.get(USER_NAME);
redisClient.expire(JWT_TOKEN_USERNAME + username, EXPIRE_TIME,TimeUnit.MILLISECONDS);
return claims.get(USER_NAME);
}