How to Handle Refresh Token When Multiple Requests are going out?
我正在使用 reactjs、mbox 和 axios,但遇到了问题。我有一个提供访问令牌和刷新令牌的 API。访问令牌每 20 分钟失效一次,当发生这种情况时,服务器会发回 401,我的代码将自动发送刷新令牌以获取新的访问令牌。
一旦授予新的访问令牌,将再次发送相同的拒绝请求。现在我的代码运行良好,直到我抛出多个几乎可以同时触发的拒绝。
所以第一个请求关闭,一个 401 被发送回来并获得一个新的刷新令牌,所有其他请求都将尝试做同样的事情,但其他请求现在将失败,因为将使用刷新令牌并且将向第一个请求发出一个新请求。
这将启动我的代码,将用户重定向到登录页面。
所以基本上我被困在一次只有 1 个请求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | export const axiosInstance = axios.create({ baseURL: getBaseUrl(), timeout: 5000, contentType:"application/json", Authorization: getAuthToken() }); export function updateAuthInstant() { axiosInstance.defaults.headers.common["Authorization"] = getAuthToken(); } function getAuthToken() { if (localStorage.getItem("authentication")) { const auth = JSON.parse(localStorage.getItem("authentication")); return `Bearer ${auth.accessToken}`; } } axiosInstance.interceptors.response.use( function(response) { return response; }, function(error) { const originalRequest = error.config; if (error.code !="ECONNABORTED" && error.response.status === 401) { if (!originalRequest._retry) { originalRequest._retry = true; return axiosInstance .post("/tokens/auth", { refreshToken: getRefreshToken(), grantType:"refresh_token", clientId :"myclient" }) .then(response => { uiStores.authenticaionUiStore.setAuthentication(JSON.stringify(response.data)) updateAuthInstant(); return axiosInstance(originalRequest); }); } else { uiStores.authenticaionUiStore.logout(); browserHistory.push({ pathname: '/login',}); } } return Promise.reject(error); } ); |
编辑
我遇到的问题是,当用户在直接 url 中复制时,我需要检查以重置身份验证的代码不起作用
app.js
1 2 3 4 5 | <React.Fragment> <Switch> <Route path="/members" component={MemberAreaComponent} /> </Switch> </React.Fragment > |
在memberAreaComponent
1 | <Route path="/members/home" component={MembersHomeComponent} /> |
当我输入
1 2 3 | MembersHomeComponent - componentDidMount runs first MemberAreaComponent - componentDidMount runs second AppCoontainer = componentDidMount runs last. |
嗨,我在 react/redux 应用程序中实现了相同的场景。但它会帮助你实现目标。您不需要在每个 API 调用中检查 401。只需在您的第一个验证 API 请求中实现它。您可以使用 setTimeOut 在身份验证令牌到期之前发送刷新令牌 api 请求。因此 locatStorage 将得到更新,并且所有 axios 请求都不会获得过期的令牌。
这是我的解决方案:
在我的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | export const USER_TOKEN = { set: ({ token, refreshToken }) => { localStorage.setItem('access_token', token); localStorage.setItem('refresh_token', refreshToken); }, remove: () => { localStorage.removeItem('access_token'); localStorage.removeItem('refresh_token'); }, get: () => ({ agent: 'agent', token: localStorage.getItem('access_token'), refreshToken: localStorage.getItem('refresh_token'), }), get notEmpty() { return this.get().token !== null; }, }; export const DEFAULT_HEADER = { get: () => ({ 'Content-type': 'application/json;charset=UTF-8', agent: `${USER_TOKEN.get().agent}`, access_token: `${USER_TOKEN.get().token}`, }), }; |
在页面加载时,User Validate API 请求如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | dispatch(actions.validateUser(userPayload)) // First time authentication with user credentials and it return access token, refresh token and expiry time .then(userData => { const { expires_in, access_token, refresh_token } = userData USER_TOKEN.set({ // setting tokens in localStorage to accessible to all API calls token: access_token, refreshToken: refresh_token, }); const timeout = expires_in * 1000 - 60 * 1000; // you can configure as you want but here it is 1 min before token will get expired this.expiryTimer = setTimeout(() => { // this would reset localStorage before token expiry timr this.onRefreshToken(); }, timeout); }).catch(error => { console.log("ERROR", error) }); onRefreshToken = () => { const { dispatch } = this.props; const refresh_token = USER_TOKEN.get().refreshToken; dispatch(actions.refreshToken({ refresh_token })).then(userData => { const { access_token, refresh_token } = userData USER_TOKEN.set({ token: access_token, refreshToken: refresh_token, }); }); }; |
有任何问题请随时提出,另一种方法是实现 axios abort 控制器来取消挂起的Promise。也很乐意帮忙!
已编辑 - 您可以在所有 API 请求中维护 axios 令牌源以随时中止它们。
在所有 api 中维护 axios 令牌源。一旦您获得第一个Promise解决,您就可以取消所有其他待处理的 API 请求。您可以在第一个Promise得到解决后调用 onAbort 方法。看到这个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | //in your component class MyComponent extends Component{ isTokenSource = axios.CancelToken.source(); // a signal you can point to any API componentDidMount{ // for example if you're sending multiple api call here this.props.dispatch(actions.myRequest(payload, this.isTokenSource.token)) .then(() => { // all good }) .catch(error => { if (axios.isCancel(error)) { console.warn('Error', error); } }); } onAbortStuff = () => { // cancel request interceptor console.log("Aborting Request"); this.isTokenSource.cancel('API was cancelled'); // This will abort all the pending promises if you send the same token in multiple requests, } render(){ // } |
在您的 axios 请求中,您可以像这样发送令牌:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | export const myRequest= (id, cancelToken) => { const URL = `foo`; return axios(URL, { method: 'GET', headers: DEFAULT_HEADER.get(), cancelToken: cancelToken }) .then(response => { // handle success return response.data; }) .catch(error => { throw error; }); }; |
您可以参考这篇文章,它对了解取消订阅非常有帮助。 https://medium.freecodecamp.org/how-to-work-with-react-the-right-way-to-avoid-some-common-pitfalls-fc9eb5e34d9e
您可以通过以下方式构建路线:
index.js
1 2 3 4 5 | <Provider store={store}> <BrowserRouter> <App /> </BrowserRouter> </Provider> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class App extends Component { state = { isAuthenticated: false, }; componentDidMount() { //authentication API and later you can setState isAuthenticate } render() { const { isAuthenticated } = this.state; return isAuthenticated ? <Routes /> : <Loading />; } |
如果您仍然发现任何问题,我非常乐意为您提供帮助。