JWT (Json Web Token)

JWT (Json Web Token)

背景

互联网服务离不开用户认证,。由于http是无状态的,对事务处理没有记忆能力。每次的请求都是独立的,它的执行情况和结果与前面的请求和之后的请求是无直接关系的。意味着我们通过浏览器输入账户密码登录某个用户,服务端验证通过后。浏览器下一个请求继续访问服务器时,并不知道当前的用户是谁,是否验证通过,除非每次请求都携带账户密码进行验证(HTTP Basic Auth)。而这种方式有把用户名密码暴露给第三方客户端的风险,在生产环境下被使用的越来越少。

认证机制

基于Session+Cookie的认证机制

在传统的用户登录系统中,一般采用Session + Cookie的模式来进行用户验证:

  1. 客户端向服务器发送用户名和密码;

  2. 服务器验证通过后,生成Session信息,来保存用户相关信息;

  3. 随即服务器将Session的session_id携带在用户请求的回复中,客户端将其写入Cookie中;

  4. 随后客户端的每一次请求,都会通过Cookie将session_id传回服务器;

  5. 服务器通过收到的session_id,找到对应的session信息,从而获取用户相关信息。

当然这种模式也存在着一些问题:

  • session是存储在服务器上的,会占用少了内存,如果网站用户非常多的话,一方面服务器内存压力大,另一方面大量session的存取也会影响服务器的性能。

  • session是保存在当前服务器上的,如果是服务器集群,或者是跨域的服务导向架构,就必须要求seesion数据共享。

  • session是基于cookie进行识别的,容易被CSRF跨站请求伪造拦截。

基于Token的认证机制

基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息:

  1. 客户端向服务器发送用户名密码;

  2. 服务器对用户名密码进行验证,验证通过返回一个签名过的token;

  3. 客户端收到返回的token并保存;

  4. 后续的每次的去请求中,客户端会将token作为http header发送给服务器;

  5. 服务器只需验证token是否有效。

那么这种机制有什么好处呢?

  • 支持跨域访问: Cookie是不允许垮域访问的,这一点对Token机制是不存在的,前提是传输的用户认证信息通过HTTP头传输.

  • 无状态(也称:服务端可扩展性):Token机制在服务端不需要存储session信息,因为Token 自身包含了所有登录用户的信息,只需要在客户端的cookie或本地介质存储状态信息.

  • 更适用CDN: 可以通过内容分发网络请求你服务端的所有资料(如:javascript,HTML,图片等),而你的服务端只要提供API即可.

  • 去耦: 不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在你的API被调用的时候,你可以进行Token生成调用即可.

  • 更适用于移动应用: 当你的客户端是一个原生平台(iOS, Android,Windows 8等)时,Cookie是不被支持的(你需要通过Cookie容器进行处理),这时采用Token认证机制就会简单得多。

  • CSRF:因为不再依赖于Cookie,所以你就不需要考虑对CSRF(跨站请求伪造)的防范。

  • 基于标准化:你的API可以采用标准化的 JSON Web Token (JWT). 这个标准已经存在多个后端库(.NET, Ruby, Java,Python, PHP)和多家公司的支持(如:Firebase,Google, Microsoft).

JWT

什么是JWT

JWT就是建立在基于Token的认证机制上的一种规范。JWT (Json Web Token)是一种基于JSON格式的开放标准(RFC 7519),它定义了一种简洁的、自包含的用于通信双方之间以JSON对象的形式安全传递信息的方法。JWT常用于身份提供者和服务提供者之间的身份验证。例如用户登录验证。

JWT原理

JWT的原理是,服务器认证之后,返回给客户端的token是一个JSON对象,就像这样:

1
2
3
4
5
{
"name": "admin",
"role": "administrator",
"expiry”: "2018-10-10 18:00"
}

之后,客户端发送给服务器的请求,都要携带这个json对象的token。服务器依靠token完成用户认证。

为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名。

JWT的组成

实际上的JWT编码后的样子是:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiend6IiwiYWdlIjoiMTgifQ.UQmqAUhUrpDVV2ST7mZKyLTomVfg7sYkEjmdDI5XF8Q

它是一个很长的字符串,中间由点(.)分隔成三个部分:

  1. Header(头部);
  2. Payload(负载);
  3. Signature(签名);

Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子:

1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}
  • alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);
  • typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT

    最后,将 JSON 对象使用 Base64URL 算法转成字符串,变成JWT字符串的Header部分。

Payload

Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据,包含三个部分:

  1. 标准中注册的声明 (建议但不强制使用):

    • iss (issuer):签发人
    • exp (expiration time):过期时间
    • sub (subject):主题
    • aud (audience):受众
    • nbf (Not Before):生效时间
    • iat (Issued At):签发时间
    • jti (JWT ID):编号
  2. 公共的声明。公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.;
  3. 私有的声明。私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

定义一个payload:

1
2
3
4
5
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}

通用将其进行Base64URL加密,得到JWT的第二部分。

Signature

Signature 部分是对前两部分的签名,防止数据篡改。

首先,需要指定一个密钥(secret)。secret是保存在服务器端的(配置文件里),jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。

然后使用Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名:

1
2
3
4
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)

也就是jwt的第三个部分。

最后,将把 Header、Payload、Signature 三个部分拼成一个字符串组成,每个部分之间用”点”(.)分隔,组成完整的jwt,就可以返回给客户端了。

JWT 的使用方式

客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。

此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。

1
Authorization: Bearer <token>

另一种做法是,跨域的时候,JWT 就放在 POST 请求的数据体里面。

JWT优点

  • 因为json的通用性,所以JWT是可以进行跨语言支持的。

  • 因为有了payload部分,所以JWT可以在自定义一些参数,非敏感信息。

  • 便于传输,JWT的构成非常简单,字节占用很小,所以它是非常便于传输的。

  • JWT不需要在服务端保存会话信息, 易于应用的扩展。

  • JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。

JWT缺点

  • JWT 默认是不加密,不能将私密信息写入JWT。

  • 由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。

  • JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。

  • 为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,建议使用 HTTPS 协议传输。