One, the disadvantages of a single token
Tokens are generally stored in the browser and are easily stolen. In order to prevent them from being stolen for a long time, the effective time of the token must not be set too long. This move will inevitably cause frequent logins from users, bringing users a bad experience.
2. Double token (accessToken, refreshToken)
(I) Design ideas
1. Store accessToken as the identifier of whether to log in in and in the browser. Since the token can be seen by the browser, it is easy to be stolen, and the effective time can be set as short as possible to solve the problem of long-term use of theft
2. Use refreshToken as the identifier of whether to update the accessToken. As long as the refreshToken expires, the accessToken will be automatically updated. The valid time can be set longer to solve the problem of frequent login.
3. Do not store the refreshToken directly to the browser, but store it in the payload of the accessToken. The backend obtains the refreshToken by obtaining the payload content of the accessToken to prevent the refreshToken from being stolen.
4. AccessToken composition:
Load: refreshToken
Valid time: as short as possible (I set it to 30 minutes here)
Key: User's password
RefreshToken composition:
Load: user's id
Valid time: long (I set it to one day here)
Key: User's password
(II) Backend code
1. TokenUtil (generate token and obtain user information)
See the comments for specific steps
@Component @Slf4j public class TokenUtils { private static IUserService staticAdminService; @Resource private IUserService adminService; @PostConstruct public void setUserService() { staticAdminService = adminService; } // Generate tokens public static String genToken(String adminId, String sign, Integer time) { // Generate expiration time Calendar instance = (); (, time*60); return ().withAudience(adminId) //Load .withExpiresAt(()) //Expiration after time minutes .sign(Algorithm.HMAC256(sign)); // Key } //Get user information based on accessToken // 1. Get the refreshToken through the accessToken payload // 2. Get userId through refreshToken's load // 3. Call the method of obtaining user information based on user ID to get user information public static User getCurrentAdmin() { String accessToken = null; try { HttpServletRequest request = ((ServletRequestAttributes) ()).getRequest(); accessToken = ("token"); ("access"+accessToken); if ((accessToken)) { ("Get the currently logged inaccessTokenfail, token: {}", accessToken); return null; } String refreshToken = (accessToken).getAudience().get(0); if ((refreshToken)) { ("Get the currently logged inrefreshTokenfail, token: {}", refreshToken); return null; } String userId = (refreshToken).getAudience().get(0); return ((userId)); } catch (Exception e) { ("Get the currently logged in管理员信息fail, token={}", accessToken, e); return null; } } }
2. JwtInterceptor (check whether accessToken is legal)
Implementation ideas:
(1) Check whether the accessToken is empty
(2) Get refreshToken through the load content of the accessToken
(3) Verify whether the obtained refreshToken is legal. If it is not legal, the accessToken is an invalid token, and the key is returned if it is legal.
(4) Use this key to determine whether the accessToken has expired
@Slf4j public class JwtInterceptor implements HandlerInterceptor { @Autowired private IUserService userService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String accessToken = ("token"); if ((accessToken)) { accessToken = ("token"); } //Execute authentication if ((accessToken)) { throw new ServiceException(ErrorCode.TOKEN_NO_EXIST.getCode(), ErrorCode.TOKEN_NO_EXIST.getMsg()); } String refreshToken; try{ refreshToken = (accessToken).getAudience().get(0); }catch (Exception e){ throw new ServiceException(ErrorCode.TOKEN_ERROR.getCode(), ErrorCode.TOKEN_ERROR.getMsg()); } String password = verifyRefreshToken(refreshToken); try { // User password added verification token JWTVerifier jwtVerifier = (Algorithm.HMAC256(password)).build(); (accessToken); // Verify token } catch (JWTVerificationException e) { throw new ServiceException(ErrorCode.TOKEN_ERROR.getCode(), ErrorCode.TOKEN_ERROR.getMsg()); } return true; } //Verify whether refreshToken is legal public String verifyRefreshToken(String token){ // Get the adminId in token String adminId; User user; try { adminId = (token).getAudience().get(0); // Query the database based on userid in token user = ((adminId)); (user); } catch (Exception e) { (); throw new ServiceException(ErrorCode.REFRESE_TOKEN_ERROR.getCode(), ErrorCode.REFRESE_TOKEN_ERROR.getMsg()); } if (user == null) { throw new ServiceException(ErrorCode.USER_NO_EXIST.getCode(), ErrorCode.USER_NO_EXIST.getMsg()); } try { // User password added verification token JWTVerifier jwtVerifier = (Algorithm.HMAC256(())).build(); (token); // Verify token } catch (JWTVerificationException e) { throw new ServiceException(ErrorCode.REFRESE_TOKEN_ERROR.getCode(), ErrorCode.REFRESE_TOKEN_ERROR.getMsg()); } return (); } }
3. WebConfig (set interception rules)
@Configuration public class WebConfig implements WebMvcConfigurer { // Add a custom interceptor JwtInterceptor to set interception rules @Override public void addInterceptors(InterceptorRegistry registry) { (jwtInterceptor()).addPathPatterns("/**").excludePathPatterns("/user/login","/user/register","/refreshToken/refresh"); } @Bean public JwtInterceptor jwtInterceptor(){ return new JwtInterceptor(); } }
4. Generate accessToken and refreshToken at the login interface
String refreshToken= (().toString(),(),24*60); String accessToken=(refreshToken,(),30);
5. Write an interface to update refreshToken
Implementation ideas:
(1) Check whether the accessToken is empty
(2) Get refreshToken through the load content of the accessToken
(3) Verify whether the obtained refreshToken is legal or illegal, then the accessToken is an invalid token (the invalid token here refers to the token written by itself, not a token generated by the backend). The accessToken is not updated, and a new accessToken is regenerated if it is legal.
@RestController @RequestMapping("/refreshToken") public class refreshTokenController { @Autowired private IUserService userService; @GetMapping("/refresh") public Result refresh(@RequestParam String accessToken) { if ((accessToken)) { throw new ServiceException(ErrorCode.TOKEN_NO_EXIST.getCode(), ErrorCode.TOKEN_NO_EXIST.getMsg()); } String refreshToken; try{ refreshToken = (accessToken).getAudience().get(0); }catch (Exception e){ throw new ServiceException(ErrorCode.TOKEN_ERROR.getCode(), ErrorCode.TOKEN_ERROR.getMsg()); } if((refreshToken)){ return (ErrorCode.REFRESH_TOKEN_NULL.getCode(), ErrorCode.REFRESH_TOKEN_NULL.getMsg()); } // Get the adminId in token String adminId; User user; try { adminId = (refreshToken).getAudience().get(0); // Query the database based on userid in token user = ((adminId)); (user); } catch (Exception e) { (); throw new ServiceException(ErrorCode.REFRESE_TOKEN_ERROR.getCode(), ErrorCode.REFRESE_TOKEN_ERROR.getMsg()); } if (user == null) { throw new ServiceException(ErrorCode.USER_NO_EXIST.getCode(), ErrorCode.USER_NO_EXIST.getMsg()); } try { // User password added verification token JWTVerifier jwtVerifier = (Algorithm.HMAC256(())).build(); (refreshToken); // Verify token } catch (JWTVerificationException e) { throw new ServiceException(ErrorCode.REFRESE_TOKEN_ERROR.getCode(), ErrorCode.REFRESE_TOKEN_ERROR.getMsg()); } String currentAccessToken= (refreshToken,(),30); return (currentAccessToken); } }
(III) Front-end code
1. After logging in successfully, store accessToken
There is no limit on storage method. I store it in localStorage and share the data in vuex
// const TOKEN_KEY = 'tk' export const getInfo = () => { const token = (TOKEN_KEY) return token || '' } export const setInfo = (token) => { (TOKEN_KEY, token) } export const removeInfo = () => { (TOKEN_KEY) }
//token moduleimport { getInfo, setInfo ,removeInfo} from '@/utils/storage' export default { namespaced: true, state () { return { tokenInfo: getInfo() } }, mutations: { setTokenInfo (state, info) { = info setInfo() }, removeTokenInfo () { removeInfo() } }, actions: {} }
('token/setTokenInfo', data)
2. Set the request header at the axios request interceptor
(function (config) { const accessToken = if (accessToken) { = accessToken } return config }, function (error) { // What to do about the request error return (error) })
3. Axios response interceptor configuration
Set the accessToken expires and determine whether the refreshToken expires. If it has not expired, regenerate the accessToken and resend the request.
(async function (response) { if ( === 200) { return } else if ( === 1008 || === 1009 || === 1010) { //accessToken expires const { data } = await refreshToken() ('token/setTokenInfo', data) return instance() } else if ( === 1014) { //refreshToken expires = '/login' } else { .$() return () } }, function (error) { // Do something to respond to errors return (error) })
4. Guard routing configuration
Determine whether there is an accessToken, whether it is on the login registration page, if there is no, then jump to the login page and log in again
(async (to, from, next) => { const token = if ( // Check whether the user is logged in !token && // ❗️ Avoid infinite redirection !== '/login' && !== '/register' ) { // Redirect the user to the login page next({ path: '/login' }) } else { next() } })
This is the end of this article about the implementation of springboot integration of JWT dual tokens. For more related springboot dual token content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!