JWT 入门

1.用户认证的状况

在这一部分我们会介绍HTTP协议的无状态性,以及目前常见的用户认证方式。

1.1 HTTP 协议的无状态性

我们先来说说什么HTTP协议,它是HyperText Transfer Prototal(超文本传输协议)的简写。HTTP协议是互联网数据通讯的基础协议。
HTTP协议的无状态性,对现在的我们来讲有些费解,但是在当时都是那么的顺其自然。HTTP协议最初的设计目的是提供一种发布和接受HTML页面的方法,主要用于百科全书,在线文档,教程,新闻等等,在HTTP/0.9协议种更是只支持GET请求。简单地说就是获取信息,不需要认证授权,不要跟踪用户行为,两次请求之间也没有关联。因此HTTP协议的无状态性可以说是非常合理。

1.2 基于Session的交互式网站

随着互联网的发展,HTTP协议的无状态性的缺点也变得更加突出,严重地阻碍了交互式 Web 应用的发展。比如一个网站的信息不是公开的,只对会员开放,这就要求用户在访问页面的时候必须提供用户名和密码,因为HTTP协议的无状态性,就要每访问一个页面就要输入一次用户名密码,用户一定会疯掉。因此session的概念就应运而生了。那么用户放文网站的流程变成这样:

  1. 用户输入网站的某个页面
  2. 服务器判断用户尚未登录(没有提供session_id),跳转到登录页面。
  3. 用户输入用户名和密码
  4. 服务器端通过验证以后,给当前会话创建一个session_id,并往当前会话中保存用户信息。服务器端向浏览器返回一个session_id,并写入用户的Cookie(一般都是通过Cookie实现)。
  5. 用户再浏览页面时,session_id作为Cookie自动传递到服务器。
  6. 服务器在接受到session_id之后,获取此会话中的用户信息,直接返回页面内容。

一般我们会将会话保存到数据库或 Redis 等其他持久化服务中,为了以下两个原因:

  1. 如果将会话保存在内存中,那么服务器因为意外宕机或发布新版本重启时,导致会话丢失,进而导致用户需要重新登录。
  2. 为了应对高并发请求,我们一般首先水平扩展应用服务器,这时候就要求会话在多个服务器之间共享,持久化最好的方案。

这样会导致工程复杂以及持久层的单点故障。有什么方法避免这种问题吗?

2 JWT 的出现给我们带来另一种体验

2.1. JWT 是什么?

JWT发音jot([jät]),是JSON Web Token的简写。它是一种在两个网络应用主体之间传递声明的开放标准(RFC7519)。

JWT的原理是服务器完成认证之后将用户信息转换成一个字符串(以下称为token)以后返回给客户端,以后客户端发送请求时都会带上token,服务器直接从token中获取用户信息。你可能会问怎么防止用户信息被篡改呢?答案是给token中还包含签名。

JWT内容分为三个部分:头部Header, 负载Payload和签名Verify Signature。在https://jwt.io/网站中,如果你修改左侧token,网站即时解析token并将结束输出在右侧(如果成功的话),你修改了右侧明文,网站即时转换成token并更新在左侧。

2.2 头部Header

JWT头部是一个JSON字符串,描述JWT的元数据,比如alg字段表示采用哪种加密算法,typ字段表示令牌类型,JWT令牌统一为’JWT’。

1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}

以上JSON字符串经过Base64URL算法处理以后就是JWT头部的最终内容了。

2.3 负载Payload

JWT负载部分也是一个JSON字符串,用来保存认证信息。JWT 协议中定义了以下 7 个官方字段,建议使用但不强制。

1
2
3
4
5
6
7
8
9
{
"jti": "", // 编号 JWT ID,可以用来防止JWT重放攻击
"iss": "", // 签发人 issuer
"sub": "", // 主题 subject
"aud": "", // 受众 audience
"iat": 1540459625186, // 签发时间 Issued At
"nbf": 1540459625186, // 生效时间 Not Before
"exp": 1540459631234 // 过期时间 expiration,必须大于签发时间
}

除了官方字段以外,还可以存放其他自定义字段,比如

1
2
3
{
"userId": "1605004"
}

和头部一样,以上JSON字符串经过Base64URL算法处理以后就是JWT头部的最终内容了。
特别注意,因为负载可以被客户端解析,因此不能存放诸如密码等敏感信息。

2.4 签名Verify Signature

签名Verify Signature是对前面两部分:头部Header和负载Payload的签名。

1
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), SECRET)

其中SECRET是秘钥。

2.5 Base64UrlEncode

前面提到数次的Base64Url算法是增强后的Base64算法,为什么不是直接使用Base64算法呢?其实JWT只是一般情况下通过Authorization请求头发送,而某些时间需要放在URL后面比如http://example.com/page?token=xxx,而Base64算法中的三个字符+, /, =在 URL 中有特殊含义,因此需要特殊,将+替换成-/替换成_=删除,这样就形成了Base64Url算法。

2.6 JWT 的传输方式

在上一小节中我们提供,JWT一般通过Authorization请求头发送,其结构是

1
Authorization: Bearer <token>

也可以放在URL后面作为参数传输,也可以放在Cookie中传输。

2.7 JWT 的特点

  1. JWT默认是不加密的,但是也可加加密。生成原始token以后,可以在用秘钥加密一次。
  2. JWT默认是不加密的,因此不能存放密码等敏感信息。
  3. JWT不仅可以用于认证,也可以用于交换信息。有效使用JWT可以减少数据库查询次数。
  4. JWT最大缺点是由于服务器端不保存session状态,因此无法在使用过程中废止token,或者更改token的权限。也就是说,一旦JWT签发以后,在到期之前就一直有效,除非服务器端有额外的逻辑处理。
  5. JWT本身包含了认证信息,一旦泄露,所有人都可以获得该令牌,为了减少盗用,JWT有效期应该设置的比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
  6. 为了减少盗用,JWT不应该使用HTTP协议明码传输,要使用HTTPS协议。

3. 参考资料

  1. RFC7519
  2. JSON Web Token 入门教程
  3. https://jwt.io/