cookie
是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。通常,它用于告知服务端两个请求是否来自同一浏览器,如保持用户的登录状态。Cookie使基于无状态的HTTP协议记录稳定的状态信息成为了可能。
最初引入cookie这个概念时,是网景的一位雇员为了偷点懒,我登录了这个网站,为啥我换一个该网站的另一页面我还要登录呢? 所以产生了cookie,将少量的信息存储到用户电脑上,同一域名下的cookie是一样的。cookie的传递方式如下图:
但是也出现了新的问题 :
- Cookie 存在于客户端,可能随时被用户删除;
- Cookie 在浏览器中,即使访问的是同一界面,不同的浏览器的 Cookie 都是不同的,且不可访问;
- Cookie 可被禁用,这时 Session 还能用吗? (这个问题留到下面讲 Session 的时候细说);
- Cookie 本身有大小限制,对用户是可见的,这显然不安全,于是诞生了另外一个会话机制 : Session 。
session
字面上讲,就是会话的意思,是一种持久网络协议,在用户(或用户代理)端和服务器端之间创建关联,从而起到交换数据包的作用机制,session在网络协议(例如telnet或FTP)中是非常重要的部分。
目前我们所接触到的大部分用到session的方式都会采用一种 (服务端session + 客户端 sessionId)的方式,如下图:
原理:
- 步骤 1:客户端把用户 ID 和密码等登录信息放入报文的实体部分,通常是以 POST 方法把请求发送给服务器。
- 步骤 2:服务器会发放用以识别用户的 Session ID。通过验证从客户端发送过来的登录信息进行身份验证,然后把用户的认证状态与 Session ID 绑定后记录在服务器端。向客户端返回响应时,会在首部字段 Set-Cookie 内写入 Session ID。
- 步骤 3:客户端接收到从服务器端发来的 Session ID 后,会将其作为 Cookie 保存在本地。下次向服务器发送请求时,浏览器会自动发送 Cookie,所以 Session ID 也随之发送到服务器。服务器端可通过验证接收到的 Session ID 识别用户和其认证状态。
具体实现:
之前在后台为了解决登录持久化问题,用了 express-session 其中便实现了以上的逻辑。
|
|
回到上面提到的一个问题: Cookie 可被禁用,这时 Session 还能用吗?
- 如果用户禁止 cookie,服务器仍会将 sessionID 以 cookie 的方式发送给浏览器,但是,浏览器不再保存这个 cookie (即 sessionID )了。
- 如果想继续使用 session,需要采取其他方式来实现 sessionID 的跟踪。可以使用 url 重写来实现 sessionID 的跟踪。
弊端:
- 传输安全
有一种叫做 Session ID 劫持的,假如 Session ID 是基于 HTTP 协议传输的,因为是明文传输,那么它就可能被中间的路由器劫持。 攻击者得到 Session ID 后,把它带到自己的请求中,就能够进入你的账户。所以一些 Web 框架还提供了 Session 的一些安全保护,比如间隔时间内动态刷新 Session ID,加上 Token 等。但这些也无法完全保证不被中间人看到。 其实从这个角度也间接体现了为什么 HTTPS 这么重要。
- 扩展性较差
如果是单纯的一个服务器的话没什么问题,如果是服务器集群等情况,就需要要求 session 数据共享,每台服务器都能读取到 session。一般会采用以下举措:
- session 进行复制: 任何一个服务器上的 session 发生改变(增删改),该节点会把这个 session 的所有内容序列化,广播给所有其它节点。
- Nginx 代理: 每个请求按访问 IP 的 hash 分配,这样来自同一 IP 固定访问一个后台服务器,避免了在服务器 A 创建 session,第二次分发到服务器 B 的现象。
- 共享 session:将 sessionID 集中存储到一个地方,所有的机器都来访问这个地方的数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。
- 还有一种就是 服务器不再保存 session 数据, 所有数据保存在客户端,每次请求都发回服务端,也就是下面要介绍的 基于 Token 的验证。
Token:
字面上来说就是令牌, 可以理解为古代的过关令牌。
验证流程如下:
- 客户端发送登录信息到服务器端;
- 服务器端进行判断正确,返回一个 token;
- 客户端收到后将其存储到 local storage,session storage 或者 cookie 中;(若放在 cookie 中 不能跨越,更好的方法是放在 HTTP 请求头的头信息 Authorization 字段中,或者放在 POST 请求中)
- 若客户端发送请求,则将其带上一并发到服务器端。
- 服务器端解码 JWT => 验证 token ,有效则处理请求。
- 用户登出, token 在客户端被销毁。
流程中提到了 JWT ( JSON Web Token ),其实是一种实现 token 的标准,其由三部分组成 Header(头部) + Payload (负载) + Signature (签名)以 xxx.xxx.xxx 的形式去呈现。
Header 是一个 JSON 对象
{
"alg": "HS256", // 表示签名的算法,默认是 HMAC SHA256(写成 HS256)
"typ": "JWT" // 表示Token的类型,JWT 令牌统一写为JWT
}
Payload(负载)
Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据
{
// 7个官方字段
"iss": "a.com", // issuer:签发人
"exp": "1d", // expiration time: 过期时间
"sub": "test", // subject: 主题
"aud": "xxx", // audience: 受众
"nbf": "xxx", // Not Before:生效时间
"iat": "xxx", // Issued At: 签发时间
"jti": "1111", // JWT ID:编号
// 可以定义私有字段
"name": "John Doe",
"admin": true
}
JWT 默认是不加密的, 因为对 JWT 中的 Payload 和 Signature 都会进行 base64 编码, 而 base64编码它是可以被翻译回原来的样子来的。它并不是一种加密过程。所以 任何人都可以读到。
Signature(签名)
Signature 是对前两部分的签名,防止数据被篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用Header里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
算出签名后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。
安全性:
- 发送JWT要使用HTTPS;不使用HTTPS发送的时候,JWT里不要写入秘密数据
- JWT的payload中要设置expire时间
参考文章: