Information Security 21 min read

Seamless Token Auto‑Refresh with Spring Cloud Gateway and Axios

The article presents a full‑stack solution for seamless token auto‑refresh, combining a Spring Cloud Gateway GlobalFilter that signals expiration with a custom 511 status, Axios response interceptors and a TypeScript timer on the client, plus server‑side token metadata, allowing developers to choose server‑centric or client‑centric refresh strategies.

Java Tech Enthusiast
Java Tech Enthusiast
Java Tech Enthusiast
Seamless Token Auto‑Refresh with Spring Cloud Gateway and Axios

This article discusses the problem of token silent refresh in an authentication server and provides a complete solution covering both server‑side and client‑side implementations.

1. Problem Overview

When a short‑lived access token expires, a refreshToken is used to obtain a new token without user interaction. The challenges include where to implement the refresh logic, how to detect token expiration, and how to replay the original request.

2. Client‑Side Implementation

2.1 Gateway Filter (Server‑Side Interceptor)

In Spring Boot 3 + Java 17, a GlobalFilter is created to check token validity. If the token is expired, the filter returns a custom status code (e.g., 511) to signal the client.

@Component
public class MyAccessFilter implements GlobalFilter, Ordered {
    @Override
    public Mono
filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String uri = request.getURI().getPath();
        HttpMethod method = request.getMethod();
        if (method.matches(HttpMethod.OPTIONS.name()))
            return chain.filter(exchange);
        if (SecurityAccessConstant.REQUEST_LOGGING_URI.equals(uri) && method.matches(HttpMethod.POST.name()))
            return chain.filter(exchange);
        String token = JWTHelper.getToken(request.getHeaders().getFirst(SecurityAccessConstant.HEADER_NAME_TOKEN));
        if (token != null) {
            if (!JWTHelper.isOutDate(token)) {
                return chain.filter(exchange);
            } else {
                if (!SecurityAccessConstant.REQUEST_REFRESH.equals(uri))
                    return ResponseUtils.out(exchange, ResultData.fail(ResultCodeEnum.NEED_TO_REFRESH_TOKEN.getCode(), ResultCodeEnum.NEED_TO_REFRESH_TOKEN.getMessage()));
                return ResponseUtils.out(exchange, ResultData.fail(ResultCodeEnum.RC401.getCode(), ResultCodeEnum.RC401.getMessage()));
            }
        }
        return ResponseUtils.out(exchange, ResultData.fail(ResultCodeEnum.RC401.getCode(), ResultCodeEnum.RC401.getMessage()));
    }
    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}

2.2 Axios Interceptor

The client uses an Axios response interceptor to handle status codes. When a 511 response is received, the interceptor calls the refresh() function to obtain a new token and retries the original request.

service.interceptors.response.use(
    response => response.data.data,
    async error => {
        if (!error.response) return Promise.reject(error);
        const status = error.response.status;
        const authStore = useAuthStore();
        let message = '';
        switch (status) {
            case 401:
                authStore.reset();
                window.sessionStorage.removeItem('isAuthenticated');
                window.sessionStorage.removeItem('token');
                window.sessionStorage.removeItem('refreshToken');
                message = 'token 失效,请重新登录';
                window.location.href = '/auth/login';
                break;
            case 511:
                try {
                    const data = refresh();
                    if (data !== null) {
                        data.then(value => {
                            if (value !== '') {
                                console.log('刷新 token 成功', value);
                                window.sessionStorage.setItem('token', value);
                                error.config.headers['Authorization'] = 'Bearer ' + value;
                                return service(error.config);
                            }
                        }).catch(err => console.error(err));
                    }
                } catch (err) {
                    console.log('请求刷新 token 失败', err);
                    router.push('/login');
                }
                break;
            case 403:
                message = '拒绝访问';
                break;
            case 404:
                message = '请求地址错误';
                break;
            case 500:
                message = '服务器故障';
                break;
            default:
                message = '网络连接故障';
        }
        Message.error(message);
        return Promise.reject(error);
    }
);

2.3 Refresh Function (TypeScript)

The refresh function sends a raw Axios request (instead of the wrapped request.ts ) to avoid interceptor loops.

export async function refresh(): Promise
{
    const refreshToken = window.sessionStorage.getItem('refreshToken');
    if (refreshToken == undefined) return '';
    try {
        const response = await axios({
            method: 'GET',
            url: 'http://127.0.0.1:9001/api/simple/cloud/access/refresh',
            headers: { Authorization: `Bearer ${refreshToken}` }
        });
        if (response.data) {
            return response.data.data;
        } else {
            return '';
        }
    } catch (error) {
        console.log(error);
        return '';
    }
}

2.4 Timer‑Based Proactive Refresh

A MyTimer class periodically checks the remaining token lifetime (using the stored tokenExpire value) and calls refresh() before the token actually expires.

class MyTimer {
    private timerId: any | null = null;
    private delay: number = 30000; // 30 s default
    private minCheck: number = 60000; // 1 min threshold
    start(): void {
        this.timerId = setInterval(async () => {
            const currentToken = window.sessionStorage.getItem('token');
            if (currentToken) {
                const tokenExpireStr = window.sessionStorage.getItem('tokenExpire') as string;
                const expirationTime = parseInt(tokenExpireStr, 10);
                const timeRemaining = expirationTime - Date.now();
                if (timeRemaining <= this.minCheck) {
                    try {
                        await refresh();
                    } catch (error) {
                        console.error('刷新失败:', error);
                        window.sessionStorage.clear();
                        Message.error('token refresh got some problem, please login');
                        window.location.href = '/auth/login';
                    }
                }
            } else {
                Message.error('token invalidate, please login');
                window.location.href = '/auth/login';
            }
        }, this.delay);
    }
    stop(): void {
        if (this.timerId !== null) {
            clearInterval(this.timerId);
            this.timerId = null;
        }
    }
    // setters for delay and minCheck omitted for brevity
}

3. Server‑Side Enhancements

The backend now returns the token expiration timestamp together with the token and refresh token, allowing the client to perform the proactive check without decoding the JWT.

public static Date getExpirationDate(String token) {
    if (StringUtil.isBlank(token)) return null;
    Claims claims = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody();
    return claims.getExpiration();
}

// When issuing tokens
String[] tokenArray = JWTHelper.createToken(userId, email, perms);
map.put("token", tokenArray[0]);
map.put("tokenExpire", JWTHelper.getExpirationDate(tokenArray[0]).getTime());
map.put("refreshToken", tokenArray[1]);

4. Comparison of Server vs. Client Refresh

Server‑Side Advantages : better security, centralized management, reduced client complexity, consistent state across devices.

Client‑Side Advantages : immediate response, offline support, lower server load, flexibility for custom refresh strategies.

5. Conclusion

The article provides a full‑stack approach to transparent token renewal, combining a Spring Cloud Gateway filter, Axios interceptors, a TypeScript timer, and server‑side token metadata. Depending on the application’s security and performance requirements, developers can choose the server‑centric or client‑centric strategy.

AxiosSpringBootgatewayrefreshtokentypescript
Java Tech Enthusiast
Written by

Java Tech Enthusiast

Sharing computer programming language knowledge, focusing on Java fundamentals, data structures, related tools, Spring Cloud, IntelliJ IDEA... Book giveaways, red‑packet rewards and other perks await!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.