Cookie、Session和Token的全面解析
一、基本概念
为什么需要会话管理
HTTP是无状态的协议(对于事务处理没有记忆能力,每次客户端和服务端会话完成时,服务端不会保存任何会话信息)。每个请求都是完全独立的,服务端无法确认当前访问者的身份信息,无法分辨上一次的请求发送者和这一次的发送者是不是同一个人。所以服务器与浏览器为了进行会话跟踪(知道是谁在访问我),就必须主动维护一个状态,这个状态用于告知服务端前后两个请求是否来自同一浏览器。
定义
Cookie:服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。
Session:代表服务器和客户端一次会话的过程。Session对象存储特定用户会话所需的属性及配置信息。这样,当用户在访问不同web页面的时候,存储在Session对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当客户端关闭会话,或者Session超时失效时会话结束。
Token:服务端生成的一串字符串,作为客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。
二、工作流程
Cookie和Session流程
- 用户登录,输入账号密码;
- 服务器验证账号密码是否正确,并创建会话,然后把会话数据存储在数据库中;
- 将session ID(加密过的)保存在cookie中返回到浏览器;
- 具有session ID的cookie保存在用户浏览器;
- 接下来的请求中,浏览器携带cookie一并发给服务器,服务器会根据数据库验证session ID,如果验证通过,则继续处理;
- 一旦用户登出,服务端和客户端同时销毁该会话。
Token和Session流程
- 用户输入登录信息;
- 服务器判断登录信息正确,返回一个token(一般是JWT);
- token存储在客户端,保存在local storage(一般保存这里),session storage或者cookie;
- 接着发起请求的时候将token放进url参数或者http header (Authorization header);
- 服务器端用私钥对数据进行加密得到的签名,跟token里面的签名进行比较,如果签名一样且时间有效,则处理该请求;
- 一旦用户登出,token在客户端被销毁,没有服务端。
三、Token详解
为什么需要Token?
每个用户只需要保存自己的会话标识(Session ID),而服务端则要保存所有用户的Session ID。如果访问服务端的用户逐渐变多,就需要保存成千上万,而且服务端是由多台服务器组成的一个集群,用户通过服务器A登录了系统,那session id会保存在服务器A上,假设用户下一次请求被转发到服务器B,服务器B可没有用户的session id。
如何判断Token是否是伪造?
服务端对数据做一个签名(Sign),比如说服务端用HMAC-SHA256加密算法,再加上一个只有服务端才知道的密钥,对数据做一个签名,把这个签名和数据一起作为Token发给客户端。客户端收到Token以后可以把它存储起来,比如存储在Cookie里或者Local Storage中。由于密钥除了服务端任何其他用户都不知道,就无法伪造令牌(Token)。
当用户把这个Token发给服务端时,服务端使用相同的HMAC-SHA256算法和相同的密钥,对数据再计算一次签名,和Token中的签名做个对比,如果相同,说明用户已经登录过了,即验证成功。若不相同,那么说明这个请求是伪造的。
Token存储位置及安全性
Token可以存储在Cookie里或者Local Storage中,存储在不同地方有不同的安全考虑:
-
Web存储(local Storage/sessionStorage):
- 可以通过同一域Javascript访问
- 意味着任何在你的网站上的运行的JavaScript都可以访问Web存储,所以容易受到XSS攻击
-
Cookie存储:
- 可以设置httponly,防止被JavaScript读取
- 可以设置secure,保证token只在HTTPS下传输
- 但需要防护CSRF攻击
建议:存储在local Storage,XSS可以通过设置检查函数过滤传进来的参数,而且cookie大小有限,并不能储存太大的token。
有效的保护方法:
- 给token设置一个短期过期时间,即使token被其他人获取,也会很快不能再用
- 创建受攻击的token黑名单,不允许这些黑名单的token访问系统
四、区别对比
Cookie与Session的区别
- 作用范围不同:Cookie保存在客户端,Session保存在服务器端
- 存取方式不同:Cookie只能保存ASCII,Session可以存任意数据类型
- 有效期不同:Cookie可设置为长时间保持(如默认登录功能),Session一般失效时间较短(客户端关闭或Session超时)
- 隐私策略不同:Cookie存储在客户端,比较容易遭到不法获取;Session存储在服务端,安全性相对Cookie要好
- 存储大小不同:单个Cookie保存的数据不能超过4K,Session可存储数据远高于Cookie
Token(存储在Local Storage中)与Cookie的区别
- 状态:Token是无状态的,后端服务不需要记录token
- 跨域处理:token的CORS可以很好地处理跨域问题
- 验证方式:每次发送请求到后端,都需要检查JWT,只要验证通过就可以处理请求
- 域限制:cookie只能单域或者子域,token没有这个限制
- 平台支持:token支持移动平台,cookie不支持
- 安全性:token可防跨站请求(CSRF),因为token黑客难构造也无法通过CSRF获取
五、JWT详解
定义
JSON Web Token (JWT)是一个开放标准(RFC 7519),它用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的,本质上也是token。
使用场景
-
授权(Authorization):最常见场景。一旦用户登录,后续每个请求都将包含JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是现在广泛使用的JWT的一个特性,因为它的开销很小,并且可以轻松地跨域使用。
-
信息交换(Information Exchange):对于安全的在各方之间传输信息而言,JSON Web Tokens无疑是一种很好的方式。因为JWT可以被签名(如用公钥/私钥对),可以确定发送人就是它们所说的那个人。另外,由于签名是使用头和有效负载计算的,还可以验证内容没有被篡改。
JWT构成
JWT由三段信息构成,用"."将三段信息连成一个JWT字符串,格式为:header.payload.signature
示例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
注意:header和payload不要写入敏感信息
1. 头部(header)
主要有2个部分:
- 声明类型,这里是jwt
- 声明加密的算法(HMAC SHA256和RSA等),通常直接使用HMAC SHA256
示例:
{
"typ": "JWT",
"alg": "HS256"
}
之后用Base64对这个json编码就得到第一段信息
2. 消息体(payload)
存放有效信息的地方,包含声明(要求),声明是关于实体(通常是用户)和其他数据的声明,声明有三种类型:
-
标准中注册的声明(推荐使用,不强制):
- iss: jwt签发者
- sub: jwt所面向的用户
- aud: 接收jwt的一方
- exp: jwt的过期时间,这个过期时间必须要大于签发时间
- nbf: 定义在什么时间之前,该jwt都是不可用的
- iat: jwt的签发时间
- jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
-
公共的声明:可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息,自定义
-
私有的声明:私有声明用于同意使用它们的各方之间共享信息,并且不是注册的或公开的声明
示例:
{
"sub": "123426711",
"name": "assion",
"admin": true
}
之后base64加密
3. 签证(signature)
这个签证信息由三部分组成:
- header (base64后的)
- payload (base64后的)
- secret(盐=私钥)
这个部分需要header (base64后的)和payload (base64)后的使用"."连接组成的字符串,然后通过header中声明的加密方式进行加secret盐组合加密,然后就构成了jwt的第三部分。
示例代码:
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
注意:secret(私钥)是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,不能泄露出去
JWT优点
- 跨语言支持:因为json的通用性,所以JWT可以进行跨语言支持,像JAVA,JavaScript,PHP等很多语言都可以使用
- 信息存储:JWT可以在payload部分存储一些其他业务逻辑所必要的非敏感信息
- 轻量高效:jwt的构成简单,字节占用小,便于传输
- 无状态:不需要在服务端保存会话信息,易于应用的扩展