WEB用户验证的几种方式

本文总阅读量

前记

HTTP协议是非常简单流行的, 然而HTTP协议也是无状态的, 且没有用户标识. 目前业界已经有几套成熟的用户标识以及用户验证的方案, 让后端知道访问的用户是谁.常见的用户验证有:
SessionCookiesTokenJwt等等

HTTP是一个无状态的连接过程,每一次访问对于Server来说都是新用户,那么需要一些技术来让 Server知道这次连接过来的是谁。从而产生了SessionCookies。其中CookiesHTTP 协议中规定的,而Session是基于Cookies衍生出来的。

可是在App端中,不像浏览器一样, Cookies是没法有效储存,导致了CookiesSession无法使用。所以我们会采用手动的形式来模拟浏览器处理Cookies的流程来创造一套属于自己的HTTP用户认证方式:TOKENJWT

1.HTTP Auth Authentication

HTTP提供了一个用于权限控制和认证的方案, 目前最简单的就是HTTP Basic Authentication. 该方案非常简单, 在Authorization字段中存放一个用户名:密码的base64的字符串, 双方通过解析该字符串从而知道了用户是谁, 不过该方案太简单, 所以也很容易被破解.

2.COOKIES

如果双方一直都用HTTP Basic Authentication验证用户, 那也太麻烦了, 而且容易泄露用户密码. 如果用户只登录一次, 后面只用一个标记身份的字符串代替, 那就非常方便了, Cookies就是这样的.
CookiesHTTP协议中自带的内容,所以浏览器对其的支持都是很好的,说白了就是一个简单的KV(key-value)的数组。同时 Cookies 是对用户透明的,所以后端都会对Cookies进行一定的编码,使cookies详细的数据不可见(但仍可以使用整段cookie来达到欺骗的效果),用户不用做任何操作,浏览器会根据 HTTP 报文来做处理。

2.1流程

Client 访问 Server 时会自动带上Cookies,具体实现是在HTTP头部的Cookies中传递过去。
Server 可以在 Client 中写入Cookies,具体是在HTTP响应报文头部中Set-Cookie 来告诉浏览器需要在 Client 中记录这些信息,以后访问的时候顺便带过来。
以访问google为栗子(提取于Django-book):
第一次访问时:

1
2
3
GET / HTTP/1.1
Host: google.com
...

响应:

1
2
3
4
5
6
7
8
HTTP/1.1 200 OK
Content-Type: text/html
Set-Cookie: PREF=ID=5b14f22bdaf1e81c:TM=1167000671:LM=1167000671;
expires=Sun, 17-Jan-2038 19:14:07 GMT;
path=/; domain=.google.com
Server: GWS/2.1
...
可以看到里面是以`;`区分的key-value数据, 第一个为`Cookie`, 第二个是过期时间, 告诉浏览器服务端会在多久之后刷新Cookie. 后面还有当前的path以及domain信息

之后客户端访问服务端时, 都会在Cookie字段填写Cookie:

1
2
3
4
GET / HTTP/1.1
Host: google.com
Cookie: PREF=ID=5b14f22bdaf1e81c:TM=1167000671:LM=1167000671
...

2.2特性

除了过期时间外, 服务端还可以通过以下字段设置而Cookie的一些限制:

  • setDomain:设置cookie范围
  • isHttpOnly:是否只是http协议使用。只能在后端通过getCookies()获取,js不能获取;

而且需要注意的是每一个cookie文件大小:4kb , 如果超过4kb浏览器不识别;cookie不安全,可能泄露用户信息,浏览器支持禁用cookie操作;

3.SESSION

Session是在Cookies发展中为了解决Cookies某些弊端而产生的技术,为了绕开Cookies的各种限制,通常借助Cookies本身和后端存储实现的,一种更高级的会话状态实现,Session是存在服务器的,用于区分会话和不同用户的访问。
Session主要是为了跟踪会话,比如某些网站中网页有不同的访问权限,有只能登录的用户访问的网页或者用户级别不同不能访问的,但是http请求是无状态的,每次访问服务端是不知道是否是登录用户,很自然的想到在http请求报文中加入登录标识就可以了,这个登录标识就可以是cookie,这样的cookie服务端要保存有所有登录用户的cookie,这样请求报文来了之后拿到登录标识cookie,在服务端进行比较久可以了。再比如购物网站,多次点击添加商品到购物车客户端很容易知道哪些物品在购物车中,但是服务端怎么知道每次添加的物品放到哪个登录用户的购物车中呢?也需要请求报文中带着cookie才行,这些cookie都是为了跟踪会话用的,所以客户端有,服务端也有,并且服务端有全部的会话cookie。

4.TOKEN

Token它可以是 Session-id,也可以是一串随机的字符。但是它的作用跟Session-id 一样都是为了识别不同的访问者,同时验证它的有效性。

Token 是一个很广泛的说法,是因为它只提供了一个验证的准则,而不是一个具体的做法,所以 JWT、OAUTH 都可以算是 Token 的一种实现。

在我看来,Token的过程就是这么一回事:
Client —> Server: 嘿,我是那个谁啊
Server —> Client: 知道了,你记着你的票号 123456,半个小时内有有效
// 几分钟后
Client —> Server: 嘿,我的票号是 123456,帮我买瓶水,钱从账户扣
Server —> Client: (嗯,123456 在有效期内)好嘞
Client —> Server: (我试下用别人的账号买东西)嘿,我的票号是 666666,帮我买瓶水,钱从账户扣
Server —> Client: (嗯?我们根本就没有 666666 这个票号的记录啊)兄弟,你是来搞事的吧
// 半个小时后
Client —> Server: 嘿,我的票号是 123456,帮我买瓶水,钱从账户扣
Server —> Client: (em,好像过时了)诶诶诶,你的票号过期了,不办理了

在token中,Token能明确这里明确告诉了用户他的识别id和有效期也就是说 Server 必须通过某种方式通知 Client,而不是像 Cookies 和 Session 那样使用 Set-Cookie就可以完成的。

服务器告知客户端的具体实现方法如下:
通过 HTTP RESPONSE 报文中 Headers 返回 Token:123456
通过 HTTP RESPONSE 报文中Content 返回{‘status’:’OK’, ‘data’:{‘token’:’123456’}}
ps:使用 Header 携带消息的方法使用较少,普遍都是使用 Content 来携带消息,因为通常情况下,需要返回的信息不只是 Token 本身,还有相关的附加信息:Expire-time 等等

客户端告知服务器的具体实现方法如下:
通过 HTTP REQUEST 报文中 Header 携带 Token:123456
通过 HTTP URL 携带:http://foo.com/buy?good=water&number=1&token=123456
通过 HTTP FORM 携带:这个方法只能出现在非 GET 请求下。
上述讲的三种方法,最常见的是前两种,第三种少使用的原因是需要为 GET 设计另外一种 Token 携带方案,而前两种适合于所有 HTTP 访问

5.JWT

JWT 是 Token 的具体且应用实现。
它具备两个特点:

  • 简洁(Compact)
    可以通过URL, POST 参数或者在 HTTP header 发送,因为数据量小,传输速度快

  • 自包含(Self-contained)
    负载中包含了所有用户所需要的信息,避免了多次查询数据库

同时JWT还具有两种特性:时效性,有效性。

时效性指 JWT 提供了 Token 的过期时间。
有效性指 JWT 可以验证当前的 Token 是否被修改。
JWT 的组成是这样的:Header||Payload||Signature
Header 标明了这是一个 JWT,而且使用了什么 Hash 算法
Payload 储存了你保存在用户数据和 JWT 自身的一些必要信息,必要信息常用的有iss(签发者),exp(过期时间),sub(面向的用户),aud(接收方),iat(签发时间)
Signature 是 Server 对 Header 和 Payload 的 HMAC 结果。也就是签名,签名的作用是保证 JWT 没有被篡改过。

虽然使用了签名能确保JWT是否被篡改,使用编码能使JWT变为非人类视角的代码,但是还是很容易破解,因此JWT适合用于向Web应用传递一些非敏感信息。所以JWT经常用于设计用户认证和授权系统,甚至实现Web应用的单点登录。

JWT流程:
1.首先,前端通过Web表单将自己的用户名和密码发送到后端的接口。这一过程一般是一个HTTP POST请求。建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探。
2.后端核对用户名和密码成功后,将用户的id等其他信息作为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT。形成的JWT就是一个形同lll.zzz.xxx的字符串。
3.后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStorage或sessionStorage上,退出登录时前端删除保存的JWT即可。
4.前端在每次请求时将JWT放入HTTP Header中的Authorization位。(解决XSS和XSRF问题)
5.后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确;检查Token是否过期;检查Token的接收方是否是自己(可选)。
6.验证通过后后端使用JWT中包含的用户信息进行其他逻辑操作,返回相应结果。

流程图:

单点登录

还没使用过,待填坑
参考文章:1,2

查看评论