使用Cookie实现页面自动登录
关于Cookie
我们知道一个页面关闭之后,关于这个页面的前端代码在内存里的东西就都没有了。如果想要进行前端数据的持久化,目前主要有Cookie、LocalStorage和SessionStorage这几种方式。这些方式本质上都是将数据通过浏览器存储在了磁盘上(所以随着浏览器的发展,以后可能还会出现更多的数据持久化的方式呢)。那为什么普遍都选用Cookie来实现页面自动登录的功能呢?
简单地说,因为Cookie有这样一个机制:它会自动出现在前端与后端发送的每一个请求中(以header的形式),即使你没有设置任何Cookie,请求中也会包含一个空的Cookie header。因此只要将用户的认证信息放在Cookie当中,就表示之后的每个请求自带了身份验证的功能,服务器端可以通过检查每一条请求的Cookie信息来确认该请求是否“合法”。这里要注意的是,Cookie是和域名绑定的,不同域名的Cookie是相互隔离的,即设置了http://www.google.com
的Cookie后,只有该页面下的请求会自动夹带这个设定的Cookie。
另外,Cookie在设置时一般会添加一个过期的时间,即当一个Cookie过期了,浏览器会自动删除该Cookie的内容,然后后端发现过来的请求的Cookie为空,就redirect到登陆页面了。用户重新登陆后,后端会同时更新Cookie的信息。设置过期时间的目的主要还是为了安全,因为Cookie里面一般存储的是根据用户的用户名和密码生成的一段秘钥,用户每次登陆就会去重新生成一段秘钥,这就好比你经常更换密码一样,坏人想破解你的秘钥,可能破解到一半秘钥就变了,你说坏人气不气。(Cookie过期的时候服务器端一般不需要做任何操作?这里如果服务器不去管理Cookie过期的话可能会有安全的隐患,即一个用户很长时间不登陆,虽然Cookie过期了,但后端服务器还是认之前这个过期的Cookie信息的,也就给人足够的时间来破解Cookie的信息并伪装成该用户登陆了)
以上,也就解释了为什么有些网站很久才需要重新登陆一次,而有些网站没过多久就又要输用户名密码了。
这里有额外的一个问题,上面这个场景下,Cookie能不能被LocalStorage替代?
答案是肯定的,因为它们都能将数据持久化嘛,只不过我们需要在前端代码中手动添加一个header来存放LocalStoage里的信息,且每次发送请求都夹带这个header,并且每次还要检查是否过期,过期了则删除该header里的内容。
Cookie in front-end
查看/设置Cookie
随便打开一个需要登录的网页,在浏览器的dev console里面输入如下命令就能看的当前页面的Cookie啦(注意,如果Cookie设置了HttpOnly
属性,JS代码是没权限查看到Cookie的,也就是说document.cookie
会永远都是空):
document.cookie
因此前端查看Cookie信息就是查看document.cookie
变量,设置Cookie也是设置这个变量。
实现自动登录
如果路由是由前端决定的,那么只需要在每次向后端发送请求的时候以及切换子页面时检查下document.cookie
变量是否包含所需的内容即可,如果不包含则跳转到登陆页面。
这里以react-router
为例,并且使用react-router-redux
来将路由信息也放入Redux的状态机中,每次登陆子页面时来检查Cookie是否存在:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import {Router, Route, browserHistory, IndexRoute} from 'react-router'
import About from './components/About'
import Login from './components/Login'
import Home from './components/Home'
import './index.css';
import {Provider} from 'react-redux'
import {syncHistoryWithStore} from 'react-router-redux'
import store from './store'
function requireAuth(nextState, replaceState) {
// Assume the cookie only saves auth info
if (!document.cookie)
replaceState('/login');
}
// Create an enhanced history that syncs navigation events with the store
const history = syncHistoryWithStore(browserHistory, store, {
selectLocationState: state => ({locationBeforeTransitions: state.get('routing').get('locationBeforeTransitions')})
});
ReactDOM.render(
(<Provider store={store}>
<Router history={history}>
<Route path="/login" component={Login}>
</Route>
<Route path="/" component={App}>
<IndexRoute component={Home} onEnter={requireAuth}/>
<Route path="about" component={About} onEnter={requireAuth}/>
</Route>
</Router>
</Provider>), document.getElementById('root')
);
当然,上面的实现有一个前提,那就是Cookie没有设置为HttpOnly。如果Cookie设置了HttpOnly该怎么实现呢?一个思路是利用LocalStorage存储一个变量表示Cookie过期的时间节点,这个时间节点在登陆成功时触发更新(更新到多久以后要和后端一致,后端也可以提供一个api来同步过期时间),每次需要向后端发送请求时检查这个时间是否已过期,如果过期则转换到登陆页面,这样就避免了JS代码直接操作Cookie产生的安全隐患。
等等,真的需要这么复杂吗?还有一种简单有效的方法是,将含有敏感信息的Cookie设为HttpOnly的同时,添加一个无关痛痒的Cookie,它不设为HttpOnly且过期时间和上面的Cookie一致,毕竟前端代码只是想知道Cookie过期了没,并不关心Cookie的内容嘛。
Cookie in back-end
查看/设置Cookie
Flask已经封装好了Cookie相关的方法,直接调用即可:
from flask import Flask, request, Response
import time
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'hello world'
@app.route('/login')
def login():
res = Response('set cookies')
res.set_cookie(key='name', value='foo', expires=time.time() + 60)
return res
@app.route('/show')
def show():
return request.cookies.__str__()
实现自动登录
如果路由是由后端决定的,那么自动登录功能就很简单啦。所谓自动登录,其实就等同于Cookie没过期时不需要重新登陆。所以,只需要在每个路由下面判断下Cookie是否过期,如果过期了则跳转到登陆页面即可。
同样用上面例子,这次如果Cookie过期了(或没设置)则自动跳转到/login
页面:
from functools import wraps
from flask import Flask, request, Response, redirect
import time
app = Flask(__name__)
def check_cookie(func):
@wraps(func)
def wrapper():
if not request.cookies:
return redirect('/login')
else:
func()
return wrapper
@app.route('/')
@check_cookie
def hello_world():
return 'hello world'
@app.route('/login')
def login():
res = Response('add cookies')
res.set_cookie(key='name', value='foo', expires=time.time() + 60)
return res
@app.route('/show')
@check_cookie
def show():
return request.cookies.__str__()
Comments