我正在使用reactjs,mbox和axios,遇到了一个问题。我有一个 api 提供访问令牌和刷新令牌。访问令牌每 20 分钟死亡一次,发生这种情况时,服务器会发回 401,我的代码将自动发送刷新令牌以获取新的访问令牌。
授予新的访问令牌后,将再次发送相同的拒绝请求。现在我的代码运行良好,直到我抛出多个几乎可以同时触发的拒绝。
因此,第一个请求关闭,401 被发回并获取一个新的刷新令牌,所有其他请求将尝试执行相同的操作,但其他请求现在将失败,因为将使用刷新令牌并将向第一个请求发出一个新的令牌。
这将启动我的代码,将用户重定向到登录页面。
所以基本上我一次只能有一个请求。
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 中复制时,我需要检查以重置身份验证的代码不起作用,我遇到了问题
应用.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.
嗨,我已经在反应/redux应用程序中实现了相同的场景。但它会帮助你实现目标。您无需在每个 API 调用中检查 401。只需在第一个验证 API 请求中实现它即可。可以使用 setTimeOut 在身份验证令牌到期的某个时间之前发送刷新令牌 api 请求。因此,locatStorage 将得到更新,并且所有 axios 请求都不会获得过期的令牌。 这是我的解决方案:
在我的Constants.js
中,我正在本地存储中维护用户令牌,如下所示:
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}`,
}),
};
在页面加载时,用户验证 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 中止控制器来取消挂起的承诺。也很乐意帮忙!
已编辑- 您可以在所有 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
您可以通过以下方式构建路线: 索引.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 />;
}
如果您仍然发现任何问题,我非常乐意为您提供帮助。