我正在使用 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 调用。
任何建议将不胜感激。
另一种方法是使用每个路由所需的 JSON Web 令牌 (JWT),并使用 localStorage 检查 JWT。
TL;博士
在前端,您有一个登录和注册路线,可以查询您的 根据服务器上的身份验证来获取 JWT 的服务器。一次 传递适当的 JWT,然后将状态属性设置为 真的。您可以有一个注销路由,允许用户进行设置 状态为 false。
包含您的路由的index.js可以检查本地存储 在渲染之前,从而消除了丢失状态的问题 刷新但保持一定的安全性。
应用程序中需要身份验证的所有路由都会被渲染 通过组合组件,并通过必要性进行保护 在标头中包含 JWT 以在服务器 API 上进行授权。
设置需要一点时间,但它将使您的应用程序“相当”安全。
解决您的问题:
在
index.js
文件中的路由之前检查本地存储,如下所示,如果需要,将状态更新为已验证。
应用程序保持安全性,因为 API 由 JWT 保护,这将解决您的刷新问题,并维护与服务器和数据的安全链接。
因此在路线中你会有这样的东西:
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
为 true。
如果 Graph
为 true,则渲染组件,在本例中为
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);
}
使用 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!'));
});
};
}
“真正的”安全来自后端。为此,您可以使用 localStorage 将 JWT 保留在前端,并将其在标头中传递给任何具有敏感/受保护信息的 API 调用。
在服务器 API 上为用户创建和解析 JWT 是另一个步骤。
一个缺点是 Web 本地存储数据可以永远存在,即使在服务器重新启动或页面退出后也是如此。但是,您可以使用令牌过期来管理它。