关于Cookie和Session以及Token的说明

现在做web的话就绕不开安全认证这一步,那就先介绍下目前常见的认证方式然后再说关于cookie、session、token的东西。常用的认证方式简单概述如下:

  • cookie-session认证方式

    出现较早的认证方式,主要形式是浏览器客户端将用户名密码发送给服务器,服务器验证后创建session并发放用于识别用户的sessionID(与用户状态绑定后记录在服务器端),这个sessionID以及一些相关的其他信息就是cookie,cookie随着响应(Set-Cookie)返回给客户端由客户端存储于浏览器,之后客户端的请求都会带上这个cookie,服务端通过cookie来获取Session信息从而进行认证校验。

  • Oauth认证方式

    Oauth是一种授权机制,主要为第三方应用颁发授权令牌(token),目前有Oauth2.0和Oauth1.0版本,其中Oauth2.0版本的标准是RFC6749,Oauth1.0版本的标准是RFC5849。Oauth2.0的具体介绍可以参考阮一峰|Oauth2.0

  • JWT认证方式

    JWT的标准是RFC7519。大概原理是客户端经过服务器认证后服务器给客户端返回一个json对象(包含用户信息且加密处理的数据),之后客户端与服务器通信都会带上这个json对象,服务器只通过这个对象来认证用户,也就是说服务器端是无状态的不会保存状态数据了(比如session)。具体可参考阮一峰|JSON Web Token 入门教程

概念梳理

  • Cookie

    Cookie(复数形态Cookies),又称为“小甜饼”。类型为“小型文本文件”[1],指某些网站为了辨别用户身份而储存在用户本地终端(Client Side)上的数据(通常经过加密)。由网景公司的前雇员卢·蒙特利在1993年3月发明[2]。最初定义于RFC 2109。当前使用最广泛的 Cookie标准却不是RFC中定义的任何一个,而是在网景公司制定的标准上进行扩展后的产物。

    Cookie总是保存在客户端中,按在客户端中的存储位置,可分为内存Cookie和硬盘Cookie。内存Cookie由浏览器维护,保存在内存中,浏览器关闭后就消失了,其存在时间是短暂的。硬盘Cookie保存在硬盘里,有一个过期时间,除非用户手工清理或到了过期时间,硬盘Cookie不会被删除,其存在时间是长期的。所以,按存在时间,可分为非持久Cookie和持久Cookie。

    Cookie就是用来绕开HTTP的无状态性的“额外手段”之一。服务器可以设置或读取Cookies中包含信息,借此维护用户跟服务器会话中的状态

    使用Cookie的缺陷:

    • Cookie会被附加在每个HTTP请求中,所以无形中增加了流量
    • 由于在HTTP请求中的Cookie是明文传递的,所以安全性成问题,除非用HTTPS
    • Cookie的大小限制在4KB左右,对于复杂的存储需求来说是不够用的

    —选自Wikipedia

  • Session

    跟上面cookie不同,cookie是实际存在的,而session是一个抽象概念,我们更多说的是session的实现。session其实就是服务器用来保存用户会话状态(因为HTTP是一个无状态的协议)的一种机制。

    服务器session存放在服务器(默认存在文件也可以存在内存、数据库中),运行需要依赖于session id,不过一般session id会存在客户端cookie中(当然如果浏览器禁用cookie的话,也可以通过其他方式实现,比如通过url来传递)

  • Token

    这里说的token是access token,仅仅是指访问资源凭证,是跟上面说的Oauth认证相关的。主要针对的是从第三方应用获得授权登录,客户端从第三方应用获取的授权登录令牌,我们就称为token。

代码示例(Flask)

下面就FLask来做一些代码演示

Demo-1(逻辑演示)

如果不使用session-cookie机制,我们也可以实现用户的登录控制,下面这个非常简陋只是为了演示下登录验证的思路,不必纠结细节,极其不pythonic,代码臃肿且不科学。说一下下面的问题

  • 缺失判断来源的逻辑(应该增加判断条件更准确的鉴别请求来源)
  • 每次都需要重新登录验证
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
app = Flask(__name__)
allow_login = False
@app.route('/login',methods=['GET','POST'],endpoint='login')
def login():
global allow_login
if request.method == 'GET':
return render_template('login.html')
if request.form['name'] == 'gourds' and request.form['password'] == 'arvon':
allow_login = True
return 'success login'
allow_login = False
return render_template('login.html')
@app.route('/page1',methods=['GET'],endpoint='p1')
def page1():
global allow_login
if allow_login == True:
allow_login = False
return 'play page1'
return redirect(url_for('login'))
@app.route('/page2',methods=['GET'],endpoint='p2')
def page2():
global allow_login
if allow_login == True:
allow_login = False
return 'play page2'
return redirect(url_for('login'))

demo-2(使用session-cookie)

使用session-cookie的话,如下
在下面的例子中当键入用户密码发出POST请求后,服务器的response是Set-cookie:session=eyJnb3VyZHMiOiJnb3VyZHMtc2Vzc2lvbiJ9.EEaMcA.eV2X1jpYYTAZePmRTT5cdYhUfXw; HttpOnly; Path=/,从浏览器开发者模式可以看到这个值存放在浏览器cookies下,然后访问其他页面时请求头(Request headers:session=eyJnb3VyZHMiOiJnb3VyZHMtc2Vzc2lvbiJ9.EEaMcA.eV2X1jpYYTAZePmRTT5cdYhUfXw)会带上这个cookiesession=eyJnb3VyZHMiOiJnb3VyZHMtc2Vzc2lvbiJ9.EEaMcA.eV2X1jpYYTAZePmRTT5cdYhUfXw,就避免了每次输入账号密码的尴尬了。如果在浏览器在这个cookie删除或者禁用,就不能正常访问了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
app = Flask(__name__)
allow_login = False
@app.route('/login',methods=['GET','POST'],endpoint='login')
def login():
if request.method == 'GET':
return render_template('login.html')
if request.form['name'] == 'gourds' and request.form['password'] == 'arvon':
session['gourds'] = 'gourds-session'
return 'success login'
return render_template('login.html')
@app.route('/page1',methods=['GET'],endpoint='p1')
def page1():
if session.get('gourds'):
return 'play page1'
return redirect(url_for('login'))
@app.route('/page2',methods=['GET'],endpoint='p2')
def page2():
if session.get('gourds'):
return 'play page2'
return redirect(url_for('login'))

Demo-3(使用Flask的特殊装饰符)

使用Flask的特殊装饰符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
app = Flask(__name__)
allow_login = False
@app.before_request
def check_status():
if request.path == '/login':
return None
if not session.get('gourds'):
return redirect('/login')
@app.route('/login', methods=['GET', 'POST'], endpoint='login')
def login():
if request.method == 'GET':
return render_template('login.html')
if request.form['name'] == 'gourds' and request.form['password'] == 'arvon':
session['gourds'] = 'gourds-session'
return 'success login'
return render_template('login.html')
@app.route('/page1', methods=['GET'], endpoint='p1')
def page1():
return 'play page1'
@app.route('/page2', methods=['GET'], endpoint='p2')
def page2():
return 'play page2'

参考文档