代码之家  ›  专栏  ›  技术社区  ›  ccoeder

redux刷新令牌中间件

  •  6
  • ccoeder  · 技术社区  · 7 年前

    我有一个中间件,可以在下一个操作运行之前转到刷新令牌,然后在访问令牌过期时运行另一个操作。

    但是,如果我一次发出多个请求,并且访问令牌已结束,那么我将尝试获取与请求相同数量的刷新令牌。我正在检查状态中的isLoading属性以防止出现这种情况。但是在请求之后,isLoading值在reducer中为true,在中间件中似乎为false,因此它一次又一次地请求。

    我正在fetching\u refresh\u令牌操作中发送refreshTokenPromise,但我从未获得状态。但是,它总是未定义的。

    我肯定对国家有意见。

    我的问题是,如何访问中间件中不断变化的状态值?

    刷新令牌中间件:(此版本多次命中端点)

    import { AsyncStorage } from 'react-native';
    import { MIN_TOKEN_LIFESPAN } from 'react-native-dotenv';
    import moment from 'moment';
    import Api from '../lib/api';
    import {
      FETCHING_REFRESH_TOKEN,
      FETCHING_REFRESH_TOKEN_SUCCESS,
      FETCHING_REFRESH_TOKEN_FAILURE } from '../actions/constants';
    
    export default function tokenMiddleware({ dispatch, getState }) {
      return next => async (action) => {
        if (typeof action === 'function') {
          const state = getState();
          if (state) {
            const expiresIn = await AsyncStorage.getItem('EXPIRES_IN');
            if (expiresIn && isExpired(JSON.parse(expiresIn))) {
              if (!state.refreshToken.isLoading) {
                return refreshToken(dispatch).then(() => next(action));
              }
              return state.refreshTokenPromise.then(() => next(action));
            }
          }
        }
        return next(action);
      };
    }
    
    async function refreshToken(dispatch) {
      const clientId = await AsyncStorage.getItem('CLIENT_ID');
      const clientSecret = await AsyncStorage.getItem('CLIENT_SECRET');
      const refreshToken1 = await AsyncStorage.getItem('REFRESH_TOKEN');
    
      const userObject = {
        grant_type: 'refresh_token',
        client_id: JSON.parse(clientId),
        client_secret: JSON.parse(clientSecret),
        refresh_token: refreshToken1,
      };
    
      const userParams = Object.keys(userObject).map(key => encodeURIComponent(key) + '=' + encodeURIComponent(userObject[key])).join('&');
    
      const refreshTokenPromise = Api.post('/token', userParams).then(async (res) => {
        await AsyncStorage.setItem('ACCESS_TOKEN', res.access_token);
        await AsyncStorage.setItem('REFRESH_TOKEN', res.refresh_token);
        await AsyncStorage.setItem('EXPIRES_IN', JSON.stringify(res['.expires']));
    
        dispatch({
          type: FETCHING_REFRESH_TOKEN_SUCCESS,
          data: res,
        });
    
        return res ? Promise.resolve(res) : Promise.reject({
          message: 'could not refresh token',
        });
      }).catch((err) => {
        dispatch({
          type: FETCHING_REFRESH_TOKEN_FAILURE,
        });
    
        throw err;
      });
    
      dispatch({
        type: FETCHING_REFRESH_TOKEN,
        refreshTokenPromise,
      });
    
      return refreshTokenPromise;
    }
    
    function isExpired(expiresIn) {
      return moment(expiresIn).diff(moment(), 'seconds') < MIN_TOKEN_LIFESPAN;
    }
    

    刷新令牌缩减器:

    import {
      FETCHING_REFRESH_TOKEN,
      FETCHING_REFRESH_TOKEN_SUCCESS,
      FETCHING_REFRESH_TOKEN_FAILURE } from '../actions/constants';
    
    const initialState = {
      token: [],
      isLoading: false,
      error: false,
    };
    
    export default function refreshTokenReducer(state = initialState, action) {
      switch (action.type) {
        case FETCHING_REFRESH_TOKEN:
          return {
            ...state,
            token: [],
            isLoading: true,
          };
        case FETCHING_REFRESH_TOKEN_SUCCESS:
          return {
            ...state,
            isLoading: false,
            token: action.data,
          };
        case FETCHING_REFRESH_TOKEN_FAILURE:
          return {
            ...state,
            isLoading: false,
            error: true,
          };
        default:
          return state;
      }
    }
    

    同时,当我将其发送到getState to refreshToken函数时,我得到了refreshToken中不断变化的状态值。但是在这个版本中,刷新令牌会转到其他操作,而不会被刷新。

    Monkey补丁版本:(此版本仅提出1个请求)

    import { AsyncStorage } from 'react-native';
    import { MIN_TOKEN_LIFESPAN } from 'react-native-dotenv';
    import moment from 'moment';
    import Api from '../lib/api';
    import {
      FETCHING_REFRESH_TOKEN,
      FETCHING_REFRESH_TOKEN_SUCCESS,
      FETCHING_REFRESH_TOKEN_FAILURE } from '../actions/constants';
    
    export default function tokenMiddleware({ dispatch, getState }) {
      return next => async (action) => {
        if (typeof action === 'function') {
          const state = getState();
          if (state) {
            const expiresIn = await AsyncStorage.getItem('EXPIRES_IN');
            if (expiresIn && isExpired(JSON.parse(expiresIn))) {
              if (!state.refreshTokenPromise) {
                return refreshToken(dispatch, getState).then(() => next(action));
              }
              return state.refreshTokenPromise.then(() => next(action));
            }
          }
        }
        return next(action);
      };
    }
    
    async function refreshToken(dispatch, getState) {
      const clientId = await AsyncStorage.getItem('CLIENT_ID');
      const clientSecret = await AsyncStorage.getItem('CLIENT_SECRET');
      const refreshToken1 = await AsyncStorage.getItem('REFRESH_TOKEN');
    
      const userObject = {
        grant_type: 'refresh_token',
        client_id: JSON.parse(clientId),
        client_secret: JSON.parse(clientSecret),
        refresh_token: refreshToken1,
      };
    
      if (!getState().refreshToken.isLoading) {
        const userParams = Object.keys(userObject).map(key => encodeURIComponent(key) + '=' + encodeURIComponent(userObject[key])).join('&');
    
        const refreshTokenPromise = Api.post('/token', userParams).then(async (res) => {
          await AsyncStorage.setItem('ACCESS_TOKEN', res.access_token);
          await AsyncStorage.setItem('REFRESH_TOKEN', res.refresh_token);
          await AsyncStorage.setItem('EXPIRES_IN', JSON.stringify(res['.expires']));
    
          dispatch({
            type: FETCHING_REFRESH_TOKEN_SUCCESS,
            data: res,
          });
    
          return res ? Promise.resolve(res) : Promise.reject({
            message: 'could not refresh token',
          });
        }).catch((err) => {
          dispatch({
            type: FETCHING_REFRESH_TOKEN_FAILURE,
          });
    
          throw err;
        });
    
        dispatch({
          type: FETCHING_REFRESH_TOKEN,
          refreshTokenPromise,
        });
    
        return refreshTokenPromise;
      }
    }
    
    function isExpired(expiresIn) {
      return moment(expiresIn).diff(moment(), 'seconds') < MIN_TOKEN_LIFESPAN;
    }
    

    2 回复  |  直到 7 年前
        1
  •  5
  •   Wildcard27    4 年前

    我使用axios中间件解决了这个问题。我觉得很不错。

    import { AsyncStorage } from 'react-native';
    import Config from 'react-native-config';
    import axios from 'axios';
    import { store } from '../store';
    import { refreshToken } from '../actions/refreshToken'; // eslint-disable-line
    
    const instance = axios.create({
      baseURL: Config.API_URL,
    });
    
    let authTokenRequest;
    
    function resetAuthTokenRequest() {
      authTokenRequest = null;
    }
    
    async function getAuthToken() {
      const clientRefreshToken = await AsyncStorage.getItem('clientRefreshToken');
    
      if (!authTokenRequest) {
        authTokenRequest = store.dispatch(refreshToken(clientRefreshToken));
    
        authTokenRequest.then(
          () => {
            const {
              token: { payload },
            } = store.getState();
    
            // save payload to async storage
          },
          () => {
            resetAuthTokenRequest();
          },
        );
      }
    
      return authTokenRequest;
    }
    
    instance.interceptors.response.use(
      response => response,
      async (error) => {
        const originalRequest = error.config;
    
        if (
          error.response.status === 401
          && !originalRequest._retry // eslint-disable-line no-underscore-dangle
        ) {
          return getAuthToken()
            .then(() => {
              const {
                token: {
                  payload: { 'access-token': accessToken, client, uid },
                },
              } = store.getState();
    
              originalRequest.headers['access-token'] = accessToken;
              originalRequest.headers.client = client;
              originalRequest.headers.uid = uid;
              originalRequest._retry = true; // eslint-disable-line no-underscore-dangle
    
              return axios(originalRequest);
            })
            .catch(err => Promise.reject(err));
        }
    
        return Promise.reject(error);
      },
    );
    
    export default instance;
    
    

    如果你有问题,请毫不犹豫地问。

        2
  •  1
  •   Lukas Liesis    7 年前

    你可以从redux传奇中受益

    https://github.com/redux-saga/redux-saga

    https://redux-saga.js.org/docs/api/#takelatestpattern-saga-args

    而redux thunk只是另一种在移动中创建操作并等待一些I/O发生,然后在I/O完成时创建更多操作的方法。它更像是同步代码模式,redux传奇更像是多线程的。在主线程中,你有你的应用程序运行,在后台线程中,你有sagas监视器和反应

    推荐文章