Cookie,Session和Token会话知识整理

HTTP是一种无状态的协议,然而当服务器端需要判断用户能否访问某些资源,记录用户的购物车内容等场景时,就需要一种机制维护会话状态,这时候Cookie、Session和Token就派上了用场。

Cookie

Cookie技术最早用于解决HTTP的会话问题, 它是 http 协议的一部分,它的处理分为如下几步:

  • 服务器向客户端发送 cookie。
    • 通常使用 HTTP 协议规定的 set-cookie 头操作。
    • 规范规定 cookie 的格式为 name = value 格式,且必须包含这部分。
  • 浏览器将 cookie 保存。
  • 每次请求浏览器都会将 cookie 发向服务器。

可选的Cookie参数

其他可选的 cookie 参数会影响将 cookie 发送给服务器端的过程,主要有以下几种:

  • path:表示 cookie 影响到的路径,匹配该路径才发送这个 cookie。
  • expires 和 maxAge:告诉浏览器这个 cookie 什么时候过期,expires 是 UTC 格式时间,maxAge 是 cookie 多久后过期的相对时间。当不设置这两个选项时,会产生 session cookie,session cookie 是 transient 的,当用户关闭浏览器时,就被清除。一般用来保存 session 的 session_id。
  • secure:当 secure 值为 true 时,cookie 在 HTTP 中是无效,在 HTTPS 中才有效。
  • httpOnly:浏览器不允许脚本操作 document.cookie 去更改 cookie。一般情况下都应该设置这个为 true,这样可以避免被 xss 攻击拿到 cookie。

常用场景

当给Cookie设置expires和maxAge后,在未到期前,浏览器端的Cookie不会因为浏览器的关闭而消失。该特性常用于自动登录,记录用户浏览信息。例如很多购物网站常用该特性记录用户的喜好和购买的物品。

Cookie安全隐患

Cookie提供了一种手段使得HTTP请求可以附加当前状态, 大多数网站就是靠Cookie来标识用户的登录状态的,例如:

  1. 用户提交用户名和密码的表单,这通常是一个POST HTTP请求。
  2. 服务器验证用户名与密码,如果合法则返回200(OK)并设置Set-Cookie为authed=true。
  3. 浏览器存储该Cookie。
  4. 浏览器发送请求时,设置Cookie字段为authed=true。
  5. 服务器收到第二次请求,从Cookie字段得知该用户已经登录。 按照已登录用户的权限来处理此次请求。

上述认证流程存在安全隐患,因为Cookie是可以被篡改的。如果使用一些HTTP客户端软件,设置Cookie字段为authed=true并发送该HTTP请求,服务器就会被欺骗。

Cookie防篡改机制

服务器为每个Cookie项生成签名,可有效地防止Cookie被篡改。因为用户篡改Cookie后无法生成对应的签名, 服务器便可得知用户对Cookie进行了篡改。一个简单的校验过程可能是这样的:

  1. 在服务器中配置一个不为人知的字符串(我们叫它Secret),比如:x$sfz32。
  2. 当服务器需要设置Cookie时(比如authed=false),不仅设置authed的值为false, 在值的后面进一步设置一个签名,最终设置的Cookie是authed=false|6hTiBl7lVpd1P。
  3. 签名6hTiBl7lVpd1P是这样生成的:Hash(‘x$sfz32’+’false’)。 要设置的值与Secret相加再取哈希。
  4. 用户收到HTTP响应并发现头字段Set-Cookie: authed=false|6hTiBl7lVpd1P。
  5. 用户在发送HTTP请求时,篡改了authed值,设置头字段Cookie: authed=true|???。 因为用户不知道Secret,无法生成签名,只能随便填一个。
  6. 服务器收到HTTP请求,发现Cookie: authed=true|???。服务器开始进行校验: Hash(‘true’+’x$sfz32’),便会发现用户提供的签名不正确。

通过给Cookie添加签名,使得服务器得以知道Cookie被篡改。然而故事并未结束。

因为Cookie是明文传输的, 只要服务器设置过一次authed=true|xxxx我不就知道true的签名是xxxx了么, 以后就可以用这个签名来欺骗服务器了。因此Cookie中最好不要放敏感数据。 一般来讲Cookie中只会放一个Session Id,而Session存储在服务器端。

Session

为了解决Cookie的安全隐患,Session机制应运而生。session机制是一种服务器端的机制,它存储在服务器端的,避免了在客户端Cookie中存储敏感数据。Session可以存储在HTTP服务器的内存中,也可以存在内存数据库(如redis)中, 对于重量级的应用甚至可以存储在数据库中。

客户端对服务端请求时,服务端会检查请求中是否包含一个session标识( 称为session id ).

  • 如果没有,那么服务端就生成一个随机的session以及和它匹配的session id,并将session id返回给客户端.
  • 如果有,那么服务器就在存储中根据session id 查找到对应的session.

基于Session的登录流程

一个简单的使用Session机制的登录流程可能是这样的:

  1. 用户提交包含用户名和密码的表单,发送HTTP请求。
  2. 服务器验证用户发来的用户名密码。
  3. 如果正确则把当前用户名(通常是用户对象)存储到redis中,并生成它在redis中的ID。这个ID称为Session ID,通过Session ID可以从Redis中取出对应的用户对象, 敏感数据(比如authed=true)都存储在这个用户对象中。
  4. 设置Cookie为sessionId=xxxxxx|checksum并发送HTTP响应, 仍然为每一项Cookie都设置签名。
  5. 用户收到HTTP响应后,便看不到任何敏感数据了。在此后的请求中发送该Cookie给服务器。
  6. 服务器收到此后的HTTP请求后,发现Cookie中有SessionID,进行放篡改验证。
  7. 如果通过了验证,根据该ID从Redis中取出对应的用户对象, 查看该对象的状态并继续执行业务逻辑。

Session安全隐患

Session ID作为Cookie存储在浏览器端,因此存在被劫持的风险,尤其是开发者没有正确的关闭会话。用户关闭会话时,应删除传递 Session ID 的 Cookie,同时撤销服务器端的Session内容。例如:

1
2
3
4
5
6
7
8
9
10
11
12
/* 普通用户登出 */
router.post('/signout', function(req, res, next) {
if (_.isEmpty(req.body) === false) {
req.session.account = null; // 删除session
res.json({
message: '登出成功!'
});

} else {
res.send(406, { message: 'The params is not correct!' });
}
});

Token

Token是用户的验证方式,最简单的token组成:uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,由token的前几位+盐以哈希算法压缩成一定长的十六进制字符串,可以防止恶意第三方拼接token请求服务器)。

基于Token的身份验证流程

使用基于 Token 的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程是这样的:

  1. 客户端使用用户名跟密码请求登录
  2. 服务端收到请求,去验证用户名与密码
  3. 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
  4. 客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Storage 里
  5. 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token
  6. 服务端收到请求,然后去验证客户端请求里面带着的 Token,如果验证成功,就向客户端返回请求的数据

基于Token方法的优势

  • JWT 方法允许我们进行AJAX调用任何服务器或域。由于HTTP头是用来传输用户信息的。
  • 没必要在服务器存储一个单独的session。JWT本身传达全部的信息。
  • 服务器端减少到只是一个API和可以通过CDN服务的静态资源(HTML,CSS,JS)。
  • 认证系统是手机兼容的,任何设备上可以生成令牌。
  • 由于已经消除了cookie的需要,也不再需要保护跨站请求。
  • API密钥提供非此即彼的解决方案,然而JWT提供更颗粒度的控制,它可以用于任何调试目的的检查。
  • API密钥依赖于中央存储和服务。JWT可以自发行或者外部服务在允许的范围和期限发布它。

JWT结构

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。

Cookie、Session和Token对比

cookie与session的区别

  1. cookie数据存放在客户端上,session数据放在服务器上。
  2. cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗。考虑到安全应当使用session。
  3. session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能。考虑到减轻服务器性能方面,应当使用COOKIE。

session与token的区别

作为身份认证token安全性比session好,因为每个请求都有签名还能防止监听以及重放攻击。

Session 是一种HTTP存储机制,目的是为无状态的HTTP提供的持久机制。Session 认证只是简单的把User 信息存储到Session 里,因为SID 的不可预测性,暂且认为是安全的。这是一种认证手段。 但是如果有了某个User的SID,就相当于拥有该User的全部权利.SID不应该共享给其他网站或第三方。

Token, 如果指的是OAuth Token 或类似的机制的话,提供的是 认证 和 授权,认证是针对用户,授权是针对App。其目的是让某App有权利访问某用户的信息。这里的Token是唯一的。不可以转移到其它App上,也不可以转到其它用户上。

参考链接

  1. cookie 和 session, by 极客学院.
  2. Cookie/Session的机制与安全, by Harttle Land.
  3. Python中关于JSON网络令牌的实例教程, by Python部落.
  4. 什么是 JWT – JSON WEB TOKEN, by Dearmadman
  5. JSON Web Token 入门教程,by 阮一峰.
  6. localForage,by github.
  7. node session 实现登录状态持久化,by 开心的米卡.