首页 > 解决方案 > 通过 axios 中的拦截器自动刷新访问令牌

问题描述

我们最近在这个问题中讨论了用于 OAuth 身份验证令牌刷新的 axios 拦截器。

拦截器应该做的是拦截任何带有401状态码的响应并尝试刷新令牌。考虑到这一点,接下来要做的是从拦截器返回一个 Promise,这样任何通常会失败的请求都会在令牌刷新后运行,因为没有任何反应。

主要问题是,拦截器只检查401状态码,这还不够,因为它refreshToken也会401在失败时返回状态码——而且我们有一个循环。

我想到了两种可能的情况:

  1. 检查调用的 URL,所以如果是这样,/auth/refresh它不应该尝试刷新令牌;
  2. refreshToken调用逻辑时省略拦截器

第一个选项对我来说看起来有点“不动态”。第二种选择看起来很有希望,但我不确定它是否可能。

那么主要的问题是,我们如何区分/识别拦截器中的调用并为它们运行不同的逻辑而无需专门“硬编码”它,或者有什么方法可以省略指定调用的拦截器?先感谢您。

拦截器的代码可能有助于理解这个问题:

Axios.interceptors.response.use(response => response, error => {
    const status = error.response ? error.response.status : null

    if (status === 401) {
        // will loop if refreshToken returns 401
        return refreshToken(store).then(_ => {
            error.config.headers['Authorization'] = 'Bearer ' + store.state.auth.token;
            error.config.baseURL = undefined;
            return Axios.request(error.config);
        })
        // Would be nice to catch an error here, which would work if the interceptor is omitted
        .catch(err => err);
    }

    return Promise.reject(error);
});

和令牌刷新部分:

function refreshToken(store) {
    if (store.state.auth.isRefreshing) {
        return store.state.auth.refreshingCall;
    }

    store.commit('auth/setRefreshingState', true);
    const refreshingCall = Axios.get('get token').then(({ data: { token } }) => {
        store.commit('auth/setToken', token)
        store.commit('auth/setRefreshingState', false);
        store.commit('auth/setRefreshingCall', undefined);
        return Promise.resolve(true);
    });

    store.commit('auth/setRefreshingCall', refreshingCall);
    return refreshingCall;
}

标签: oauth-2.0oauthaxiosinterceptor

解决方案


我可能找到了一种更简单的方法来处理这个问题:当我调用 /api/refresh_token 端点时,使用 axios.interceptors.response.eject() 禁用拦截器,然后重新启用它。

编码 :

createAxiosResponseInterceptor() {
    const interceptor = axios.interceptors.response.use(
        response => response,
        error => {
            // Reject promise if usual error
            if (error.response.status !== 401) {
                return Promise.reject(error);
            }
            
            /* 
             * When response code is 401, try to refresh the token.
             * Eject the interceptor so it doesn't loop in case
             * token refresh causes the 401 response
             */
            axios.interceptors.response.eject(interceptor);

            return axios.post('/api/refresh_token', {
                'refresh_token': this._getToken('refresh_token')
            }).then(response => {
                saveToken();
                error.response.config.headers['Authorization'] = 'Bearer ' + response.data.access_token;
                return axios(error.response.config);
            }).catch(error => {
                destroyToken();
                this.router.push('/login');
                return Promise.reject(error);
            }).finally(createAxiosResponseInterceptor);
        }
    );
}

推荐阅读