【问题标题】:How to Handle Refresh Token When Multiple Requests are going out?多个请求发出时如何处理刷新令牌?
【发布时间】:2019-01-25 16:10:42
【问题描述】:

我在使用 reactjs、mbox 和 axios 时遇到了问题。我有一个提供访问令牌和刷新令牌的 api。访问令牌每 20 分钟失效一次,发生这种情况时服务器会返回 401,我的代码会自动发送刷新令牌以获取新的访问令牌。

一旦授予新的访问令牌,将再次发送相同的拒绝请求。现在我的代码运行良好,直到我抛出多个几乎可以同时触发的拒绝。

所以第一个请求关闭,401 被发回并获得一个新的刷新令牌,所有其他请求都将尝试做同样的事情,但其他请求现在将失败,因为将使用刷新令牌并且将向第一个请求发出一个新请求。

这将启动我的代码以将用户重定向到登录页面。

所以基本上我一次只能有 1 个请求。

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

  <React.Fragment>
       <Switch>
          <Route path="/members" component={MemberAreaComponent} />
        </Switch>
  </React.Fragment >

在成员区域组件中

      <Route path="/members/home" component={MembersHomeComponent} />

当我输入http://www.mywebsite/members/home

MembersHomeComponent - componentDidMount runs first
MemberAreaComponent - componentDidMount runs second
AppCoontainer = componentDidMount runs last.

【问题讨论】:

    标签: reactjs authentication axios refresh-token mbox


    【解决方案1】:

    您好,我在 react/redux 应用中实现了相同的场景。但它会帮助你实现目标。您不需要在每个 API 调用中检查 401。只需在您的第一个验证 API 请求中实现它。您可以使用 setTimeOut 在身份验证令牌到期之前发送刷新令牌 api 请求。所以 locatStorage 将得到更新,所有 axios 请求都不会得到过期的令牌。 这是我的解决方案:

    在我的Constants.js 中,我在 localStorage 中维护 USER TOKEN,如下所示:

     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 请求如下:

    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 令牌源。一旦您解决了第一个承诺,您就可以取消所有其他待处理的 API 请求。你可以在你的第一个承诺得到解决后调用 onAbort 方法。看到这个:

    //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 请求中,您可以像这样发送令牌:

    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

    <Provider store={store}>
      <BrowserRouter>
        <App />
      </BrowserRouter>
    </Provider>
    

    App.js:

    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 />;
      }
    

    如果您仍然发现任何问题,我非常乐意为您提供帮助。

    【讨论】:

    • 嗯,很有趣,我以为您会使用间隔计时器,因为也许我没有看到它,但是您如何保持计时器运转?我想既然访问令牌只要过期就被接受,即使在发送旧令牌时创建了新令牌,它仍然可以通过吗?如果说刷新令牌无效并且您实际上应该强制它们离开您的站点,您的代码将如何处理?还在检查 401 吗?
    • 如果显示的内容不多,我想看看中止将如何工作,因为我在想如果您全部中止它们,您真的会重新发送请求以生成新的令牌吗?
    • 我想回答您的两个问题; - Timer => 在后端有一些实现,我得到该令牌的剩余到期时间以响应验证 API。甚至 App 也会重新加载,它会再次发送验证 API 请求并获得剩余的到期时间,因为我们正在后端管理到期时间。所以我在实际到期时间之前设置了 1 分钟的 SetTImeOut。它接受刷新令牌,我得到新的访问令牌和刷新令牌。然后我更新 locatStoarge 所以所有 API 在其标头中都使用最新的访问令牌。您无需在所有 API 中检查 401。
    • 我也选择了选项 1。我只是在说他们关闭浏览器然后通过复制的链接返回时遇到问题。所以我把我的代码放在我的 app.js 中检查他们是否有身份验证。我得到一个重定向到我的登录作为他们要在 app.js 安装之前运行的组件,所以一个 ajax 发生并且它返回一个 401。我需要一些在其他所有事情之前触发的事件。
    • 嗯,我正在查看我的代码。我有到我的管理部分的路由的 MembersArea,但是当我直接输入管理区域的 url 时,管理组件在 memberarea 组件之前运行
    猜你喜欢
    • 2016-03-31
    • 2020-06-30
    • 2017-06-09
    • 2016-02-23
    • 2023-02-08
    • 2016-01-05
    • 2019-10-13
    • 2017-04-21
    • 1970-01-01
    相关资源
    最近更新 更多