我使用以下中间件在令牌过期时刷新令牌:
import {AsyncStorage} from 'react-native';
import moment from 'moment';
import fetch from "../components/Fetch";
import jwt_decode from 'jwt-decode';
/**
* This middleware is meant to be the refresher of the authentication token, on each request to the API,
* it will first call refresh token endpoint
* @returns {function(*=): Function}
* @param store
*/
const tokenMiddleware = store => next => async action => {
if (typeof action === 'object' && action.type !== "FETCHING_TEMPLATES_FAILED") {
let eToken = await AsyncStorage.getItem('eToken');
if (isExpired(eToken)) {
let rToken = await AsyncStorage.getItem('rToken');
let formData = new FormData();
formData.append("refresh_token", rToken);
await fetch('/token/refresh',
{
method: 'POST',
body: formData
})
.then(response => response.json())
.then(async (data) => {
let decoded = jwt_decode(data.token);
console.log({"refreshed": data.token});
return await Promise.all([
await AsyncStorage.setItem('token', data.token).then(() => {return AsyncStorage.getItem('token')}),
await AsyncStorage.setItem('rToken', data.refresh_token).then(() => {return AsyncStorage.getItem('rToken')}),
await AsyncStorage.setItem('eToken', decoded.exp.toString()).then(() => {return AsyncStorage.getItem('eToken')}),
]).then((values) => {
return next(action);
});
}).catch((err) => {
console.log(err);
});
return next(action);
} else {
return next(action);
}
}
function isExpired(expiresIn) {
// We refresh the token 3.5 hours before it expires(12600 seconds) (lifetime on server 25200seconds)
return moment.unix(expiresIn).diff(moment(), 'seconds') < 10;
}
};
export default tokenMiddleware;
还有获取帮助器:
import { AsyncStorage } from 'react-native';
import GLOBALS from '../constants/Globals';
import {toast} from "./Toast";
import I18n from "../i18n/i18n";
const jsonLdMimeType = 'application/ld+json';
export default async function (url, options = {}, noApi = false) {
if ('undefined' === typeof options.headers) options.headers = new Headers();
if (null === options.headers.get('Accept')) options.headers.set('Accept', jsonLdMimeType);
if ('undefined' !== options.body && !(options.body instanceof FormData) && null === options.headers.get('Content-Type')) {
options.headers.set('Content-Type', jsonLdMimeType);
}
let token = await AsyncStorage.getItem('token');
console.log({"url": url,"new fetch": token});
if (token) {
options.headers.set('Authorization', 'Bearer ' + token);
}
let api = '/api';
if (noApi) {
api = "";
}
const link = GLOBALS.BASE_URL + api + url;
return fetch(link, options).then(response => {
if (response.ok) return response;
return response
.json()
.then(json => {
if (json.code === 401) {
toast(I18n.t(json.message), "danger", 3000);
AsyncStorage.setItem('token', '');
}
const error = json['message'] ? json['message'] : response.statusText;
throw Error(I18n.t(error));
})
.catch(err => {
throw err;
});
})
.catch(err => {
throw err;
});
}
我的问题是:
next(action)
方法。/templates
端点是在我的/token/refresh
端点之前(而不是之后)使用旧的过期令牌调用的。 编辑:为解决此问题,我重新编写了代码以将其放入一个文件中。 我还放置了一些console.log来显示如何执行此代码
从图像中我们可以看到:
对此有任何帮助吗?
编辑直到赏金结束:
从这个问题中,我试图理解为什么我对中间件的方法是错误的,因为我在互联网上发现的许多资源都将中间件视为实现刷新令牌操作的最佳解决方案。
答案 0 :(得分:3)
我的处理设置略有不同。我将其定义为辅助函数,而不是在中间件中处理刷新令牌逻辑。这样,我可以在我认为合适的任何网络请求之前立即执行所有令牌验证,并且任何不涉及网络请求的redux动作都不需要此功能
export const refreshToken = async () => {
let valid = true;
if (!validateAccessToken()) {
try {
//logic to refresh token
valid = true;
} catch (err) {
valid = false;
}
return valid;
}
return valid;
};
const validateAccessToken = () => {
const currentTime = new Date();
if (
moment(currentTime).add(10, 'm') <
moment(jwtDecode(token).exp * 1000)
) {
return true;
}
return false;
};
现在我们有了这个辅助函数,我将其用于所有需要的redux动作
const shouldRefreshToken = await refreshToken();
if (!shouldRefreshToken) {
dispatch({
type: OPERATION_FAILED,
payload: apiErrorGenerator({ err: { response: { status: 401 } } })
});
} else {
//...
}
答案 1 :(得分:2)
在中间件中,您正在使store.dispatch
异步,但是store.dispatch
的原始签名是同步的。这可能会产生严重的副作用。
让我们考虑一个简单的中间件,该中间件记录应用程序中发生的所有操作以及操作后的状态:
const logger = store => next => action => {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
编写上述中间件实际上是在做以下事情:
const next = store.dispatch // you take current version of store.dispatch
store.dispatch = function dispatchAndLog(action) { // you change it to meet your needs
console.log('dispatching', action)
let result = next(action) // and you return whatever the current version is supposed to return
console.log('next state', store.getState())
return result
}
将此示例与3个这样的中间件链接在一起:
const {
createStore,
applyMiddleware,
combineReducers,
compose
} = window.Redux;
const counterReducer = (state = 0, action) => {
switch (action.type) {
case "INCREMENT":
return state + 1;
default:
return state;
}
};
const rootReducer = combineReducers({
counter: counterReducer
});
const logger = store => next => action => {
console.log("dispatching", action);
let result = next(action);
console.log("next state", store.getState());
return result;
};
const logger2 = store => next => action => {
console.log("dispatching 2", action);
let result = next(action);
console.log("next state 2", store.getState());
return result;
};
const logger3 = store => next => action => {
console.log("dispatching 3", action);
let result = next(action);
console.log("next state 3", store.getState());
return result;
};
const middlewareEnhancer = applyMiddleware(logger, logger2, logger3);
const store = createStore(rootReducer, middlewareEnhancer);
store.dispatch({
type: "INCREMENT"
});
console.log('current state', store.getState());
<script src="https://unpkg.com/redux@4.0.1/dist/redux.js"></script>
首先logger
得到动作,然后logger2
,然后logger3
,然后到达实际的store.dispatch
,然后调用reducer。减速器将状态从0更改为1,logger3
获得更新后的状态,并将返回值(操作)传播回logger2
,然后传播到logger
。
现在,让我们考虑一下,当您将store.dispatch
更改为链中间某个位置的异步函数时会发生什么:
const logger2 = store => next => async action => {
function wait(ms) {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, ms);
});
}
await wait(5000).then(v => {
console.log("dispatching 2", action);
let result = next(action);
console.log("next state 2", store.getState());
return result;
});
};
我修改了logger2
,但是logger
(上一个)不知道next
现在是异步的。它将返回挂起的Promise
并返回“未更新”状态,因为调度的动作尚未到达减速器。
const {
createStore,
applyMiddleware,
combineReducers,
compose
} = window.Redux;
const counterReducer = (state = 0, action) => {
switch (action.type) {
case "INCREMENT":
return state + 1;
default:
return state;
}
};
const rootReducer = combineReducers({
counter: counterReducer
});
const logger = store => next => action => {
console.log("dispatching", action);
let result = next(action); // will return a pending Promise
console.log("next state", store.getState());
return result;
};
const logger2 = store => next => async action => {
function wait(ms) {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, ms);
});
}
await wait(2000).then(() => {
console.log("dispatching 2", action);
let result = next(action);
console.log("next state 2", store.getState());
return result;
});
};
const logger3 = store => next => action => {
console.log("dispatching 3", action);
let result = next(action);
console.log("next state 3", store.getState());
return result;
};
const middlewareEnhancer = applyMiddleware(logger, logger2, logger3);
const store = createStore(rootReducer, middlewareEnhancer);
store.dispatch({ // console.log of it's return value is too a pending `Promise`
type: "INCREMENT"
});
console.log('current state', store.getState());
<script src="https://unpkg.com/redux@4.0.1/dist/redux.js"></script>
因此我的store.dispatch
从中间件链中立即返回,并带有待处理的Promise,并且console.log('current state', store.getState());
仍显示0。操作到达原始store.dispatch
和减速器紧接着。
我不知道您的整个设置,但是我的猜测是您的情况正在发生。您假设您的中间件已经完成了一些工作并进行了往返,但是实际上它还没有完成工作(或者没有人await
让他完成工作)。可能是您正在分派一个操作来提取/templates
,并且由于您编写了一种自动更新载体令牌的中间件,所以您假设将使用全新的令牌调用fetch helper实用程序。但是dispatch
早早带着未完成的承诺返回了,您的令牌仍然是旧的。
除此之外,显然只有一件事似乎是错误的:您正在通过next
在中间件中调度两次相同的操作:
const tokenMiddleware = store => next => async action => {
if (something) {
if (something) {
await fetch('/token/refresh',)
.then(async (data) => {
return await Promise.all([
// ...
]).then((values) => {
return next(action); // First, after the `Promise.all` resolves
});
});
return next(action); // then again after the `fetch` resolves, this one seems redundant & should be removed
} else {
return next(action);
}
}
建议:
redux thunk的示例:
function apiCallMaker(dispatch, url, actions) {
dispatch({
type: actions[0]
})
return fetch(url)
.then(
response => response.json(),
error => {
dispatch({
type: actions[2],
payload: error
})
}
)
.then(json =>
dispatch({
type: actions[1],
payload: json
})
)
}
}
export function createApiCallingActions(url, actions) {
return function(dispatch, getState) {
const { accessToken, refreshToken } = getState();
if(neededToRefresh) {
return fetch(url)
.then(
response => response.json(),
error => {
dispatch({
type: 'TOKEN_REFRESH_FAILURE',
payload: error
})
}
)
.then(json =>
dispatch({
type: 'TOKEN_REFRESH_SUCCESS',
payload: json
})
apiCallMaker(dispatch, url, actions)
)
} else {
return apiCallMaker(dispatch, url, actions)
}
}
您将这样使用它:
dispatch(createApiCallingActions('/api/foo', ['FOO FETCH', 'FOO SUCCESS', 'FOO FAILURE'])
dispatch(createApiCallingActions('/api/bar', ['BAR FETCH', 'BAR SUCCESS', 'BAR FAILURE'])
答案 2 :(得分:1)
您的请求处于竞争状态,没有正确的解决方案可以完全解决此问题。下一项可以用作解决此问题的起点:
强制令牌更新的频率要比超时更新的频率高-在超时的50%至75%的范围内更改令牌会减少失败请求的数量(但如果用户在整个会话时间内处于中间状态,这些请求仍将保留)。因此,任何有效的请求都将返回将使用的新的有效令牌,而不是旧的令牌。
可以执行的令牌扩展期,可以认为旧令牌在传输期间是有效的-为了避免问题而将旧令牌扩展了有限的时间(听起来不是很好,但是至少可以选择)< / p>