使用JWT来保证API安全

JWT

why JWT

现在,前后端分离和 RESTful API 越来越火热,当后台渐渐开始只负责为客户端提供 API 接口之后,身份校验和接口安全成了难题。在传统的开发模式下,使用 cookie-session 可以保证接口安全,在没有登录的情况下访问关键数据会跳转到登录界面或者请求失败。而使用 REStful API 之后,cookie-session 存在以下 3 个问题:

  • 客户端除了浏览器,可能还包括手机端 APP,对于手机端而言,管理 cookie 是一件麻烦的事情。
  • RESTful 风格的 API 不建议使用 cookie。
  • cookie 本身有一个缺陷,不能跨域。

正是存在上面的几个缺陷,现在 API 开始使用 JWT 代替 cookie-session 来做身份验证。

what JWT

JWT 全称 JSON Web Token。本质上 JWT 是一串 token 字符串。客户端登录之后,服务端返回一串 token 给客户端,之后每次客户端请求 API 接口都需要携带该 token 进行身份校验。JWT 由三个部分组成:头部(header)、载荷(payload)、签名(signature)。这三个部分使用 . 连接在一起就是一个完整的 JWT。所以,一个完整的 JWT 应该类似下面这种形式:

xxxxxx.yyyyy.zzzzz

header

header 是一个 json 数据,用于描述 JWT 的基本信息。一般要由两个部分组成:

  • alg
  • typ

alg 代表的是加密所使用的算法(后面会提到加密数据),typ 表示该 token 是什么类型的。这里 typ 自然是 JWT。

{
    'alg':'HS256',
    'typ':'JWT'
}

一个完整的 header 信息。最后使用 Base64 对 header 进行编码,得到 JWT 的第一部分。

payload

payload 是 JWT 存储信息的部分。payload 也是一个 json 数据,每一个 json 的 key-value 称为一个声明。

payload 有两种类型的声明:标准声明和自定义声明。

标准声明一共有 6 个,其名称和对应含义如下:

  • iss : JWT 的签发者。
  • iat : JWT 的签发时间,是一个 unix 时间戳。
  • exp : JWT 的过期时间,是一个 unxi 时间戳。
  • aud : 接受 JWT 的一方。
  • sub : JWT 所面向的用户。
  • jti : 唯一标识一个 JWT。

自定义声明为用户自己定义的 key-value,可以用来存储一些简单的基本信息。考虑到性能,不应该在 payload 中定义太多自定义声明。

定义一个 payload :

{
    "iss": "jaychen",
    "iat": 1441593502,
    "exp": 1441594722,
    "aud": "jaychen.cc",
    "sub": "chenjiayaooo@gmail.com",
    "jti:" "xxxxxxxx",

    "user_id": "1",
    "username": "jaychen"
}

上面这个 payload 中,idusername 为自定义声明。

有了 payload 只有,将该 payload 进行 Base64 加密,得到一串字符串之后,用 . 把 header 和 payload 连接起来。

signature

将 header 和 payload 两个部分连接起来之后,得到的字符串类似下面

xxxxx.yyyyy

接着,使用 header.alg 定义的加密算法对 hader.payload 的字符串进行加密,并且加密的时候应该有一个密钥。加密之后,得到一串加密字符串,最后把这串加密字符串也是用 . 拼接在 header.payload 后面,形成完整的 JWT。

这里签名的目的是为了保证 payload 数据的完整性。如果 JWT 在传输过程中被第三方劫持,中间人对 header.payload 进行修改,并且使用自己的密钥重新签名。服务端收到中间人修改过的 JWT,使用自己的密钥对 header.payload 进行再次加密,由于中间人和服务端使用的是不同的密钥签名,所以服务端再次加密的结果肯定和中间人加密的结果不一致,由此可以断定该 JWT 被恶意篡改。

基于 JWT 的身份验证

现在已经明白了 JWT 的生成过程,现在来梳理下 JWT 的使用流程。

  • 首次登陆系统,向服务端发送 username&&password 进行登录。
  • 服务端验证 username&&password,验证合法为客户端生成一串 JWT,这里在 payload 中可以自定义声明 user_id,username 等字段用来保存信息。
  • 客户端收到服务端的 JWT 字符串,自行保存。后续需要请求 API 都要携带该 JWT 到服务端进行身份校验。
  • 服务端收到客户端的 API 请求,先获取 JWT 信息,通过签名判断 JWT 的合法性,如果合法,返回数据。

JWT 的优点和注意事项

注意事项

上面生成 JWT 的过程中使用了 Base64 的加密算法对 payload 进行加密,Base64 是一种可逆的加密算法,这意味着其他人可以轻易的从加密结果中得到加密之前的信息,所以这注定了 payload 中不能保存密码之类的敏感信息。

优点

回顾上面生成 JWT 的步骤,payload 中我们保存了 user_idusername 这样的信息。在传统的 cookie-session 中,这些数据是服务端在 session 中维护的。JWT 把之前需要在服务端维护的 session 数据转移到客户端,使得服务端的压力小了很多。

JWT 本质只是一串字符串,所以可以无限制的使用各种姿势传递给服务端:当做 get 参数拼接在 URL 中、添加到 header 头部中、当做 post 参数传递。。。

如果客户端是手机 APP 等非浏览器客户端,那么使用 JWT 就可以免去对 cookie 的管理。

本文首发于:https://jaychen.cc
作者:jaychen

推荐阅读更多精彩内容