[TOC]
为什么要有Cookie?
HTTP 协议是无状态的,主要是为了让 HTTP 协议尽可能简单,使得它能够处理大量事务。HTTP/1.1 引入 Cookie 来
保存状态信息。
Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器之后向同一服务器再次发起请求时被携带上
用途:
· 会话状态管理(如用户登录状态、购物车)
· 浏览器行为跟踪(如跟踪分析用户行为等)
Cookie创建过程?
服务器发送的响应报文包含 Set-Cookie 首部字段,客户端得到响应报文后把 Cookie 内容保存到浏览器中
1 | HTTP/1.0 200 OK |
客户端之后对同一个服务器发送请求时,会从浏览器中取出 Cookie 信息并通过 Cookie 请求首部字段发送给服务
器。
1 | GET /sample_page.html HTTP/1.1 |
Cookie分类
会话期 Cookie:浏览器关闭之后它会被自动删除,也就是说它仅在会话期内有效。
持久性 Cookie:指定过期时间(Expires)或有效期(max-age)之后就成为了持久性的 Cookie
Session?
除了可以将用户信息通过 Cookie 存储在用户浏览器中,也可以利用 Session 存储在服务器端,存储在服务器端的信息更加安全。
session 认证流程:
- 用户第一次请求服务器的时候,服务器根据用户提交的相关信息,创建对应的 Session
- 请求返回时将此 Session 的唯一标识信息 SessionID 返回给浏览器
- 浏览器接收到服务器返回的 SessionID 信息后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名
- 当用户第二次访问服务器的时候,请求会自动判断此域名下是否存在 Cookie 信息,如果存在自动将 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息,如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作。
SessionID 是连接 Cookie 和 Session 的一道桥梁
在对安全性要求极高的场景下,例如转账等操作,除了使用 Session管理用户状态之外,还需要对用户进行重新验证,比如重新输入密码,或者使用短信验证码等方式。
Session和Cookie区别?
- Cookie 只能存储 ASCII 码字符串,而 Session 则可以存储任何类型的数据,因此在考虑数据复杂性时首选
Session; - Cookie 存储在浏览器中,容易被恶意查看。如果非要将一些隐私数据存在 Cookie 中,可以将 Cookie 值进行加
密,然后在服务器进行解密; - 对于大型网站,如果用户所有的信息都存储在 Session 中,那么开销是非常大的,因此不建议将所有的用户信
息都存储到 Session 中。
Cookie和Session的选择?
Cookie 只能存储 ASCII 码字符串,而 Session 则可以存储任何类型的数据,因此在考虑数据复杂性时首选Session;
Cookie 存储在浏览器中,容易被恶意查看。如果非要将一些隐私数据存在 Cookie 中,可以将 Cookie 值进行加密,然后在服务器进行解密;
对于大型网站,如果用户所有的信息都存储在 Session 中,那么开销是非常大的,因此不建议将所有的用户信息都存储到 Session 中。
Cookie的不可跨域名性?
一个cookie只能用于一个域名(有效的二级域名,比如 shop.com),不能够发给其它的域名。Cookie跨域指的是允许不同的二级域名间共享
Token?
- 把用户的用户名和密码发到后端
- 后端进行校验,校验成功会生成token, 把token发送给客户端
- 客户端自己保存token, 再次请求就要在Http协议的请求头中带着token去访问服务端,和在服务端保存的token信息进行比对校验。
- 每一次请求都需要携带 token,需要把 token 放到 HTTP 的 Header 里
- 基于 token 的用户认证是一种服务端无状态的认证方式,服务端不用存放 token 数据。用解析 token 的计算时间换取 session 的存储空间,从而减轻服务器的压力,减少频繁的查询数据库
- token 完全由应用管理,所以它可以避开同源策略
Token 和 Session 的区别
Session 是一种记录服务器和客户端会话状态的机制,使服务端有状态化,可以记录会话信息。而 Token 是令牌,访问资源接口(API)时所需要的资源凭证。Token 使服务端无状态化,不会存储会话信息。
Session 和 Token 并不矛盾,作为身份认证 Token 安全性比 Session 好,因为每一个请求都有签名还能防止监听以及重放攻击,而 Session 就必须依赖链路层来保障通讯安全了。如果你需要实现有状态的会话,仍然可以增加 Session 来在服务器端保存一些状态。
所谓 Session 认证只是简单的把 User 信息存储到 Session 里,因为 SessionID 的不可预测性,暂且认为是安全的。而 Token ,如果指的是 OAuth Token 或类似的机制的话,提供的是 认证 和 授权 ,认证是针对用户,授权是针对 App 。
JWT
Header
JWT 的 Header 通常包含两个字段,分别是:typ(type) 和 alg(algorithm)。
- typ:token的类型,这里固定为 JWT
- alg:使用的 hash 算法,例如:HMAC SHA256 或者 RSA
一个简单的例子:
1 | { |
我们对他进行编码后是:
1 | >>> base64.b64encode(json.dumps({"alg":"HS256","typ":"JWT"})) |
Payload
JWT 中的 Payload 其实就是真实存储我们需要传递的信息的部分,例如正常我们会存储些用户 ID、用户名之类的。此外,还包含一些例如发布人、过期日期等的元数据。
但是,这部分和 Header 部分不一样的地方在于这个地方可以加密,而不是简单得直接进行 BASE64 编码。但是这里我为了解释方便就直接使用 BASE64 编码,需要注意的是,这里的 BASE64 编码稍微有点不一样,切确得说应该是 Base64UrlEncoder,和 Base64 编码的区别在于会忽略最后的 padding(=号),然后 ‘-‘ 会被替换成’_’。
举个例子,例如我们的 Payload 是:
1 | {"user_id":"zhangsan"} |
那么直接 Base64 的话应该是:
1 | >>> base64.urlsafe_b64encode('{"user_id":"zhangsan"}') |
然后去掉 = 号,最后应该是:
1 | 'eyJ1c2VyX2lkIjoiemhhbmdzYW4ifQ' |
Signature
Signature 部分其实就是对我们前面的 Header 和 Payload 部分进行签名,保证 Token 在传输的过程中没有被篡改或者损坏,签名的算法也很简单,但是,为了加密,所以除了 Header 和 Payload 之外,还多了一个密钥字段,完整算法为:
1 | Signature = HMACSHA256( |
还是以前面的例子为例,
1 | base64UrlEncode(header) =》 eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9 |
secret 就设为:”secret”, 那最后出来的签名应该是:
1 | >>> import hmac |
将上面三个部分组装起来就组成了我们的 JWT token了,所以我们的
1 | {'user_id': 'zhangsan'} |
的 token 就是:
1 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiemhhbmdzYW4ifQ.ec7IVPU-ePtbdkb85IRnK4t4nUVvF2bBf8fGhJmEwSs |
在头部信息中声明加密算法和常量, 然后把header使用json转化为字符串
在载荷中声明用户信息,同时还有一些其他的内容;再次使用json 把载荷部分进行转化,转化为字符串
使用在header中声明的加密算法和每个项目随机生成的secret来进行加密, 把第一步分字符串和第二部分的字符串进行加密, 生成新的字符串。词字符串是独一无二的。
解密的时候,只要客户端带着JWT来发起请求,服务端就直接使用secret进行解密。
JWT与token的联系
Token:服务端验证客户端发送过来的 Token 时,还需要查询数据库获取用户信息,然后验证 Token 是否有效。
JWT: 将 Token 和 Payload 加密后存储于客户端,服务端只需要使用密钥解密进行校验(校验也是 JWT 自己实现的)即可,不需要查询或者减少查询数据库,因为 JWT 自包含了用户信息和加密的数据。