JWT 英文名是 Json Web Token,是一种用于通信双方之间传递安全信息的简洁的、URL安全的表述性声明规范,经常用于跨域身份验证。
JWT 以 JSON 对象的形式安全地传递信息。因为存在数字签名,因此所传递的信息是安全的。
为什么需要JWT ?
在了解 JWT 之前我们先来看一下为什么需要 JWT
传统方式的局限性
传统的登录方式:用户端输入用户名密码,服务器端校验通过,根据用户信息生成一个 token,将 token 和 user_id 存到数据库或 session会话 中,并将 token 返回给前端,存入cookie,浏览器每次请求都会带上 cookie,服务端根据cookie中的 token 查询用户(如user_id),验证用户有效性。
传统方式存在以下弊端:
① 如果出现XSS(跨站脚本攻击)漏洞,由于 cookie 可以被 js 读取,XSS 漏洞会导致用户 token 被泄露。解决方案:
- 设置 httpOnly。这样的话 cookie 将不会被 js 读取,浏览器会自动将它加到请求头信息中,但是带来了新的问题,很容易被XSRF(跨站请求伪造)攻击,因为只要当前浏览器开着,另一个界面可以很容易地跨站请求这个界面的内容,因为cookie会被默认发送出去。
- 设置 secure。这样 cookie 就只能通过 https 传输,可以过滤掉一些使用 http 协议请求的 XSS 注入。
② 将验证信息存到数据库中,每次验证的时候,都需要去数据库中查询,增加了数据库的查询和存储开销。
③ 如果将 token 存到 session 中,也会增加服务器的存储压力。
④ 无法横向扩展。在服务器集群或者面向服务且跨域的结构中,需要数据库来保存 Session 会话,实现多个服务器之间的会话数据共享。在单点登录中为了解决共享 Session 问题,其中一种方法是持久化 Session 数据,但是缺点也非常明显,就是架构修改很困难,验证逻辑需要重写,并且整体依赖于数据库,如果存储 Session 会话的数据库挂掉那么整个身份认证就无法使用,进而导致系统无法登录。
JWT的优势
① 可以通过 URL POST 参数或者 http header 中发送,数据量小,传输速度快。
② 自包含:负载中包含了用户所需要的所有信息,避免多次查询数据库。原来是先拿着 token 去找 user_id,现在是,JWT 的有效载荷中就包含了 user_id 等信息。这样就不需要服务器端存储 Session 信息。
JWT组成
说白了 JWT 就是一段数据,我理解的是它既代表了一种思想,又提供了具体的解决方案
JWT 是由三段字符串和两个 . 组成,类似于这样:xxxxxx.yyyyyy.zzzzzz,每个字符串代表了不同的功能。
① JWT 头 Header
JWT 头描述了 JWT 元数据,是一个 JSON 对象,它的格式如下:
json{"alg":"HS256",
"typ":"JWT"}
其中 “alg” 属性表示签名所使用的算法,JWT 签名默认的算法为 HMAC SHA256 ,
“typ” 属性表示令牌类型,这里就是 JWT。
② 有效载荷Payload
有效载荷是 JWT 的主体,也是个 JSON 对象。有效载荷包含三个部分:
-
标准注册声明。它一般包括以下内容:
iss:jwt的签发者/发行人
sub:主题
aud:接收方
nbf:jwt生效时间
exp:jwt过期时间
iat:签发时间
jti:jwt唯一身份标识,可以避免重放攻击
-
公共声明。
可以在公共声明添加任何信息,我们一般会在里面添加用户信息和业务信息,但是不建议添加敏感信息,因为公共声明部分可以在客户端解密。
-
私有声明。
私有声明是服务器和客户端共同定义的声明,同样这里不建议添加敏感信息。
下面这个代码段就是定义了一个有效载荷:
json{"exp":"201909181230",
"role":"admin",
"isShow":false}
③ 哈希签名 Signature
哈希签名的算法主要是确保数据不会被篡改。它主要是对前面所讲的两个部分进行编码,通过 JWT 头定义的算法(“alg”:“HS256”)生成哈希。
③Signature = HMACSHA256(
base64UrlEncode(①Header) +
"." +
base64UrlEncode(②Payload),
password
)
最终
JWT = Header.Payload.Signature
JWT 使用过程
① Client 通过账号密码登录,Server 验证通过后,将用户的 id 及其他非敏感信息作为JWT Payload,将其与头部分别进行 base64 编码后签名,生成 JWT,然后将 JWT返 回给 Client。
② Client 将收到的 JWT 保存到 localStorage 或者 sessionStorage 中,跳转到登录页或请求 API,将 JWT 发送给 Server;退出登录时,Client 删除保存的 JWT 信息即可。
③ Server端进行过滤器拦截请求(验证JWT的有效性),若通过,则进行业务逻辑操作并想客户端返回数据,若不通过,则返回错误信息, Client 提示错误,跳转回登录页面。
服务端验证 JWT
服务端验证 JWT 的过程就是签名的过程,如何判判断客户端发送的 JWT 就是我刚才给你的那个?关键就在于存储在服务端的密码
JWT 最终是由 Header、Payload、Signature 三部分组成,其中签名又是由 Header 和 Payload 的编码以及密码组成,当客户端发送 JWT 过来,服务端将 Header 和 Payload 以及存储在服务端的密码(一般是加密过的,而不是明文)重新进行签名,然后将此签名与客户端发送的 JWT 中的签名进行比对,如果相同则认为客户端发送过来的这个 JWT 就是我刚才给你发送的那个,从而验证客户端身份。
这里的关键就在于存储在服务端的密码,因为密码只有服务端有,因此就保证了签名的唯一性。而从客户端发送过来的 JWT 中是无法得到的密码的,因为密码通过哈希被包含在了签名中,而哈希是不可逆的,这就避免了JWT 在传输过程中的密码泄漏问题。
JWT 应用场景
JWT 通常用来设计用户认证和授权系统,还有我们通常说的单点登录等。
JWT注意事项
在使用 JWT 时需要注意以下事项:
JWT 默认不加密,如果要写入敏感信息必须加密,可以用生成的原始令牌再次对内容进行加密;
JWT只适合向 web 端传递一些非敏感信息,因为 base64 编码是可逆的,很容易被破解;
JWT 无法使服务器保存会话状态,当令牌生成后在有效期内无法取消也不能更改;
JWT 包含认证信息,如果泄露了,任何人都可以获得令牌所有的权限;因此 JWT 有效期不能太长,对于重要操作每次请求都必须进行身份验证。
JWT本质只能是能够避免了服务端进行存储token的弊端,服务端通过采用数字签名的方式避免只进行验证而不存储,但是JWT本身的明文传输仍然是不安全,JWT可以在SSL的安全通道下进行传输。JWT最大的作用也是认证。