通过axios中的拦截器自动刷新访问令牌

Daw*_*ski 10 oauth interceptor oauth-2.0 vue.js 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);
});
Run Code Online (Sandbox Code Playgroud)

和令牌刷新部分:

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;
}
Run Code Online (Sandbox Code Playgroud)

Ism*_*oev 27

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

代码 :

createAxiosResponseInterceptor() {
    const interceptor = axios.interceptors.response.use(
        response => response,
        error => {
            // Reject promise if usual error
            if (errorResponse.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);
        }
    );
}
Run Code Online (Sandbox Code Playgroud)

  • 抱歉,但是我不知道如何使用该解决方案,能否请您提供一个提示,说明如何将该功能注入到axios中。 (2认同)
  • 当然,这意味着在刷新请求之前返回的所有挂起请求都不会被拦截器捕获,因此在接收到新令牌时将不会重试。 (2认同)
  • 如何在我的 axios 配置中使用此功能? (2认同)
  • 要使用拦截器: `axios.interceptors.response.use(createAxiosResponseInterceptor);` 在此处阅读更多内容:https://github.com/axios/axios#interceptors (2认同)

小智 7

这是我的实现,可以避免刷新路由返回时的无限循环401

为了避免无限循环,我使用一个干净的 axios 实例,没有拦截器来刷新令牌。

我正在使用cookie,但如果您使用的是localStorage,您可以参考其他答案。

src/api/index.js

import axios from 'axios'

const baseURL = process.env.NODE_ENV === 'production' ? '/api' : http://localhost:5000/api`

const axiosInstance = axios.create({
  baseURL,
  timeout: 30000
})

axiosInstance.interceptors.response.use(response => response, error => {
  const { response, config } = error

  if (response.status !== 401) {
    return Promise.reject(error)
  }

  // Use a 'clean' instance of axios without the interceptor to refresh the token. No more infinite refresh loop.
  return axios.get('/auth/refresh', {
    baseURL,
    timeout: 30000
  })
    .then(() => {
      // If you are using localStorage, update the token and Authorization header here
      return axiosInstance(config)
    })
    .catch(() => {
      return Promise.reject(error)
    })
})

export default axiosInstance
Run Code Online (Sandbox Code Playgroud)

Vue我在前端使用。


wal*_*ali 6

不确定这是否符合您的要求,但另一种解决方法也可以是axios.create用于 refreshToken 和其余 API 调用的单独 Axios 实例(使用方法)。通过这种方式,您可以轻松绕过默认拦截器,以便在 refreshToken 的情况下检查 401 状态。

所以,现在你的普通拦截器是一样的。

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);
});
Run Code Online (Sandbox Code Playgroud)

而且,您的 refreshToken 将是这样的:

const refreshInstance = Axios.create();

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

  store.commit('auth/setRefreshingState', true);
  const refreshingCall = refreshInstance.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;
}
Run Code Online (Sandbox Code Playgroud)

这里有一些不错的链接[1] [2],您可以参考 Axios 实例