Solution for JWT Token auto-renewal
Preface
In a front-end/back-end development model, the back-end service issues a jwt token to the user after the front-end user logs in successfully.
Each subsequent request will put this token in the request header and pass it to the backend service, which will have a filter to intercept the token and check whether the token is expired, and if the token is expired, the frontend will jump to the login page to log in again.
Because the jwt token generally contains the user’s basic information, the token’s expiration time is generally set relatively short in order to ensure the security of the token.
But this will lead to the front-end users need to frequently login (token expiration), and even some forms are more complex, the front-end users need to think for a long time when filling out the form, and when the form is really submitted, the back-end verification found that the token expired and had to jump to the login page.
If this really happens, the user experience is very unfriendly. This article is to realize the automatic token renewal without the front-end user’s awareness, to avoid frequent login and form filling content loss.
Implementation principle
The principle of jwt token auto-renewal is as follows.
- After successful login, the user-generated
jwt token
is stored in the cache as key and value (the key and value values are the same), and the cache validity is set to twice the validity time of the token. - when the user requests again, a
jwt filter
on the backend checks if the front-end token is a valid token, and if the token is invalid, the request is illegal and an exception is thrown directly. - according to the rules to take out the cache token, determine whether the cache token exists, at this time, the following main cases.
- cache token does not exist This situation indicates that the user account has been idle for a long time and the returned user information is no longer valid, please log in again.
- If the cache token exists, you need to use the jwt tool class to verify if the cache token has expired, but there is no need to handle it. If it expires, it means that the user has been operating but the token has expired. The back-end program will regenerate the jwt token for the value value of the key mapping corresponding to the token and overwrite the value value, and the cache life cycle will be recalculated.
The core principle of the implementation logic: The token set in the Header of the front-end request remains unchanged, and the validity of the checksum is based on the token in the cache.
Code implementation (pseudo code)
- issue a token to the user after successful login, and set the validity of the token
SysUser sysUser = userService.getUser(username,password);
if(null ! == sysUser){
String token = JwtUtil.sign(sysUser.getUsername(),
sysUser.getPassword());
}
public static String sign(String username, String secret) {
// Set the token validity to 30 minutes
Date date = new Date(System.currentTimeMillis() + 30 * 60 * 1000);
//use HS256 to generate the token, and the key is the user's password
Algorithm algorithm = Algorithm.HMAC256(secret);
// with username information
return JWT.create().withClaim("username", username).withExpiresAt(date).sign(algorithm);
}
- store the token in redis and set the expiration time to double the expiration time of the token in redis
Sting tokenKey = "sys:user:token" + token;
redisUtil.set(tokenKey, token);
redisUtil.expire(tokenKey, 30 * 60 * 2);
- filter check token, check token validity
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
//get token from header
String token = httpServletRequest.getHeader("token")
if(null == token){
throw new RuntimeException("illegal request, token is necessary!")
}
//parse the token to get the username
String username = JwtUtil.getUsername(token);
//get the user entity based on username, in practice it is fetched from redis
User = userService.findByUser(username);
if(null == user){
throw new RuntimeException("illegal request, token is Invalid!")
}
//check if the token is invalid and renew it automatically
if(!refreshToken(token,username,user.getPassword()){
throw new RuntimeException("illegal request, token is expired!")
}
...
}
- Implement automatic renewal of token
public boolean refreshToken(String token, String userName, String passWord) {
Sting tokenKey = "sys:user:token" + token ;
String cacheToken = String.valueOf(redisUtil.get(tokenKey));
if (StringUtils.isNotEmpty(cacheToken)) { if (StringUtils.isNotEmpty(cacheToken)) {
// verify the token validity, note that the token to be verified is the one in the cache
if (!JwtUtil.verify(cacheToken, userName, passWord)) {
String newToken = JwtUtil.sign(userName, passWord);
// Set the timeout
redisUtil.set(tokenKey, newToken) ;
redisUtil.expire(tokenKey, 30 * 60 * 2);
}
return true;
}
return false;
}
public static boolean verify(String token, String username, String secret) {
try {
// Generate a JWT validator based on the password
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm).withClaim("username", username).build();
// Validate TOKEN
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (Exception exception) {
return false;
}
}
Summary
The core principle of jwt token implementation logic is front-end request Header set token remains unchanged, check the validity of the token in the cache shall prevail, do not directly check the token in the Header. The realization of the principle part of the good experience, the idea is more important than the realization!
JwtUtil
public class JwtUtil {
public static final long EXPIRE_TIME = 30 * 60 * 1000;
public static boolean verify(String token, String username, String secret) {
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm).withClaim("username", username).build();
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (Exception exception) {
return false;
}
}
public static String getUsername(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("username").asString();
} catch (JWTDecodeException e) {
return null;
}
}
public static String sign(String username, String secret) {
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
Algorithm algorithm = Algorithm.HMAC256(secret);
return JWT.create().withClaim("username", username).withExpiresAt(date).sign(algorithm);
}
public static String getUserNameByToken(HttpServletRequest request) {
String accessToken = request.getHeader("X-Access-Token");
String username = getUsername(accessToken);
if (StringUtils.isEmpty(username)) {
throw new RuntimeException("can not get user");
}
return username;
}
}