什么是JWT
JSON Web Token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519),该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
JWT是目前最流行的跨域认证解决方案之一。
为什么使用JWT
在看为什么使用之前,我们必须要先了解之前我们是如何进行验证请求的。
Session认证
在 Session 认证方式中,用户登录后发给服务器,服务器在接收并验证发送过来的账号密码请求之后,就会把这个用户信息放入 Session 中,然后把 Session 存在服务器上,这样服务器就知道了这个用户的存在,当下一次用户访问的时候,就能认证了。
但因为我们知道http协议是一种无状态的协议,也就是说当下一次用户发送请求的时候,请求中没有任何信息能表明用户身份!也就是说不知道请求是谁发出来了,这样也就不能认证了。
所以就需要利用 Cookie 来管理 Session,即把 SessionID 放入 HTTP 响应中发给客户端,并保存在客户端,当客户端发送下一次请求的时候,就把这个 SessionID 一起发送回来,这样就能这次的请求是谁发出来的了。
扩展:Cookie 是由客户端(通常是浏览器)保存的小型文本信息,其内容是一系列的键值对,是由 HTTP 服务器设置并保存在浏览器上的信息。
Session认证的问题
内存开销大
: 我们知道 Session 是存在服务器上的,实际上为了加快认证的速度,我们一般都会放在内存中,这样当用户基数大的时候,内存的开销就会很大。当然也可以将 Session 存入到 Session 表或者是缓存(redis等)中,但是依旧会有这样的问题。安全性(CSRF)
: 因为是基于 Cookie 进行用户识别,如果 Cookie 被截获,用户就会很容易收到跨站请求伪造的攻击。分布式负载均衡
: 因为 Session 信息是被单个服务器所保存的,所以在分布式系统中就不能适用了。比如 Session 一开始是保存在 A 服务器上,但是下一次请求的时候,这个请求被服务器负载均衡转发到了 B 服务器,而 B 服务器则没有这个 Session 信息,所以就不能用过认证了。
JWT的优点
因为 JWT 是由服务端生成的,通过请求传给客户端(客户端可以以任意方式存放)。所以服务器不需要存储任何 JWT 信息。这样就能避免了上述 Session 的几个问题了。当然 JWT 还有其自身的一些优点。
轻量级
:JWT是非常轻量级的,传输的方式多样化,可以通过URL/POST参数/HTTP头部等方式传输。无状态/跨域认证
:token包含所有用于标识用户的信息,这消除了对会话状态的需要。如果我们使用负载均衡,我们依然可以将token传递给任何服务器,而不是存储在我们登录的同一台服务器上。安全性
:无需担心跨站请求伪造(CSRF)攻击。
JWT 组成
由三个部分组成:header.payload.signature
header
header
:包含了两个部分 typ 和 alg,分别是声明类型和JWT的加密算法。
1 | { |
经过 base64URL 加密之后得到 JWT 的第一部分信息:
1 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 |
payload
payload
:负载,存放有效信息的地方。这些有效信息包含三个部分:标准中注册的声明、公共的声明 和 私有的声明。
- iss:JWT 的签发者
- sub:JWT 所面向的用户
- aud:接收 JWT 的一方
- exp:JWT 的过期时间这个过期时间必须大于签发时间
- nbf:JWT 起作用的开始时间,即定义在什么时间之前,该JWT都是不可用的
- iat:JWT 的签发时间
- jti:JWT 的唯一身份标识,主要用来作为一次性 token,从而回避重放攻击。
除了官方字段,你还可以在这个部分定义私有字段:
1 | { |
经过 base64URL 加密之后得到 JWT 的第二部分信息:
1 | eyJzdWIiOiIxMjM0NTY3ODkwIiwibmlja25hbWUiOiJoenpseSIsInVzZXJuYW1lIjoiaHp6bHkiLCJzY29wZXMiOlsiYWRtaW4iLCJ1c2VyIl19 |
signature
signature
:是对前两部分的签名,防止数据篡改。由三个部分组成:header、payload 和 secret。其中 header 和 payload 都是加密后的字符串,secret就是一个字符串(密钥)。
1 | const signature = HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret); |
算出签名以后,把 header、payload、signature 三个部分拼成一个字符串,每个部分之间用”点”(.)分隔,就可以返回给客户端。
最终的jwt:
1 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmlja25hbWUiOiJoenpseSIsInVzZXJuYW1lIjoiaHp6bHkiLCJzY29wZXMiOlsiYWRtaW4iLCJ1c2VyIl19.sXaHGg9SWyRpl-rhiSBFuD01G4yE3Gmi5m-JD7u6YyI |
base64URL
面提到,header 和 payload 串型化的算法是 base64URL。这个算法跟 base64 算法基本类似,但有一些小的不同。
JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。base64 有三个字符+、/和=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-,/替换成_ 。这就是 Base64URL 算法。
JWT的使用方式
客户端收到服务器返回的 JWT,可以储存在 sessionStorage 或 localStorage 里面。
此后,客户端每次与服务器通信,都要带上这个 JWT。需要把它放在 HTTP 请求的头信息 Authorization 字段里面。
1 | Authorization: Bearer <token> |
另一种做法是,跨域的时候,JWT 就放在 POST 请求的数据体里面。