不同于传统的前端项目,React + React Router架构的SPA(Single Page Application)网站在不同的路由间共享一个状态集合,这就需要我们重新考虑登录验证的整体设计。
Q:登录信息保存在哪儿?
A:一般来说浏览器的数据存储有3种方法:Cookie、localStorage、sessionStorage,它们的异同如下:
特性 | Cookie | localStorage | sessionStorage |
---|---|---|---|
数据的生命期 | 可设置失效时间,默认是关闭浏览器后失效 | 除非被清除,否则永久保存 | 仅在当前会话下有效,关闭页面或浏览器后被清除 |
存放数据大小 | 4K左右 | 一般为5MB | 一般为5MB |
与服务器端通信 | 每次都会携带在HTTP头中,如果使用cookie保存过多数据会带来性能问题 | 仅在客户端(即浏览器)中保存,不参与和服务器的通信 | 仅在客户端(即浏览器)中保存,不参与和服务器的通信 |
易用性 | 需要程序员自己封装,源生的Cookie接口不友好 | 源生接口可以接受,亦可再次封装来对Object和Array有更好的支持 | 源生接口可以接受,亦可再次封装来对Object和Array有更好的支持 |
兼容性 | 所有现代浏览器 | IE8+、Chrome4+ | IE8+、Chrome5+ |
如果保存在sessionStorage中,在同一浏览器的不同Tab间无法共享状态信息,显然不能满足要求。由于localStorage兼容性尚可(即使需要兼容IE8以下浏览器也可以通过polyfill使用userData实现),而且具有较好的性能表现,故我们采用localStorage作为登录信息的保存方法。
Q:何时验证?
A:简单来说,就是利用react-router的onEnter方法
以这个路由结构为例:(index.js)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15ReactDOM.render(
<Provider store={store}>
<Router history={hashHistory}>
<Route name='主页' path="/" component={App}>
<IndexRoute component={HomePage} />
<Route name='登录' path='/login' component={LoginPage} />
<Route name='所有活动' path='/all-event' component={EventPage} >
<Route name='' path='/all-event/:id' component={EventDetailPage} />
</Route>
<Route name='404: No Match for route' path='*' component={NoMatchPage} />
</Route>
</Router>
</Provider>,
document.getElementById('root')
);
登录后,我们将登录信息同时保存到redux和localStorage中。前者用于在各个组件中取出登录信息,比如用户名等;后者用于持久化登录信息。因为当页面关闭,redux中的信息就会丢失,所以我们需要在index.js中将登录信息从localStorage传递给redux。
完整的index.js
如下:
1 | import React from 'react'; |
getAccount方法的作用是:
① 获取登录信息
② 如果未登录则重定向到登录页,如果已经登录且在登录页,重定向到主页。1
2
3
4
5
6
7
8
9
10
11
12
13
14function getAccount(router) {
let account = JSON.parse(localStorage.getItem('account'));
let isLoginPage = false;
if (router) {
isLoginPage = router.location.pathname === "/login";
}
if (account !== null && isLoginPage) {
window.location.href = '/#/';
} else if (account === null && !isLoginPage) {
let url = window.location.href;
window.location.href = '/#/login?callback=' + encodeURIComponent(url);
}
return account;
}
Q:如何验证登录信息的有效性?
A:以JWT Token为例,Token中携带了自身的过期时间,可初步验证Token的有效性。但我们知道localStorage是对用户开放的,如果登录信息被篡改了怎么办?这就要介绍一下JWT Token的基本原理了。
一个JWT Token由头部(Header)、载荷(Payload)、签名(Signture)3部分组成,每个部分用一个「.」连接。其中签名是由载荷的Base64编码用后端服务器上的密钥通过HS256算法加密得到的。所以,如果载荷和签名的任意部分被篡改,后端服务器可以轻易识别,从而通过API返回401或403错误。
而Token的过期时间就保存在载荷里,只要我们对载荷部分进行Base64解码,就可以得到Token的过期时间。由此我们对getAccount方法改造如下:
1 | function getAccount(router) { |