JWT(Json Web Token)
JWT(JSON Web Token)的定义
- JWT 是一种开放标准(RFC 7519),用于在各方之间安全地传输信息。它是一种紧凑的、自包含的方式,可以将信息在用户和服务器之间进行传递,并且可以被验证和信任。JWT 主要由三部分组成,即头部(Header)、载荷(Payload)和签名(Signature)。
- 头部(Header) :通常包含两部分信息,一是令牌的类型(即 JWT),二是所使用的签名算法,如 HMAC - SHA256 或 RSA 等。例如:
{
"alg": "HS256",
"typ": "JWT"
}
-
载荷(Payload) :包含声明(Claims)的部分,用于承载一些关于用户或者数据的信息。有三种类型的声明:
- 注册的声明(Registered claims) :这些是预定义的声明,不是强制的,但推荐使用,如
iss(发行人)、exp(过期时间)、sub(主题)等。例如:
- 注册的声明(Registered claims) :这些是预定义的声明,不是强制的,但推荐使用,如
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
- 公共的声明(Public claims) :这些可以由使用 JWT 的各方自定义,但是为了避免冲突,应该在 IANA JSON Web Token Registry 中定义或者使用一个带有命名空间的自定义名称。
- 私有声明(Private claims) :这些是在特定应用场景下,由用户自定义的声明,用于在通信双方之间传递一些特定的信息。
- 签名(Signature) :用于验证消息在传递过程中没有被篡改。它是通过将头部编码、载荷编码和一个密钥(secret),使用头部中指定的签名算法(如 HS256)生成的。
使用场景
身份认证(Authentication)
- 单页应用(SPA) :在现代的前端单页应用中,如使用 Vue.js 或 React.js 构建的应用,JWT 被广泛用于用户认证。当用户登录成功后,服务器生成一个 JWT 并返回给客户端。之后客户端在每次请求需要认证的后端接口时,将 JWT 放在请求头(如
Authorization: Bearer <token>)中发送给服务器。服务器通过验证 JWT 来确定用户的身份,而不需要在每个请求中都查询数据库验证用户凭据,大大提高了认证效率。 - 移动应用:对于移动应用(如 iOS 和 Android 应用),JWT 也提供了一种轻量级的身份认证方式。用户登录后,移动应用存储 JWT,并在向服务器发送请求时携带它,用于验证用户身份,确保只有经过授权的用户能够访问受保护的资源。
授权(Authorization)
-
基于角色的访问控制(RBAC) :JWT 可以在载荷部分包含用户的角色信息,例如
"role": "admin"或"role": "user"。当客户端请求访问某个资源时,服务器验证 JWT,检查用户角色是否具有访问该资源的权限。比如,管理员角色(admin)的用户可能被允许访问和管理所有用户数据,而普通用户(user)角色只能访问自己的数据。 -
微服务架构:在微服务架构中,服务之间的通信和授权可以使用 JWT。当一个服务成功认证用户并生成 JWT 后,这个 JWT 可以在不同的微服务之间传递。每个微服务可以验证 JWT 来确定用户的身份和权限,从而决定是否允许用户执行特定的操作或者访问特定的资源。
信息交换(Information Exchange)
- 跨域资源共享(CORS)场景:当不同域名的系统需要安全地交换信息时,JWT 是一种很好的选择。例如,一个公司的主网站和其合作伙伴的网站之间需要共享用户的部分信息,如用户 ID 和一些基本的用户属性。通过 JWT,可以在保证信息安全的前提下,方便地进行跨域信息传递。
- 分布式系统中的数据传输:在分布式系统中,不同节点之间可能需要传递一些包含用户相关信息的消息。JWT 提供了一种标准化、安全的方式来封装这些信息,确保接收节点能够验证信息的来源和完整性,从而可靠地进行数据处理和业务逻辑执行。
一个demo
- 加密解密都应在服务端处理,secretKey放在客户端过于危险
-main.js
-package.json
main.js
const jwt = require('jsonwebtoken');
const secretKey = 'my_secret_key';//这是一个用于签名的密钥,应该妥善保管
const payload = {
user_id: 1,
username: 'example_user'
};
const token = jwt.sign(payload, secretKey, { expiresIn: '1h' });//expiresIn指定了令牌的过期时间,这里是1小时
console.log(token);
//eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImV4YW1wbGVfdXNlciIsImlhdCI6MTczMjYwMTMzNSwiZXhwIjoxNzMyNjA0OTM1fQ.1uCWofIHNMx-nDXU7IwhcoOA7aolu1TDjVKteHj_irU
try {
const decoded = jwt.verify(token, secretKey);
console.log('decoded:',decoded);
} catch (err) {
console.error('JWT验证失败:', err);
}
package.json
{
"scripts": {
"serve": "node main.js"
},
"name": "project-name",
"type": "commonjs",
"dependencies": {
"jsonwebtoken": "^9.0.2"
}
}
执行
npm run serve
console.log↓
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImV4YW1wbGVfdXNlciIsImlhdCI6MTczMjYwMTMzNSwiZXhwIjoxNzMyNjA0OTM1fQ.1uCWofIHNMx-nDXU7IwhcoOA7aolu1TDjVKteHj_irU
decoded: {
user_id: 1,
username: 'example_user',
iat: 1732601335,
exp: 1732604935
}
-
服务端验证 JWT 的合法性主要是通过验证签名来实现的。不过这个过程涉及到对头部和载荷信息的处理。
-
当服务端收到一个 JWT 时,首先会将其按照 “.” 分割为头部(Header)、载荷(Payload)和签名(Signature)三个部分。
-
服务端会使用 Base64Url 解码头部和载荷部分,以获取其中包含的信息,如签名算法、令牌的过期时间(
exp)、签发时间(iat)、用户信息等。-
第一段是头部,解码后大致如下(上面有说)
{ "alg": "HS256", "typ": "JWT" } -
第二段是载荷,解码后是上执行后打印的decoded,是主要传输核心内容
-
第三段是签名
-
签名验证过程
-
计算签名:
- 服务端会根据头部中指定的签名算法(例如 HS256),使用与生成 JWT 时相同的密钥(secret),对解码后的头部和载荷重新进行签名计算。这个计算过程与生成 JWT 签名的过程类似。例如,对于 HS256 算法,计算公式大致为
HMACSHA256(base64UrlEncode(header)+ "."+base64UrlEncode(payload),secret)。
- 服务端会根据头部中指定的签名算法(例如 HS256),使用与生成 JWT 时相同的密钥(secret),对解码后的头部和载荷重新进行签名计算。这个计算过程与生成 JWT 签名的过程类似。例如,对于 HS256 算法,计算公式大致为
-
比对签名:
- 服务端将重新计算得到的签名(就是前两段)和接收到的 JWT 中的签名部分(就是第三段)进行比对。如果两个签名相同,那么可以初步判断 JWT 在传输过程中没有被篡改,并且是由拥有正确密钥的一方生成的。
防篡改
在身份认证场景中,JWT 用于传递用户身份信息。如果没有防篡改机制,恶意用户可以修改 JWT 中的用户 ID 或其他身份标识信息,然后伪装成其他用户访问受保护的资源。例如,在一个 Web 应用中,用户登录后获得一个 JWT,其中载荷部分包含用户 ID 和角色信息。如果 JWT 可以被轻易篡改,攻击者可能将用户 ID 修改为其他用户的 ID,并且将自己伪装成管理员角色,从而获取非法的管理权限。