Session, Cookie, Token和JWT

priority
Updated
Sep 29, 2021 02:58 AM
date
Sep 29, 2021
type
Post
slug
session-cookie-jwt
Created
Dec 23, 2020 07:43 AM
status
Published
tags
Web
summary
session和cookie应该是刚入行的时候做网站就会接触的概念,很早之前整理过一次,最近在接手用户系统的需求时,处理sso相关的时候,又想到了这个。翻看之前整理的实在是太乱了,有些含混不清,基于当前的理解重新整理了一下,顺便补充了JWT的内容

session和cookie区别

  1. cookie其实是一种存储方式,其和local storage是等价的,只不过在现代浏览器赋予cookie一个特点:所有基于当前网站的请求,cookie会被自动带上,而不需要开发者额外的操作
  1. session是【会话】的意思,其描述的是一种状态,由于http服务是无状态的,而实际服务和用户的交互是要保持这个状态(例如:要知道用户是否登陆),所以服务端会存储这个状态,而这个被存储的状态,称之为session
  1. session保存在服务端,需要和客户端关联,采用的方式时候针对session分配一个session-id,返回给前端,前端负责存储,并在每次请求的时候带上session-id
    1. 而通常的实现是,前端也不存,后端直接设置session-id在cookie中,让浏览器自动带上,前端就不用处理了。
Q: 为什么需要session而不把状态存储在cookie中?
A: 因为cookie是可以被任意更改的,比如我session中记录了一个用户的id和其是否是vip,如果在cookie中记录这些信息,那用户可以轻易伪造其是否是vip了。所以在cookie中,只能记录一些加密信息,或者是非敏感信息。

token的诞生

token是【令牌】的意思,简单说,就是用户访问我们服务大门的钥匙。那既然有了上文的方案,将session-id存储在cookie,难道不够么?
答案是Yes,随着服务规模的扩大,移动互联网的兴起,产生了一些新问题:
  1. 一些设备,例如移动设备,不支持cookie和浏览器自动提交cookie的行为(现在很多框架对cookie提供了支持)
  1. 有时候需要在两个不同公司的服务之间进行请求,也是不支持cookie的
  1. 服务规模扩大,采用分布式服务之后,用户的session通常会储存在分布式缓存中,缓存压力增大,同时客户端要查询某些信息必须通过和服务端请求来实现
token是为了解决问题1和2产生的,生成一个令牌,约定传输方式就好。所以token是一个很通用的术语,其本身就是一串字符串,至于这段字符串如何传输、如何实现校验,由使用双方约定。
不过通常的传输方式是,将token放在请求的http-header中传输。当然你也跨域放在url、cookie、local storage中。
同时,token还可以解决CSRF跨站请求伪造问题。

JWT(Json Web Token)

如JWT的名字,JWT本身也是一个token。那么JWT解决了什么问题呢?
如上节所述,token其实是请求方和被请求方约定的,也就产生了各种各样的加密方式,header名称,JWT的目的之一,就是提出一个规范,来统一这个约定。
第二个目的,就是上节的问题3,数据存储在缓存中,缓存压力变大,客户端的请求也增加来;但是又不能存储在客户端,因为cookie会被篡改。如何解决呢?对数据进行签名防止篡改不就好了?JWT就是采用了这样的理念,将session的数据存储在客户端,并进行签名,通过签名校验数据是否正确。
实际的 JWT 如下:
notion image
它是一个很长的字符串,中间用点(.)分隔成三个部分。注意,JWT 内部是没有换行的,这里只是为了便于展示,将它写成了几行。
JWT 的三个部分依次如下。
Header(头部)Payload(负载)Signature(签名)
写成一行,就是下面的样子。
Header.Payload.Signature
notion image
下面依次介绍这三个部分。

3.1 Header

Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。
{
  "alg": "HS256",
  "typ": "JWT"
}
上面代码中,alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT
最后,将上面的 JSON 对象使用 Base64URL 算法(详见后文)转成字符串。

3.2 Payload

Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。
  1. iss (issuer):签发人
  1. exp (expiration time):过期时间
  1. sub (subject):主题
  1. aud (audience):受众
  1. nbf (Not Before):生效时间
  1. iat (Issued At):签发时间
  1. jti (JWT ID):编号
除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}
注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分
这个 JSON 对象也要使用 Base64URL 算法转成字符串。

3.3 Signature

Signature 部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。
 
既然JWT是一个token,其存储方式和cookie一样,推荐header存储和传输。另外,JWT中包含了一些可公开信息,例如记录用户注册时间、是否是vip等一些客户端常用数据,客户端可以直接访问而不需要频繁请求服务端获取这些信息,也减少了服务端存储session的压力。

实践分析

DXY的cookie实现中,也采用了JWT的理念:对原始数据签名,只不过由于历史包袱,token是存储在session中,token中包含了登陆信息、是否vip、状态等等信息,可以参考
Euvee当初的实现太乱了,只能说没有理解透JWT,生硬的集成了别人的代码。
复盘了一下,有亮点也有失败 Dec 23, 2020
  1. 鉴权必要集成spring security,加个spring的拦截器,配合自定义注解 or 接口方法也挺好的,简单实用
  1. JWT的使用完全没有必要加框架,自己谢谢好了
  1. 是不应该提供管理员编辑session的功能(太bug了这个)
  1. 是密码加密时候没有加盐,参考
 

© Song 2015 - 2021