Double Token senseless refresh mechanism is implemented
In modern web application development, front-end separation has become a trend. As a front-end framework, Java is widely used as a combination of back-end languages. In terms of user authentication, JWT is also popular because of its stateless and easy to expand.
This article will introduce in detail how to implement a senseless refresh mechanism of dual tokens in Vue front-end and Java back-end.
Backend dependencies
<dependencies> <dependency> <groupId></groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId></groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId></groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <!-- Other dependencies --> </dependencies>
Security configuration
Configure the Jwt filter and the authentication failure filter.
@Configuration @EnableWebSecurity public class SecurityConfig { /** * Authentication failure handling class */ @Autowired private AuthenticationEntryPointImpl unauthorizedHandler; /** * Jwt filter */ @Autowired private JwtAuthenticationFilter jwtAuthenticationFilter; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf().disable() .cors().and() .addFilterBefore(jwtAuthenticationFilter, ) .headers().cacheControl().disable().and() .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() .sessionManagement().sessionCreationPolicy().and() .authorizeRequests() .antMatchers("/user/login", "/user/forgetPassword/**", "/user/sendUpdatePasswordEmailCode/**", "/user/register", "/", "/user/sendEmailLoginCode", "/user/verifyEmailLoginCode/**").permitAll() .antMatchers("/admin/**").hasRole("ADMIN") .anyRequest().authenticated() .and() .headers().frameOptions().disable(); return (); } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return (); } // Use BCryptPasswordEncoder as the default passwordEncoder for security @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
Jwt filter *
This filter is the core of implementing the token refresh mechanism. Each front-end request carries accessToken and refreshToken. After obtaining this filter, first parse the accessToken. If the parsing fails (Expired), then the refreshToken will be parsed next. After the parsing is completed, if it does not expire, a new accessToken and refreshToken will be generated and returned to the front-end, and a new request header will be setToken-Refreshed, you can set the value at will, just get it at the front end.
package ; import .; import ; import ; import ; import ; import ; import ; import ; import .*; import ; import .slf4j.Slf4j; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; import ; /** * @ClassName: JwtAuthenticationFilter * @author: Hhzzy99 * @date: 2024/3/17 16:09 * description: Inherit filters that will only go through once per request */ @Component @Slf4j public class JwtAuthenticationFilter extends OncePerRequestFilter { @Autowired private RedisCache redisCache; @Value("${}") private Long expiration; @Autowired private IUserMapper userMapper; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // Get the current request path String requestPath = (); // Exclude paths that do not need to be filtered if (("/user/login") || ("/user/register")) { (request, response); return; } // Get token String accessToken = ("access_token"); String refreshToken = ("refresh_token"); if ("null".equals(accessToken) || "".equals(accessToken) || "undefined".equals(accessToken) || null == accessToken) { // Release (request, response); return; } // parse token String userId = ""; boolean isRefresh = false; try { userId = (accessToken).getSubject(); } catch (Exception e) { isRefresh = true; (); } if (isRefresh) { try { userId = (refreshToken).getSubject(); accessToken = (userId); refreshToken = (userId); User loginUser = ((userId)); Integer ttl = () / 1000; ("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); ("userInfo:" + userId, loginUser, ttl, ); writeTokenResponse(response, accessToken, refreshToken); return; } catch (Exception e1) { throw new BusinessException(EnumException.THE_LOGIN_HAS_EXPIRED); } } // Get user information from redis User loginUser = ("userInfo:" + userId); if ((loginUser)) { throw new BusinessException(EnumException.THE_LOGIN_HAS_EXPIRED); } // Save SecurityContextHolder UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, null); ().setAuthentication(authenticationToken); // Release (request, response); } private void writeTokenResponse(HttpServletResponse response, String accessToken, String refreshToken) throws IOException { Map<String, String> tokenMap = new HashMap<>(); ("accessToken", accessToken); ("refreshToken", refreshToken); Map<String, String> headers = new HashMap<>(); ("Token-Refreshed", "true"); CommonResponse<Map<String, String>> commonResponse = new CommonResponse<>(200, "Token refreshed successfully", tokenMap); (response, headers, (commonResponse)); } }
Front-end configuration
Front-end for allaxiosRequest global configuration firstEvery requestWhen setting the request header accessToken and refreshToken, and every request issaveUp, if the backend parses to the accessToken during request, and returns the new accessToken and refreshToken, get the backend settings in the request header.Token-Refreshed, thenagainSave the new accessToken and refreshToken locally in the browser and resend the previously saved request to achieve a senseless refresh.
import axios from 'axios' import {ref} from "vue"; // create an axios instance const service = ({ baseURL: '/api', // url = base url + request url timeout: 20000 // request timeout }) const retryRequest = ref(null) // request interceptor ( config => { // Add header information configuration if (("access_token") !== null && ("access_token") !== undefined){ ['access_token'] = ("access_token") } if (("refresh_token") !== null && ("refresh_token") !== undefined){ ['refresh_token'] = ("refresh_token") } = config return config } ) // response interceptor ( response => { if (['token-refreshed']) { ('Token refresh successfully'); // If there is a Token-Refreshed header, update the token in the local storage ('access_token', ); ('refresh_token', ); ("continue") // Continue to send the original request return axios() } return response; }, async error => { const originalRequest = ; // If the 401 error caused by the token expires and there is no retry mark, try to refresh the token if ( === 401 && !originalRequest._retry) { originalRequest._retry = true; const refreshToken = ('refresh_token'); if (refreshToken) { try { const response = await ('/refresh-token', { refreshToken }); const { accessToken, refreshToken: newRefreshToken } = ; ('access_token', accessToken); ('refresh_token', newRefreshToken); // Update the Authorization header of the original request ['access_token'] = accessToken; // Resend the original request return instance(originalRequest); } catch (refreshError) { // Failed to refresh the token, jump to the login page or perform other processing ('Token refresh failed:', refreshError); // You can jump to the login page or perform other processing here } } } return (error); } ) export default service
Through the above steps, we can implement the dual Token senseless refresh mechanism. This mechanism automatically refreshes when the token expires by combining short-term effective access tokens and long-term effective refreshing tokens.
This example only shows the basic implementation method, and more security and robustness issues need to be considered in the actual production environment.
Summarize
The above is personal experience. I hope you can give you a reference and I hope you can support me more.