Skip to content

什么是 JWT?

JWT (JSON Web Token) :目前最流行的跨域认证解决方案,是一种基于 Token 的认证授权机制。

JWT 自身包含了身份验证所需要的所有信息,因此,我们的服务器不需要存储 Session 信息。这显然增加了系统的可用性和伸缩性,大大减轻了服务端的压力。

JWT 本质上就是一组字串(如xxxxx.yyyyy.zzzzz),通过(.)切分成三个为 Base64 编码的部分:

  • Header : 描述 JWT 的元数据,定义了生成签名的算法以及 Token 的类型。

    • typ(Type):令牌类型,也就是 JWT。
    • alg(Algorithm):签名算法,比如 HS256。
  • Payload : 用来存放实际需要传递的数据:默认不加密,所以不建议存放私密信息(也可以服务端进行加密)

  • Signature(签名):服务器通过 Payload、Header 和一个密钥(Secret)使用 Header 里面指定的签名算法(默认是 HMAC SHA256)生成。

你可以在 jwt.io 这个网站上对其 JWT 进行解码,解码之后得到的就是 Header、Payload、Signature 这三部分。

Header 和 Payload 都是 JSON 格式的数据,Signature 由 Payload、Header 和 Secret(密钥)通过特定的计算公式和加密算法得到。

如何基于 JWT 进行身份验证?

在基于 JWT 进行身份验证的的应用程序中,服务器通过 Payload、Header 和 Secret(密钥)创建 JWT 并将 JWT 发送给客户端。客户端接收到 JWT 之后,会将其保存在 Cookie 或者 localStorage 里面,以后客户端发出的所有请求都会携带这个令牌。

简化后的步骤如下:

  1. 用户登陆系统成功。服务端会返回已经签名的 Token,也就是 JWT。
  2. 用户以后每次向后端发请求都在 Header 中带上这个 JWT 。
  3. 服务端检查 JWT 并从中获取用户相关信息。

两点建议:

  1. 建议将 JWT 存放在 localStorage 中,放在 Cookie 中会有 CSRF 风险。
  2. 请求服务端并携带 JWT 的常见做法是将其放在 HTTP Header 的 Authorization 字段中(Authorization: Bearer Token)。

JWT优势

  • 无状态
    • JWT 自身包含了身份验证所需要的所有信息,因此,我们的服务器不需要存储 Session 信息。这显然增加了系统的可用性和伸缩性,大大减轻了服务端的压力。
    • 我们想要在 JWT 有效期内废弃一个 JWT 或者更改它的权限的话,并不会立即生效,当用户 Logout 的话,JWT 也还有效。
      • 可以将JWT存入Redis中,避免这个问题,但是违背了无状态,但是最有效
      • 或者设置有效期较短,但是体验不好
      • JWT 续签问题也可以通过Redis实现(其实这些直接使用普通的Token生成配合Redis即可)
  • 有效避免了 CSRF 攻击
    • JWT 在前端一般选择存放在 localStorage 中,不会涉及 Cookie。因此,即使你点击了非法链接发送了请求到服务端,这个非法请求也是不会携带 JWT 的,所以这个请求将是非法的。
    • 总结来说就一句话:使用 JWT 进行身份验证不需要依赖 Cookie ,因此可以避免 CSRF 攻击。
    • 但是无法避免 XSS (Cross-Site Scripting,跨站脚本攻击)
      • 在 Spring 项目中,我们一般是通过创建 XSS 过滤器来实现的。
  • 适合移动端应用
    • 移动端网络不稳定,用SessionId成本较高,且平台多,兼容性容易成为问题,
  • 单点登录友好

如何防止 JWT 被篡改?

JWT 安全的核心在于签名,签名安全的核心在密钥

服务端拿到 JWT 之后,会解析出其中包含的 Header、Payload 以及 Signature 。服务端会根据 Header、Payload、密钥再次生成一个 Signature。拿新生成的 Signature 和 JWT 中的 Signature 作对比,如果一样就说明 Header 和 Payload 没有被修改。

黑客在没有密钥的情况下没办法同时篡改 Signature、Header、Payload。

所以密钥一定保管好,一定不要泄露出去。

CRSF

CSRF(Cross Site Request Forgery) 一般被翻译为 跨站请求伪造,属于网络攻击领域范围。相比于 SQL 脚本注入、XSS 等安全攻击方式,CSRF 的知名度并没有它们高。但是,它的确是我们开发系统时必须要考虑的安全隐患。就连业内技术标杆 Google 的产品 Gmail 也曾在 2007 年的时候爆出过 CSRF 漏洞,这给 Gmail 的用户造成了很大的损失。

那么究竟什么是跨站请求伪造呢? 简单来说就是用你的身份去做一些不好的事情(发送一些对你不友好的请求比如恶意转账)。

举个简单的例子:小壮登录了某网上银行,他来到了网上银行的帖子区,看到一个帖子下面有一个链接写着“科学理财,年盈利率过万”,小壮好奇的点开了这个链接,结果发现自己的账户少了 10000 元。这是这么回事呢?原来黑客在链接中藏了一个请求,这个请求直接利用小壮的身份给银行发送了一个转账请求,也就是通过你的 Cookie 向银行发出请求。

html
<a src="http://www.mybank.com/Transfer?bankId=11&money=10000"
  >科学理财,年盈利率过万</a
>

CSRF 攻击需要依赖 Cookie ,Session 认证中 Cookie 中的 SessionID 是由浏览器发送到服务端的,只要发出请求,Cookie 就会被携带。借助这个特性,即使黑客无法获取你的 SessionID,只要让你误点攻击链接,就可以达到攻击效果。

另外,并不是必须点击链接才可以达到攻击效果,很多时候,只要你打开了某个页面,CSRF 攻击就会发生。

html
<img src="http://www.mybank.com/Transfer?bankId=11&money=10000" />

如何加强 JWT 的安全性?

  1. 使用安全系数高的加密算法。
  2. 使用成熟的开源库,没必要造轮子。
  3. JWT 存放在 localStorage 中而不是 Cookie 中,避免 CSRF 风险。
  4. 一定不要将隐私信息存放在 Payload 当中。
  5. 密钥一定保管好,一定不要泄露出去。JWT 安全的核心在于签名,签名安全的核心在密钥。
  6. Payload 要加入 exp (JWT 的过期时间),永久有效的 JWT 不合理。并且,JWT 的过期时间不易过长。
  7. ……

正在精进