现在做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'))

使用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'

参考文档