react app中令牌过期时如何注销用户
Posted
技术标签:
【中文标题】react app中令牌过期时如何注销用户【英文标题】:How to logout user when token expires in react app 【发布时间】:2020-09-11 19:05:53 【问题描述】:我正在开发一个应用程序,我使用 React 作为我的前端,React-apollo-graphql
用于我的 API 调用。
我正在使用react-hooks
,即在 React 16.8 + 中。
我在做什么
我已经创建了一个 auth.js
文件,当用户登录时我在其中存储我的值并检查令牌是否有效(我正在检查到期),但该文件仅加载我正在刷新或重新加载页面,这不是应该的工作方式
我的 auth.js 文件
const initialstate =
user: null,
;
if (localStorage.getItem("JWT_Token"))
const jwt_Token_decoded = Jwt_Decode(localStorage.getItem("JWT_Token"));
console.log(jwt_Token_decoded.exp * 1000);
console.log(Date.now());
if (jwt_Token_decoded.exp * 1000 < Date.now())
localStorage.clear(); // this runs only when I refresh the page or reload on route change it dosent work
else
initialstate.user = jwt_Token_decoded;
const AuthContext = createContext(
user: null,
login: (userData) => ,
logout: () => ,
);
const AuthReducer = (state, action) =>
switch (action.type)
case "LOGIN":
return
...state,
user: action.payload,
;
case "LOGOUT":
return
...state,
user: null,
;
default:
return state;
;
const AuthProvider = (props) =>
const [state, dispatch] = useReducer(AuthReducer, initialstate);
const login = (userData) =>
localStorage.setItem("JWT_Token", userData.token);
dispatch(
type: "LOGIN",
payload: userData,
);
;
const logout = () =>
localStorage.clear();
dispatch( action: "LOGOUT" );
;
return (
<AuthContext.Provider
value= user: state.user, login, logout
...props
/>
);
;
export AuthContext, AuthProvider ;
正如我已经评论了我正在检查令牌到期的行。
我唯一的问题是为什么它在页面重新加载上工作,而不是像我们在使用 Redux 时在存储文件中所做的那样在每个路由上工作。
我的 App.js
<AuthProvider>
<Router>
<div className="App wrapper">
<Routes/>
</div>
</Router>
</AuthProvider>
我的 index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import ApolloClient from 'apollo-boost'
import ApolloProvider from '@apollo/react-hooks';
import InMemoryCache from 'apollo-cache-inmemory';
const client = new ApolloClient(
uri: 'my url',
cache: new InMemoryCache(),
);
ReactDOM.render(
<ApolloProvider client=client>
<App />
</ApolloProvider>,
document.getElementById('root')
);
要点
当我使用react-apollo-graphql
时,他们是否提供 ant 身份验证流程?就像 redux 一样,我们必须创建一个存储文件来存储我们的数据
我使用的是 React 16.8 +,所以我使用的是 react-hooks,所以这里我只使用 use Reducer
。
我唯一的问题是我做得对吗?我对其他方法持开放态度。
我已经使用 Vuex 在 Vue 中完成了身份验证和授权,我用它来创建一个在任何路线上运行的商店文件
与 Redux 一样,在我的存储文件中我用来存储状态和所有内容。
现在,如果我使用 react-hooks 和 react-apollo-graphql,那么就不需要使用 redux 来做这些事情了。
我正在使用apollo-link-context
传递如下所示的标头(授权)
const authLink = setContext(() =>
const token = localStorage.getItem('JWT_Token')
return
headers:
Authorization: token ? `$token` : ''
);
我认为在这里我可以检查每个路由或每个请求是否令牌有效(检查 exp 时间)如果它无效然后我将注销并清除我的本地存储,清除存储不是什么大问题主要是如何重定向到登录页面。
【问题讨论】:
【参考方案1】:您面临的问题很简单。您的 AuthReducer 在创建时只接受一次 initialState。现在,当您重新加载您的应用程序时,一切都会再次初始化,并且到期由您的逻辑处理。但是在路线更改时,它不会重新评估您的初始状态。
但是,您可以在使用 setContext
时通过使用 jwtDecode
解码令牌来检查是否过期,如果令牌过期则刷新令牌并保存在 localStorage 中,因为这是在每次请求时执行的
const authLink = setContext(async () =>
let token = localStorage.getItem('JWT_Token')
const exp = jwtDecode(token)
// Refresh the token a minute early to avoid latency issues
const expirationTime = (exp * 1000) - 60000
if (Date.now() >= expirationTime)
token = await refreshToken()
// set LocalStorage here based on response;
return
// you can set your headers directly here based on the new token/old token
headers:
...
)
但是,由于您希望在令牌过期时重定向到登录页面而不刷新令牌,您可以使用带有路由的自定义历史对象
src/history.js
import createBrowserHistory from 'history';
const history = createBrowserHistory()
export default history;
App.js
import history from '/path/to/history.js';
import Router from 'react-router-dom';
<AuthProvider>
<Router history=history>
<div className="App wrapper">
<Routes/>
</div>
</Router>
</AuthProvider>
然后在 setContext 你可以做
import history from '/path/to/history';
const authLink = setContext(async () =>
let token = localStorage.getItem('JWT_Token')
const exp = jwtDecode(token)
const expirationTime = (exp * 1000) - 60000
if (Date.now() >= expirationTime)
localStorage.clear();
history.push('/login');
return
// you can set your headers directly here based on the old token
headers:
...
)
【讨论】:
我没有得到这个 refreshToken() 我没有创建这个函数,我已经尝试过这种方法,比如通过检查jwtDecode
导出令牌时我想注销,即重定向到登录页面并清除令牌,但这里我不知道如何使用重定向或路由。
好的,更新我的答案
嘿 '/path/to/history'
我应该放什么路径?
history.js 文件的相对路径
我还没有创建历史文件,我正在为此使用 react useHistory Hook。【参考方案2】:
对于您的问题,解决方案可能如下:
从上下文中删除身份验证部分。 (不好的做法) 创建一个订阅了react-router
的组件来检查用户的身份验证状态。
在main
组件中渲染它。
authverify.component.js
import withRouter from "react-router-dom";
const AuthVerifyComponent = ( history ) =>
history.listen(() => // <--- Here you subscribe to the route change
if (localStorage.getItem("JWT_Token"))
const jwt_Token_decoded = Jwt_Decode(localStorage.getItem("JWT_Token"));
console.log(jwt_Token_decoded.exp * 1000);
console.log(Date.now());
if (jwt_Token_decoded.exp * 1000 < Date.now())
localStorage.clear();
else
initialstate.user = jwt_Token_decoded;
);
return <div></div>;
;
export default withRouter(AuthVerifyComponent);
app.js
<AuthProvider>
<Router>
<div className="App wrapper">
<Routes />
<AuthVerifyComponent />
</div>
</Router>
</AuthProvider>;
【讨论】:
我已经有一个单独的路由文件,其中写入了所有路由,并且会在每次路由检查时运行,我的意思是在每次路由渲染时运行? 是的,你可以试试。我以前做过,但现在没试过。 嘿,基本上你是说删除 auth.js 文件并将我所有的代码放在你创建的新组件中AuthVerifyComponent
?
不,只需将验证部分移至AuthVerifyComponent
,以便您可以使用withRouter
钩子,其余部分保持不变。
所以它说初始状态未定义,我也必须通过初始状态,如何?【参考方案3】:
从 Yogesh Aggarwal 的回答中得到启发,我更喜欢这样做:
import React from 'react';
import useLocation, useHistory from 'react-router-dom';
const AuthProvider = () =>
const pathName = useLocation().pathname;
const history = useHistory();
if (pathName === your_path_that_need_authentication)
// if token expired then history.push(login_page));
return null;
;
export default AuthProvider;
然后把这个 AuthProvider 放到项目中。
从历史中提取路径名是一个很好的选择,但不是最好的选择。问题是如果您键入或复制/粘贴所需的 URL,它不再起作用,但使用 useLocation 挂钩将解决此问题
【讨论】:
以上是关于react app中令牌过期时如何注销用户的主要内容,如果未能解决你的问题,请参考以下文章