在 React Router 上,如何保持登录状态甚至页面刷新?
On React Router, how to stay logged in state even page refresh?
我正在用 React、React Router 和 Redux 制作一个网站。许多路由(页面)要求用户登录。如果用户未登录,我可以重定向到登录页面:
function requireAuth(nextState, replace) {
let loggedIn = store.getState().AppReducer.UserReducer.loggedIn;
if(!loggedIn) {
replace({
pathname: '/login',
state: {
nextpathname: nextState.location.pathname
}
});
}
}
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<Route path="/" component={App}>
<IndexRoute component={Index} />
<Route path="login" component={Login} />
<Route path="register" component={Register} />
<Route path="dashboard" component={Graph} onEnter={requireAuth}>
... some other route requires logged in ...
</Route>
</Route>
</Router>
</Provider>,
document.getElementById('entry')
);
请查看代码,如果用户未登录,我使用 onEnter 挂钩重定向到 '/login' 路由。用于检查用户是否登录的数据在商店中,它将在之后更新用户登录。
它工作得很好,但问题是当我刷新页面时,商店被重置并且用户没有登录状态。
我知道发生这种情况是因为 Redux 存储只是内存存储,所以刷新页面会丢失存储中的所有数据。
在每次刷新时检查服务器会话可能有效,但这可能是请求太多,所以这似乎是个坏主意。
将登录状态数据保存到 localStorage 可能有效,但在这种情况下,我应该检查每个 AJAX 调用是否失败,因为会话已过期或不存在,请求被拒绝,这看起来像是也是个坏主意。
有没有更简单的方法解决这个问题?我的网站需要处理大量用户,所以我想尽可能减少 XHR 调用。
任何建议将不胜感激。
为什么不使用带有登录状态和到期日期的 sessionStorage?您将不得不编写更多代码来检查 sessionStorage 状态,但我认为这是避免发送 XHR 调用的唯一方法。
另一种方法是使用 JSON Web Tokens (JWT) that are required for each route, and localStorage 检查 JWT。
TL;DR
在前端,您有一个登录和注册路由来查询您的
根据服务器上的身份验证为 JWT 服务器。一次
通过了适当的 JWT,然后您可以将 属性 状态设置为
真的。您可以有一个注销路由,允许用户设置它
状态为假。
包含你的路由的index.js可以查看本地存储
在渲染之前,从而消除了丢失状态的问题
刷新但保持一定的安全性。
您的应用程序中需要身份验证的所有路由都已呈现
通过组合组件,并根据需要进行保护
在 header 中具有 JWT 以在服务器 API.
上进行授权
设置它需要一些时间,但它会使您的应用程序 'reasonably' 安全。
解决您的问题:
检查 index.js
文件中路由之前的本地存储,如下所示,如果需要,将状态更新为已验证。
应用程序维护安全,因为 API 由 JWT 保护,这将解决您的刷新问题,并维护对您的服务器和数据的安全 link。
因此在路线中你会有这样的东西:
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware, compose } from 'redux';
import { Router, Route, browserHistory, IndexRoute } from 'react-router';
import reduxThunk from 'redux-thunk';
import { AUTHENTICATE_THE_USER } from './actions/types';
import RequireAuth from './components/auth/require_auth';
import reducers from './reducers';
/* ...import necessary components */
const createStoreWithMiddleware = compose(applyMiddleware(reduxThunk))(createStore);
const store = createStoreWithMiddleware(reducers);
/* ... */
// Check for token and update application state if required
const token = localStorage.getItem('token');
if (token) {
store.dispatch({ type: AUTHENTICATE_THE_USER });
}
/* ... */
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<Route path="/" component={App}>
<IndexRoute component={Index} />
<Route path="login" component={Login} />
<Route path="register" component={Register} />
<Route path="dashboard" component={RequireAuth(Graph)} />
<Route path="isauthenticated" component={RequireAuth(IsAuthenticated)} />
... some other route requires logged in ...
</Route>
</Router>
</Provider>
, document.getElementById('entry'));
RequiredAuth
是组合组件,而 Graph
和 IsAuthenticated
(可以是任意数量的适当命名的组件)要求 state.authenticated
为真。
组件,在本例中 Graph
和 IsAuthenticated
在 state.authenticated
为真时呈现。否则默认返回根路由。
然后你可以像这样构建一个组合组件,通过它呈现你所有的路由。它将在呈现之前检查您所持有的用户是否已通过身份验证(布尔值)的状态是否为真。
require_auth.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
export default function (ComposedComponent) {
// If user not authenticated render out to root
class Authentication extends Component {
static contextTypes = {
router: React.PropTypes.object
};
componentWillMount() {
if (!this.props.authenticated) {
this.context.router.push('/');
}
}
componentWillUpdate(nextProps) {
if (!nextProps.authenticated) {
this.context.router.push('/');
}
}
render() {
return <ComposedComponent {...this.props} />;
}
}
function mapStateToProps(state) {
return { authenticated: state.authenticated };
}
return connect(mapStateToProps)(Authentication);
}
在 signup/signin 方面,您可以创建一个操作来存储 JWT 并设置状态以通过 action-creator -> redux 存储进行身份验证。此示例 makes use of axios 到 运行 异步 HTTP 请求响应周期。
export function signinUser({ email, password }) {
// Note using the npm package 'redux-thunk'
// giving direct access to the dispatch method
return function (dispatch) {
// Submit email and password to server
axios.post(`${API_URL}/signin`, { email, password })
.then(response => {
// If request is good update state - user is authenticated
dispatch({ type: AUTHENTICATE_THE_USER });
// - Save the JWT in localStorage
localStorage.setItem('token', response.data.token);
// - redirect to the route '/isauthenticated'
browserHistory.push('/isauthenticated');
})
.catch(() => {
// If request is bad show an error to the user
dispatch(authenticationError('Incorrect email or password!'));
});
};
}
当然,您还需要设置商店(在本例中为 Redux)和 action creator。
'real' 安全来自后端。为此,您使用 localStorage 将 JWT 保留在前端,并将其在 header 中传递给任何具有 sensitive/protected 信息的 API 调用。
为服务器上的用户创建和解析 JWT API 是另一个步骤。 I have found passport to be effective.
我正在用 React、React Router 和 Redux 制作一个网站。许多路由(页面)要求用户登录。如果用户未登录,我可以重定向到登录页面:
function requireAuth(nextState, replace) {
let loggedIn = store.getState().AppReducer.UserReducer.loggedIn;
if(!loggedIn) {
replace({
pathname: '/login',
state: {
nextpathname: nextState.location.pathname
}
});
}
}
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<Route path="/" component={App}>
<IndexRoute component={Index} />
<Route path="login" component={Login} />
<Route path="register" component={Register} />
<Route path="dashboard" component={Graph} onEnter={requireAuth}>
... some other route requires logged in ...
</Route>
</Route>
</Router>
</Provider>,
document.getElementById('entry')
);
请查看代码,如果用户未登录,我使用 onEnter 挂钩重定向到 '/login' 路由。用于检查用户是否登录的数据在商店中,它将在之后更新用户登录。
它工作得很好,但问题是当我刷新页面时,商店被重置并且用户没有登录状态。
我知道发生这种情况是因为 Redux 存储只是内存存储,所以刷新页面会丢失存储中的所有数据。
在每次刷新时检查服务器会话可能有效,但这可能是请求太多,所以这似乎是个坏主意。
将登录状态数据保存到 localStorage 可能有效,但在这种情况下,我应该检查每个 AJAX 调用是否失败,因为会话已过期或不存在,请求被拒绝,这看起来像是也是个坏主意。
有没有更简单的方法解决这个问题?我的网站需要处理大量用户,所以我想尽可能减少 XHR 调用。
任何建议将不胜感激。
为什么不使用带有登录状态和到期日期的 sessionStorage?您将不得不编写更多代码来检查 sessionStorage 状态,但我认为这是避免发送 XHR 调用的唯一方法。
另一种方法是使用 JSON Web Tokens (JWT) that are required for each route, and localStorage 检查 JWT。
TL;DR
在前端,您有一个登录和注册路由来查询您的 根据服务器上的身份验证为 JWT 服务器。一次 通过了适当的 JWT,然后您可以将 属性 状态设置为 真的。您可以有一个注销路由,允许用户设置它 状态为假。
包含你的路由的index.js可以查看本地存储 在渲染之前,从而消除了丢失状态的问题 刷新但保持一定的安全性。
您的应用程序中需要身份验证的所有路由都已呈现 通过组合组件,并根据需要进行保护 在 header 中具有 JWT 以在服务器 API.
上进行授权
设置它需要一些时间,但它会使您的应用程序 'reasonably' 安全。
解决您的问题:
检查 index.js
文件中路由之前的本地存储,如下所示,如果需要,将状态更新为已验证。
应用程序维护安全,因为 API 由 JWT 保护,这将解决您的刷新问题,并维护对您的服务器和数据的安全 link。
因此在路线中你会有这样的东西:
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware, compose } from 'redux';
import { Router, Route, browserHistory, IndexRoute } from 'react-router';
import reduxThunk from 'redux-thunk';
import { AUTHENTICATE_THE_USER } from './actions/types';
import RequireAuth from './components/auth/require_auth';
import reducers from './reducers';
/* ...import necessary components */
const createStoreWithMiddleware = compose(applyMiddleware(reduxThunk))(createStore);
const store = createStoreWithMiddleware(reducers);
/* ... */
// Check for token and update application state if required
const token = localStorage.getItem('token');
if (token) {
store.dispatch({ type: AUTHENTICATE_THE_USER });
}
/* ... */
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<Route path="/" component={App}>
<IndexRoute component={Index} />
<Route path="login" component={Login} />
<Route path="register" component={Register} />
<Route path="dashboard" component={RequireAuth(Graph)} />
<Route path="isauthenticated" component={RequireAuth(IsAuthenticated)} />
... some other route requires logged in ...
</Route>
</Router>
</Provider>
, document.getElementById('entry'));
RequiredAuth
是组合组件,而 Graph
和 IsAuthenticated
(可以是任意数量的适当命名的组件)要求 state.authenticated
为真。
组件,在本例中 Graph
和 IsAuthenticated
在 state.authenticated
为真时呈现。否则默认返回根路由。
然后你可以像这样构建一个组合组件,通过它呈现你所有的路由。它将在呈现之前检查您所持有的用户是否已通过身份验证(布尔值)的状态是否为真。
require_auth.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
export default function (ComposedComponent) {
// If user not authenticated render out to root
class Authentication extends Component {
static contextTypes = {
router: React.PropTypes.object
};
componentWillMount() {
if (!this.props.authenticated) {
this.context.router.push('/');
}
}
componentWillUpdate(nextProps) {
if (!nextProps.authenticated) {
this.context.router.push('/');
}
}
render() {
return <ComposedComponent {...this.props} />;
}
}
function mapStateToProps(state) {
return { authenticated: state.authenticated };
}
return connect(mapStateToProps)(Authentication);
}
在 signup/signin 方面,您可以创建一个操作来存储 JWT 并设置状态以通过 action-creator -> redux 存储进行身份验证。此示例 makes use of axios 到 运行 异步 HTTP 请求响应周期。
export function signinUser({ email, password }) {
// Note using the npm package 'redux-thunk'
// giving direct access to the dispatch method
return function (dispatch) {
// Submit email and password to server
axios.post(`${API_URL}/signin`, { email, password })
.then(response => {
// If request is good update state - user is authenticated
dispatch({ type: AUTHENTICATE_THE_USER });
// - Save the JWT in localStorage
localStorage.setItem('token', response.data.token);
// - redirect to the route '/isauthenticated'
browserHistory.push('/isauthenticated');
})
.catch(() => {
// If request is bad show an error to the user
dispatch(authenticationError('Incorrect email or password!'));
});
};
}
当然,您还需要设置商店(在本例中为 Redux)和 action creator。
'real' 安全来自后端。为此,您使用 localStorage 将 JWT 保留在前端,并将其在 header 中传递给任何具有 sensitive/protected 信息的 API 调用。
为服务器上的用户创建和解析 JWT API 是另一个步骤。 I have found passport to be effective.