Javascript axios 拦截器和异步登录

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/35900230/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 18:21:00  来源:igfitidea点击:

Axios interceptors and asynchronous login

javascriptajaxauthenticationpromiseaxios

提问by Dmitry Shvedov

I'm implementing token authentication in my web app. My access tokenexpires every N minutes and than a refresh tokenis used to log in and get a new access token.

我正在我的网络应用程序中实现令牌身份验证。Myaccess token每 N 分钟过期一次,然后refresh token使用a登录并获取新的access token.

I use Axios for all my API calls. I have an interceptor set up to intercept 401responses.

我所有的 API 调用都使用 Axios。我设置了一个拦截器来拦截401响应。

axios.interceptors.response.use(undefined, function (err) {
  if (err.status === 401 && err.config && !err.config.__isRetryRequest) {
    serviceRefreshLogin(
      getRefreshToken(),
      success => { setTokens(success.access_token, success.refresh_token) },
      error => { console.log('Refresh login error: ', error) }
    )
    err.config.__isRetryRequest = true
    err.config.headers.Authorization = 'Bearer ' + getAccessToken()
    return axios(err.config);
  }
  throw err
})

Basically, as I intercept a 401 response, I want to do a login and than retry the original rejected request with the new tokens. My serviceRefreshLoginfunction calls setAccessToken()in its thenblock. But the problem is that the thenblock happens later than the getAccessToken()in the interceptor, so the retry happens with the old expired credentials.

基本上,当我拦截 401 响应时,我想进行登录,然后使用新令牌重试原始被拒绝的请求。我的serviceRefreshLogin函数setAccessToken()在其then块中调用。但问题是该then块发生的时间晚于getAccessToken()拦截器中的 ,因此重试发生在旧的过期凭据上。

getAccessToken()and getRefreshToken()simply return the existing tokens stored in the browser (they manage localStorage, cookies, etc).

getAccessToken()getRefreshToken()简单地返回存储在浏览器中的现有令牌(它们管理 localStorage、cookies 等)。

How would I go about ensuring statements do not execute until a promise returns?

我将如何确保语句在承诺返回之前不会执行?

(Here's a corresponding issue on github: https://github.com/mzabriskie/axios/issues/266)

(这是github上的相应问题:https: //github.com/mzabriskie/axios/issues/266

回答by ForceUser

Just use another promise :D

只需使用另一个承诺:D

axios.interceptors.response.use(undefined, function (err) {
    return new Promise(function (resolve, reject) {
        if (err.status === 401 && err.config && !err.config.__isRetryRequest) {
            serviceRefreshLogin(
                getRefreshToken(),
                success => { 
                        setTokens(success.access_token, success.refresh_token) 
                        err.config.__isRetryRequest = true
                        err.config.headers.Authorization = 'Bearer ' + getAccessToken();
                        axios(err.config).then(resolve, reject);
                },
                error => { 
                    console.log('Refresh login error: ', error);
                    reject(error); 
                }
            );
        }
        throw err;
    });
});

If your enviroment doesn't suport promises use polyfill, for example https://github.com/stefanpenner/es6-promise

如果您的环境不支持 promise,请使用 polyfill,例如https://github.com/stefanpenner/es6-promise

But, it may be better to rewrite getRefreshToken to return promise and then make code simpler

但是,最好重写 getRefreshToken 以返回承诺,然后使代码更简单

axios.interceptors.response.use(undefined, function (err) {

        if (err.status === 401 && err.config && !err.config.__isRetryRequest) {
            return getRefreshToken()
            .then(function (success) {
                setTokens(success.access_token, success.refresh_token) ;                   
                err.config.__isRetryRequest = true;
                err.config.headers.Authorization = 'Bearer ' + getAccessToken();
                return axios(err.config);
            })
            .catch(function (error) {
                console.log('Refresh login error: ', error);
                throw error;
            });
        }
        throw err;
});

Demo https://plnkr.co/edit/0ZLpc8jgKI18w4c0f905?p=preview

演示https://plnkr.co/edit/0ZLpc8jgKI18w4c0f905?p=preview

回答by Daniel

Could do it in the request instead of the response, and it'd probably be cleaner since it'd avoid hitting the server when the access token's expired. Copying from this article:

可以在请求而不是响应中执行此操作,并且它可能会更清晰,因为它会在访问令牌过期时避免访问服务器。从这篇文章复制:

function issueToken() {
  return new Promise((resolve, reject) => {
    return client({
      ...
    }).then((response) => {
      resolve(response);
    }).catch((err) => {
      reject(err);
    });
  });
}

client.interceptors.request.use((config) => {
  let originalRequest = config;
  if (tokenIsExpired && path_is_not_login) {
    return issueToken().then((token) => {
      originalRequest['Authorization'] = 'Bearer ' + token;
      return Promise.resolve(originalRequest);
    });
  }
  return config;
}, (err) => {
  return Promise.reject(err);
});