SoFunction
Updated on 2025-04-11

Implementation of dual token senseless refresh mechanism

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.