JSON Web Token(简称JWT)是目前最流行的跨域认证解决方案,本文介绍其原理和使用方法。
一、跨域认证问题
互联网服务离不开用户认证,一般流程如下:
用户将用户名和密码发送给服务器。
服务器验证成功后,保存当前会话中的相关数据,如用户角色,登录时间等。
服务器返回一个session_id给用户,并且写入到用户的Cookie中。
后续用户每次请求,session_id都会通过Cookie发送回服务器。
服务器收到session_id之后,找到预先保存的数据,从而知道用户的身份。这种模型的问题是扩展性差,单机的话是没有问题的,但如果是服务器集群或者跨域的面向服务架构,就需要session数据共享,各个服务器都要能够读取session。比如网站A和网站B是同一个公司的相关服务,现在要求用户一旦登录了其中一个网站,在访问另外一个网站的时候,也自动登录了。如何实现呢?一种方案就是将session数据持久化,写入数据库或者其他持久层,各个服务在接收到请求之后,再向持久层请求数据。这种方案的好处是架构清晰,但是缺点是工作量比较大,另外如果持久层出现故障,就会出现单点故障。还有一种方案就是服务器干脆不保存session数据,所有的数据都保存在客户端,每次请求的时候再发回服务器。 JWT就是这种解决方案的代表。
二、JWT的原理
JWT的原理是,服务器认证成功后,会生成一个JSON对象返回给用户,就像这样:
{"name": "Alice", "role": "admin", "expiration time": "0:00, July 1, 2024"}
之后用户跟服务端通信的时候,需要返回这个 JSON 对象,服务端完全根据这个对象来判断用户的身份。为了防止用户篡改数据,服务端在生成这个对象的时候会加上签名(后面会详细介绍)。
服务端不再保存任何 session 数据。也就是说,服务端变成了无状态的,这样更容易实现扩展。
三、JWT的数据结构
实际的JWT大概是这样的:
它是一个很长的字符串,被点(.)分成三部分。注意 JWT 内部没有换行符,这里为了方便展示,分成几行写。JWT
的三个部分如下:
标头(Header)
有效载荷(有效载荷)
签名(Signature)写在一行中,看起来像这样:Header.Payload.Signature
下面依次介绍这三个部分。
3.1 标头
Header 部分是一个 JSON 对象,描述了 JWT 的元数据,通常是这样的:
{"alg": "HS256", "typ": "JWT"}
上述代码中,alg 属性表示签名算法(algorithm),默认为 HMAC SHA256(写为 HS256);typ 属性表示此 token 的类型,JWT token 统一写为 JWT。
最后将上述 JSON 对象利用 Base64URL 算法(后面会详细介绍)转化为字符串。
3.2 有效载荷
Payload部分也是一个JSON对象,用来存储真正需要传输的数据。JWT定义了7个官方字段供选择。
iss(发行人):发行人
exp(到期时间):到期时间
sub(主题):主题
aud(观众):观众
nbf(Not Before):生效时间
iat(Issued At):签发时间
jti(JWT ID):序列号除了官方定义的字段外,你还可以在此部分定义私有字段,以下是示例:
{"sub": "1234567890", "name": "John Doe", "admin": true}
注意,JWT 默认是不加密的,任何人都可以读取,所以这部分不要放机密信息,
这个 JSON 对象也需要用 Base64URL 算法转换成字符串。
3.3 签名
Signature 部分是对前两部分进行签名,防止数据被篡改。
首先需要指定一个秘钥(secret),这个密钥只有服务端知道,不能泄露给用户。然后使用 Header 中指定的签名算法(默认为 HMAC SHA256)按照以下公式生成签名:
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
计算完签名之后,将Header、Payload、Signature部分组合成一个字符串,各部分之间用“点”(.)分隔,然后返回给用户。
3.4 Base64URL
前面提到过,Header 和 Payload 的序列化算法是 Base64URL,这个算法和 Base64 算法基本类似,但是还是有一些小区别的,
JWT 作为 token,有些情况下可能会放在 URL 中(比如 http://api.example.com/?token=xxx)。Base64 中有三个字符 +、/、=,在 URL 中有特殊含义,需要替换:= 省略,+ 替换为 -,/ 替换为 _。这就是 Base64URL 算法。
四、JWT 的使用
客户端在收到服务端返回的 JWT 之后,可以将其存放在 Cookie 中,也可以存放在 localStorage 中。
此后,客户端每次与服务端通信时,都需要带上这个 JWT。你可以将其放在 Cookie 中,然后自动发送,但是这样无法跨域。所以更好的办法是将其放在 HTTP 请求头的 Authorization 字段中。
Authorization: Bearer
另一种方式是,跨域时,将 JWT 放在 POST 请求的数据体中。
五、JWT的几个特点
(1)JWT默认是不加密的,但是可以加密,生成原始Token后可以用秘钥再次加密。
(2)在 JWT 未加密的情况下,秘密数据无法写入 JWT 中。
(3)JWT 不仅可以用来认证,还可以用于信息交换,有效使用 JWT 可以减少服务器查询数据库的次数。
(4)JWT 最大的缺点是由于服务端不保存会话状态,所以无法在使用过程中撤销某个 token,也无法改变 token 的权限。也就是说,一旦签发了一个 JWT,它将一直有效,直到过期,除非服务端部署额外的逻辑。
(5)JWT 本身包含认证信息,一旦泄露,任何人都可以获得 token 的所有权限。为了减少盗窃,JWT 的有效期应该设置的比较短,对于一些比较重要的权限,使用时应该让用户再次认证。
(6)为了减少盗窃,JWT不应该使用HTTP协议明文传输,而应该使用HTTPS协议传输。

