【51CTO.com快译】您一定听说过JSON Web Token(JWT)吧? 它是当前用来保护API的先进技术之一。与大多数安全概念与技术一样,我们在准备使用它之前,了解其工作原理是非常必要且重要的。当然,过于专业和技术性的JWT解释可能会让您觉得费解,甚至感到头痛。那么让我试着用一种比较浅显易懂的方式,向您阐述JWT是如何加固API的吧。
API身份验证
不言而喻,在复杂的网络环境中,我们需要对各种API资源实施访问限制。例如,我们不希望某个用户能够更改另一个用户的密码。那么,我们就需要该用户以提交其ID和密码的方式,来保护和加固目标资源。换句话说:我们需要对他们进行身份验证。
而在实际应用中,我们保护HTTP类API的难点在于:各种请求是无状态的。也就是说:API无法知道任意两个请求是否来自同一个用户。有人可能会追问:我们为什么不能要求用户在每次调用API时,都提供他们的ID和密码呢?答案是:因为这样会给用户带来极差的访问体验。
JSON Web Token
因此,我们需要的是:用户只用一次性提供信任凭据,而在后续的请求中,服务器会以另一种方式进行用户身份的识别。基于这种思想,JSON Web Token应运而生。
当然,如果您是一位爱好钻研的学霸,那么您可以通过链接:https://robmclarty.com/blog/what-is-a-json-web-token,来对JSON Web Token的工作原理进行全面深入的参悟。
在此让我们想象一下:如果您打算入住一家酒店,那么“令牌”就是允许您进入自己房间、以及酒店内其他设施的安全门卡,显然您不能进入其他人的房间。而且在退房的时候,您需要退还门卡,即:注销。
令牌的结构
通常情况下, JSON Web Token是通过各种HTTP请求的头部(header)被发送的。如下所示:
- Authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U
可见,令牌就是“Authorization:Bearer”的后面部分,隶属于HTTP的头部信息。
上述信息虽然显得比较凌乱,不过它包含了如下部分:
首先,令牌由三个不同的字符串所组成,它们分别以点号隔开。这三个字符串使用了base 64编码,分别对应头部(header)、有效载荷(payload)和签名(signature),如下所示:
- // Header
- eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
- // Payload
- eyJzdWIiOiIxMjM0NTY3ODkwIn0
- // Signature
- dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U
注:base 64是一种转换字符串的方法,能够确保字符串在跨Web传输的过程中不会出现问题。由于它不是一种加密方法,因此任何人都可以很容易地对它进行解码,以查看原始数据。
下面,我们对上述字符串进行解码,以便更好地了解JWT的结构。
头部
通过解码令牌的头部,我们可以得到如下的元信息(meta information)。由于它对于我们理解整体的工作原理帮助不大,所以我们在此并不做详细解读。
- {
- "alg":"HS256",
- "typ":"JWT"
- }
有效载荷
有效载荷里的内容要丰富得多。您可以用它来包含任何自己需要传递的数据。在此,由于该令牌的目的是对API的访问进行身份验证,因此仅包含了用户的ID。
- {
- "userId":"1234567890"
- }
值得注意的是:有效负载并不安全。任何人都可以通过解码令牌,来查看有效负载中的确切内容。因此,我们通常只包含ID,而不会包含诸如用户邮件内容等敏感的标识信息。
尽管该有效负载为API提供了识别用户所需的全部信息,但是它并不提供具体的身份验证方法。毕竟凭借这些信息,黑客足以能够轻松地找到用户的ID,并可伪造出令牌。因此,我们还需要有签名,而它才是令牌认证环节中的关键部分。
哈希算法
在开始解释签名的工作原理之前,我们需要先来了解一下什么是哈希算法。
首先,它是一个函数,可用来将目标字符串转换为另一种被称为哈希值(hash)的新字符串。例如,我们对字符串“Hello, world.”进行哈希操作,那么就能够得到如下经过了SHA256哈希算法的输出:
- 4ae7c3b6ac0beff671efa8cf57386151c06e58ca53a78d83f36107316cec125f
注:哈希算法有许多种不同的类型,JWT常用的是SHA256。
而哈希的重要属性在于:我们无法使用哈希算法,通过哈希值来识别出原始的字符串。换句话说,我们无法凭借上述哈希值,直接计算或得出原始的字符串“Hello, world.”。从理论上说,根据哈希的复杂性,猜测出原始字符串是完全不可行的。
JWT签名
现在,让我们来看JWT令牌结构的第三个部分:签名。实际上,该部分是需要进行计算的。
- HMACSHA256(
- base64UrlEncode(header) + "." + base64UrlEncode(payload),
- "secret string"
- );
我们下面来具体分析一下上述代码:
- 首先,HMACSHA256是哈希函数的名称,它用到了两个参数:需要进行哈希的字符串和密钥(secret)。
- 其次,这个需要进行哈希的字符串,是经过base 64编码过的头部和有效载荷。
- 第三,密钥是一串任意数据,而且只有服务器知晓。
问:为什么要将头部和有效载荷添加到签名的哈希值中呢?
答:这样可以确保签名对于该特定令牌来说是仅有的。
问:什么是密钥?
答:让我们从如何伪造一个令牌的角度来回答该问题。我们之前说过,黑客无法从输出值来推导出经过哈希的输入信息。但是,由于签名中包括了头部和有效载荷,而这些都是公共的信息,因此如果黑客知道了哈希算法(这通常是在头部被指定的),那么就能够生成相同的哈希值。
可见,如果服务器掌握了某个非公开的密钥,并且将其包含在哈希处理的过程中,那么就能够防止黑客自行伪造并生成带有哈希值的令牌。同时,由于哈希值“掩盖”了各种原始信息,因此也就保证了密钥不会被黑客所发现。
注:将私有数据添加到哈希之中的过程,被称为加盐(salting),这使得破解令牌几乎是不可能的。
身份验证过程
至此,想必您已经理解了令牌的创建过程。那么,我们又该如何用它来验证用户的API呢?
登录
在用户登录时,系统会生成一个令牌,并将其与用户模型(model)一起存储在数据库中。
- logincontrol.js:
- if (passwordCorrect) {
- user.token = generateToken(user.id);
- user.save();
- }
然后作为对于登录请求的响应,该令牌被添加到authorization的头部。
logincontrol.js:
- if (passwordCorrect) {
- user.token = generateToken(user.id);
- user.save();
- res.headers("authorization",`Bearer ${token}`).send();
- }
验证请求
有了令牌,用户现在就可以将其添加到各种后续的请求中,以验明正身了。
而当服务器收到添加了身份信息的令牌请求后,会进行如下操作:
- 对令牌进行解码,并从有效载荷中提取ID。
- 使用此ID,在数据库中查找该用户的信息。
- 将请求令牌与带有用户模型的存储令牌进行比较。如果匹配,则认定该用户的“合法”身份。
authMiddleware.js:
- const token = req.header.token;
- const payload = decodeToken(token);
- const user = User.findById(payload.id);
- if (user.token = token) {
- // Authorized
- } else {
- // Unauthorized
- }
注销
如果该用户要求注销,那么系统只需删除掉当前已添加到用户模型的令牌便可。由于用户手上的令牌及时失效了,因此如果他需要再次登录的话,应重新产生新的令牌。
logoutcontrol.js:
- user.token = null;
- user.save();
总结
通过上面的逐步分析,希望您能够对于如何使用JSON Web Token来加固API,已经建立起了基本的概念。当然,该话题涵括的内容远不止这些,如果您有兴趣的话,可以通过如下链接进行扩展阅读:
- Jwt.io - https://jwt.io/
- 什么是JSON Web Token?- https://robmclarty.com/blog/what-is-a-json-web-token
【51CTO译稿,合作站点转载请注明原文译者和出处为51CTO.com】