lan*_*nan 43 javascript jwt reactjs react-native redux
我们的React Native Redux应用程序使用JWT令牌进行身份验证.有许多动作需要这样的令牌,并且很多动作同时被派遣,例如当app加载时.
例如
componentDidMount() {
dispath(loadProfile());
dispatch(loadAssets());
...
}
Run Code Online (Sandbox Code Playgroud)
双方loadProfile并loadAssets要求JWT.我们将令牌保存在状态和AsyncStorage.我的问题是如何处理令牌过期.
最初我打算使用中间件来处理令牌过期
// jwt-middleware.js
export function refreshJWTToken({ dispatch, getState }) {
return (next) => (action) => {
if (isExpired(getState().auth.token)) {
return dispatch(refreshToken())
.then(() => next(action))
.catch(e => console.log('error refreshing token', e));
}
return next(action);
};
Run Code Online (Sandbox Code Playgroud)
}
我遇到的问题是令牌loadProfile和loadAssets动作都会刷新令牌,因为在派遣令牌时,令牌将过期.理想情况下,我想"暂停"需要身份验证的操作,直到刷新令牌.有没有办法用中间件做到这一点?
lan*_*nan 36
我找到了解决这个问题的方法.我不确定这是否是最佳实践方法,并且可能会对其进行一些改进.
我最初的想法仍然存在:JWT刷新是在中间件中.thunk如果thunk使用中间件必须先到来.
...
const createStoreWithMiddleware = applyMiddleware(jwt, thunk)(createStore);
Run Code Online (Sandbox Code Playgroud)
然后在中间件代码中,我们检查令牌是否在任何异步操作之前过期.如果它已过期,我们还会检查我们是否已经刷新了令牌 - 为了能够进行这样的检查,我们将新的令牌的承诺添加到州.
import { refreshToken } from '../actions/auth';
export function jwt({ dispatch, getState }) {
return (next) => (action) => {
// only worry about expiring token for async actions
if (typeof action === 'function') {
if (getState().auth && getState().auth.token) {
// decode jwt so that we know if and when it expires
var tokenExpiration = jwtDecode(getState().auth.token).<your field for expiration>;
if (tokenExpiration && (moment(tokenExpiration) - moment(Date.now()) < 5000)) {
// make sure we are not already refreshing the token
if (!getState().auth.freshTokenPromise) {
return refreshToken(dispatch).then(() => next(action));
} else {
return getState().auth.freshTokenPromise.then(() => next(action));
}
}
}
}
return next(action);
};
}
Run Code Online (Sandbox Code Playgroud)
最重要的部分是refreshToken功能.该函数需要在刷新令牌时调度操作,以便状态将包含新令牌的承诺.这样,如果我们同时发送多个使用令牌身份验证的异步操作,则令牌只会刷新一次.
export function refreshToken(dispatch) {
var freshTokenPromise = fetchJWTToken()
.then(t => {
dispatch({
type: DONE_REFRESHING_TOKEN
});
dispatch(saveAppToken(t.token));
return t.token ? Promise.resolve(t.token) : Promise.reject({
message: 'could not refresh token'
});
})
.catch(e => {
console.log('error refreshing token', e);
dispatch({
type: DONE_REFRESHING_TOKEN
});
return Promise.reject(e);
});
dispatch({
type: REFRESHING_TOKEN,
// we want to keep track of token promise in the state so that we don't try to refresh
// the token again while refreshing is in process
freshTokenPromise
});
return freshTokenPromise;
}
Run Code Online (Sandbox Code Playgroud)
我意识到这很复杂.我也有点担心调度refreshToken行动本身不是行动.请告诉我您使用redux处理JWT令牌过期的任何其他方法.
Zek*_*oid 18
您可以改为使用商店变量来知道您是否仍在获取令牌,而不是"等待"某个操作完成:
样品减速剂
const initialState = {
fetching: false,
};
export function reducer(state = initialState, action) {
switch(action.type) {
case 'LOAD_FETCHING':
return {
...state,
fetching: action.fetching,
}
}
}
Run Code Online (Sandbox Code Playgroud)
现在是动作创建者:
export function loadThings() {
return (dispatch, getState) => {
const { auth, isLoading } = getState();
if (!isExpired(auth.token)) {
dispatch({ type: 'LOAD_FETCHING', fetching: false })
dispatch(loadProfile());
dispatch(loadAssets());
} else {
dispatch({ type: 'LOAD_FETCHING', fetching: true })
dispatch(refreshToken());
}
};
}
Run Code Online (Sandbox Code Playgroud)
安装组件时会调用此方法.如果auth键是陈旧的,它将调度一个设置fetching为true的操作并刷新令牌.请注意,我们还不会加载配置文件或资产.
新组件:
componentDidMount() {
dispath(loadThings());
// ...
}
componentWillReceiveProps(newProps) {
const { fetching, token } = newProps; // bound from store
// assuming you have the current token stored somewhere
if (token === storedToken) {
return; // exit early
}
if (!fetching) {
loadThings()
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,现在您尝试在挂载时加载您的东西,但在接收道具时也会在某些条件下加载(这将在商店更改时调用,以便我们可以保留fetching)当初始提取失败时,它将触发refreshToken.完成后,它将在商店中设置新令牌,更新组件并因此调用componentWillReceiveProps.如果它仍然没有取出(不确定这个检查是否必要),它将加载东西.
我做了一个简单的包装redux-api-middleware来推迟操作并刷新访问令牌.
middleware.js
import { isRSAA, apiMiddleware } from 'redux-api-middleware';
import { TOKEN_RECEIVED, refreshAccessToken } from './actions/auth'
import { refreshToken, isAccessTokenExpired } from './reducers'
export function createApiMiddleware() {
const postponedRSAAs = []
return ({ dispatch, getState }) => {
const rsaaMiddleware = apiMiddleware({dispatch, getState})
return (next) => (action) => {
const nextCheckPostoned = (nextAction) => {
// Run postponed actions after token refresh
if (nextAction.type === TOKEN_RECEIVED) {
next(nextAction);
postponedRSAAs.forEach((postponed) => {
rsaaMiddleware(next)(postponed)
})
} else {
next(nextAction)
}
}
if(isRSAA(action)) {
const state = getState(),
token = refreshToken(state)
if(token && isAccessTokenExpired(state)) {
postponedRSAAs.push(action)
if(postponedRSAAs.length === 1) {
return rsaaMiddleware(nextCheckPostoned)(refreshAccessToken(token))
} else {
return
}
}
return rsaaMiddleware(next)(action);
}
return next(action);
}
}
}
export default createApiMiddleware();
Run Code Online (Sandbox Code Playgroud)
我在状态中保留令牌,并使用简单的帮助程序将Acess令牌注入请求标头
export function withAuth(headers={}) {
return (state) => ({
...headers,
'Authorization': `Bearer ${accessToken(state)}`
})
}
Run Code Online (Sandbox Code Playgroud)
所以redux-api-middleware行动几乎保持不变
export const echo = (message) => ({
[RSAA]: {
endpoint: '/api/echo/',
method: 'POST',
body: JSON.stringify({message: message}),
headers: withAuth({ 'Content-Type': 'application/json' }),
types: [
ECHO_REQUEST, ECHO_SUCCESS, ECHO_FAILURE
]
}
})
Run Code Online (Sandbox Code Playgroud)
我写了这篇文章并分享了项目示例,该示例显示了JWT刷新令牌工作流程的实际应用
| 归档时间: |
|
| 查看次数: |
20771 次 |
| 最近记录: |