关于java:八幅漫画理解使用-JWT-设计的单点登录系统

5次阅读

共计 4267 个字符,预计需要花费 11 分钟才能阅读完成。

JSON Web Token(JWT)是一个十分笨重的标准。这个标准容许咱们应用 JWT 在用户和服务器之间传递安全可靠的信息。

让咱们来假想一下一个场景。在 A 用户关注了 B 用户的时候,零碎发邮件给 B 用户,并且附有一个链接“点此关注 A 用户”。链接的地址能够是这样的

https://your.awesome-app.com/make-friend/?from_user=B&target_user=A

下面的 URL 次要通过 URL 来形容这个当然这样做有一个弊病,那就是要求用户 B 用户是肯定要先登录的。可不可以简化这个流程,让 B 用户不必登录就能够实现这个操作。JWT 就容许咱们做到这点。

JWT 的组成

一个 JWT 实际上就是一个字符串,它由三局部组成,头部 载荷 签名

载荷(Payload)

咱们先将下面的增加好友的操作形容成一个 JSON 对象。其中增加了一些其余的信息,帮忙今后收到这个 JWT 的服务器了解这个 JWT。

{
    "iss": "John Wu JWT",
    "iat": 1441593502,
    "exp": 1441594722,
    "aud": "www.example.com",
    "sub": "jrocket@example.com",
    "from_user": "B",
    "target_user": "A"
}

这外面的前五个字段都是由 JWT 的规范所定义的。

  • iss: 该 JWT 的签发者
  • sub: 该 JWT 所面向的用户
  • aud: 接管该 JWT 的一方
  • exp(expires): 什么时候过期,这里是一个 Unix 工夫戳
  • iat(issued at): 在什么时候签发的

这些定义都能够在规范中找到。

将下面的 JSON 对象进行 [base64 编码] 能够失去上面的字符串。这个字符串咱们将它称作 JWT 的Payload(载荷)。

eyJpc3MiOiJKb2huIFd1IEpXVCIsImlhdCI6MTQ0MTU5MzUwMiwiZXhwIjoxNDQxNTk0NzIyLCJhdW

如果你应用 Node.js,能够用 Node.js 的包 base64url 来失去这个字符串。

小常识:Base64 是一种编码,也就是说,它是能够被翻译回原来的样子来的。它并不是一种加密过程。

var base64url = require('base64url')
var header = {
    "from_user": "B",
    "target_user": "A"
}
console.log(base64url(JSON.stringify(header)))
// 输入:eyJpc3MiOiJKb2huIFd1IEpXVCIsImlhdCI6MTQ0MTU5MzUwMiwiZXhwIjoxNDQxNTk0NzIyLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiZnJvbV91c2VyIjoiQiIsInRhcmdldF91c2VyIjoiQSJ9

头部(Header)

JWT 还须要一个头部,头部用于形容对于该 JWT 的最根本的信息,例如其类型以及签名所用的算法等。这也能够被示意成一个 JSON 对象。

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

在这里,咱们阐明了这是一个 JWT,并且咱们所用的签名算法(前面会提到)是 HS256 算法。

对它也要进行 Base64 编码,之后的字符串就成了 JWT 的Header(头部)。

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

签名(签名)

将下面的两个编码后的字符串都用句号. 连贯在一起(头部在前),就造成了

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0

这一部分的过程在 node-jws 的源码中有体现

最初,咱们将下面拼接完的字符串用 HS256 算法进行加密。在加密的时候,咱们还须要提供一个密钥(secret)。如果咱们用 mystar 作为密钥的话,那么就能够失去咱们加密后的内容

rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM

这一部分又叫做 签名

最初将这一部分签名也拼接在被签名的字符串前面,咱们就失去了残缺的 JWT

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn

于是,咱们就能够将邮件中的 URL 改成

https://your.awesome-app.com/make-friend/?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJm

这样就能够平安地实现增加好友的操作了!

且慢,咱们肯定会有一些问题:

  1. 签名的目标是什么?
  2. Base64 是一种编码,是可逆的,那么我的信息不就被裸露了吗?

让我逐个为你阐明。

签名的目标

最初一步签名的过程,实际上是对头部以及载荷内容进行签名。一般而言,加密算法对于不同的输出产生的输入总是不一样的。对于两个不同的输出,产生同样的输入的概率极其地小(有可能比我成世界首富的概率还小)。所以,咱们就把“不一样的输出产生不一样的输入”当做必然事件来对待吧。

所以,如果有人对头部以及载荷的内容解码之后进行批改,再进行编码的话,那么新的头部和载荷的签名和之前的签名就将是不一样的。而且,如果不晓得服务器加密的时候用的密钥的话,得进去的签名也肯定会是不一样的。

服务器利用在承受到 JWT 后,会首先对头部和载荷的内容用同一算法再次签名。那么服务器利用是怎么晓得咱们用的是哪一种算法呢?别忘了,咱们在 JWT 的头部中曾经用 alg 字段指明了咱们的加密算法了。

如果服务器利用对头部和载荷再次以同样办法签名之后发现,本人计算出来的签名和承受到的签名不一样,那么就阐明这个 Token 的内容被他人动过的,咱们应该回绝这个 Token,返回一个 HTTP 401 Unauthorized 响应。

信息会裸露?

是的。

所以,在 JWT 中,不应该在载荷外面退出任何敏感的数据。在下面的例子中,咱们传输的是用户的 User ID。这个值实际上不是什么敏感内容,个别状况下被晓得也是平安的。

然而像明码这样的内容就不能被放在 JWT 中了。如果将用户的明码放在了 JWT 中,那么怀有歹意的第三方通过 Base64 解码就能很快地晓得你的明码了。

JWT 的实用场景

咱们能够看到,JWT 适宜用于向 Web 利用传递一些非敏感信息。例如在下面提到的实现加好友的操作,还有诸如下订单的操作等等。

用户认证八步走

所谓用户认证(Authentication),就是让用户登录,并且在接下来的一段时间内让用户拜访网站时能够应用其账户,而不须要再次登录的机制。

小常识:可别把用户认证和用户受权(Authorization)搞混了。用户受权指的是规定并容许用户应用本人的权限,例如公布帖子、治理站点等。

首先,服务器利用(上面简称“利用”)让用户通过 Web 表单将本人的用户名和密码发送到服务器的接口。这一过程个别是一个 HTTP POST 申请。倡议的形式是通过 SSL 加密的传输(https 协定),从而防止敏感信息被嗅探。

接下来,利用和数据库核查用户名和明码。

核查用户名和明码胜利后,利用将用户的 id(图中的 user_id)作为 JWT Payload 的一个属性,将其与头部别离进行 Base64 编码拼接后签名,造成一个 JWT。这里的 JWT 就是一个形同 lll.zzz.xxx 的字符串。

利用将 JWT 字符串作为该申请 Cookie 的一部分返回给用户。留神,在这里必须应用 HttpOnly 属性来避免 Cookie 被 JavaScript 读取,从而防止跨站脚本攻打(XSS 攻打)。

在 Cookie 生效或者被删除前,用户每次拜访利用,利用都会承受到含有 jwt 的 Cookie。从而利用就能够将 JWT 从申请中提取进去。

利用通过一系列工作查看 JWT 的有效性。例如,查看签名是否正确;查看 Token 是否过期;查看 Token 的接管方是否是本人(可选)。

利用在确认 JWT 无效之后,JWT 进行 Base64 解码(可能在上一步中曾经实现),而后在 Payload 中读取用户的 id 值,也就是 user_id 属性。这里用户的 id 为 1025。

利用从数据库取到 id 为 1025 的用户的信息,加载到内存中,进行 ORM 之类的一系列底层逻辑初始化。

利用依据用户申请进行响应。

和 Session 形式存储 id 的差别

Session 形式存储用户 id 的最大弊病在于要占用大量服务器内存,对于较大型利用而言可能还要保留许多的状态。一般而言,大型利用还须要借助一些 KV 数据库和一系列缓存机制来实现 Session 的存储。

而 JWT 形式将用户状态扩散到了客户端中,能够显著加重服务端的内存压力。除了用户 id 之外,还能够存储其余的和用户相干的信息,例如该用户是否是管理员、用户所在的分桶(见[《你所应该晓得的 A / B 测试根底》一文](
/2015/08/27/introduction-to-ab-testing/)等。

虽说 JWT 形式让服务器有一些计算压力(例如加密、编码和解码),然而这些压力相比磁盘 I / O 而言或者是一丘之貉。具体是否采纳,须要在不同场景下用数据谈话。

单点登录

Session 形式来存储用户 id,一开始用户的 Session 只会存储在一台服务器上。对于有多个子域名的站点,每个子域名至多会对应一台不同的服务器,例如:

  • www.taobao.com
  • nv.taobao.com
  • nz.taobao.com
  • login.taobao.com

所以如果要实现在 login.taobao.com 登录后,在其余的子域名下仍然能够取到 Session,这要求咱们在多台服务器上同步 Session。

应用 JWT 的形式则没有这个问题的存在,因为用户的状态曾经被传送到了客户端。因而,咱们只须要将含有 JWT 的 Cookie 的 domain 设置为顶级域名即可,例如

Set-Cookie: jwt=lll.zzz.xxx; HttpOnly; max-age=980000; domain=.taobao.com

留神 domain 必须设置为一个点加顶级域名,即.taobao.com。这样,taobao.com 和 *.taobao.com 就都能够承受到这个 Cookie,并获取 JWT 了。

原文:https://u.nu/2k4wk
本文首发于公众号:Java 版 web 我的项目,欢送关注获取更多精彩内容

正文完
 0