JWT -- JSON Web Token

前言

做的项目一直都是用最经典session来实现用户认证,今天老大可能在重构H5模块发现session有问题,具体问题到时我再去看看。然后老大让我了解一下JWT,没听过这个东东,也是一脸懵逼,所以自己就在网上开始搜索相关知识,下面是一些网上找到的博客文章,进行收集整理,然后再添加自己的一些见解,具体的转载地址都在下面。

传统session认证流程

  1. 当用户使用用户名和密码登录之后, 服务器就会生成一个 session 文件, session 文件中保存着对这个用户的授权信息,这个文件可以储存在硬盘/内存/数据库中.
  2. 同时还要生成一个对应这个 session 文件的 sessionid, 通过 sessionid 就能够找到这个 session 文件.
  3. 然后将 sessionid 发送给客户端, 客户端就将 sessionid 保存起来, 保存的方式有很多种, 目前大多情况是通过 cookie 来保存 sessionid.
  4. 保存之后, 当客户机以后再向服务器发送请求的时候, 请求携带上 sessionid, 这样服务器收到 sessionid 之后,自己就会在服务区上查找对应的 session 文件, 如果查找成功, 就会得到该用户的授权信息, 从而完成一次授权.

session可能存在的问题

  1. 随着用户量的增加, 每个用户都需要在服务器上创建一个 session 文件, 这对服务器造成了压力;
  2. 对于服务器压力的分流问题, 如果一个用户的 session被存储在某台服务器上, 那么当这个用户访问服务器时,用户就只能在这台服务器上完成授权,其他的分流服务器无法进行对这种请求进行分流;
  3. 共享session 的问题, 当我们在一台服务器上成功登录, 如果我们想要另外的一台别的域名的服务器也能让用户不登录就能完成授权;
  4. CSRF 攻击,通过非法的攻击代码,在用户不知情的情况下携带Cookie信息访问服务器,获取用户隐私信息

JWT原理

大致流程:

  1. 客户端发送认证信息(一般就是用户名/密码), 向服务器发送请求;
  2. 服务器验证客户端的认证信息, 验证成功之后, 服务器向客户端返回一个加密的token(一般情况下就是一个字符串);
  3. 客户端存储(cookie, session, app 中都可以存储)这个 token, 在之后每次向服务器发送请求时, 都携带上这个 token;
  4. 服务器验证这个 token 的合法性, 只要验证通过, 服务器就认为该请求是一个合法的请求

其中服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样。

1
2
3
4
5
{
"姓名": "张三",
"角色": "管理员",
"到期时间": "2018年7月1日0点0分"
}

以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名(详见后文)。服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。

JWT 的数据结构

JWT实际上就是一个字符串,它由三部分组成,头部(Header)、载荷(Payload)与签名(Signature)
形如:

1
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0.rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM

头部 Header

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

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

alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT。
最后,将上面的 JSON 对象使用 Base64URL 算法(详见后文)转成字符串。

载荷 Payload

Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。

1
2
3
4
5
6
7
iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号

除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。

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

注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分
这个 JSON 对象也要使用 Base64URL 算法转成字符串。

签名 Signature

Signature 部分是对前两部分的签名,防止数据篡改
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

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

算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用”点”(.)分隔,就可以返回给用户。

JWT 的使用方式

客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。
此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。
Authorization: Bearer <token>
另一种做法是,跨域的时候,JWT 就放在 POST 请求的数据体里面。

JWT 的几个特点

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

以上就是对JWT资料的收集,有助于先对JWT有一个大概的认识和理解,后面公司项目版本迭代应该会用上JWT,到时会有实际操作,应该是使用支持laravel的一个插件–jwt-auth(Seaony:使用 Jwt-Auth 实现 API 用户认证以及无痛刷新访问令牌)。

以上主要参考

[3]: http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html