JWT 令牌

什么是 JWT

OAuth2.0 体系中令牌分为两类,分别是透明令牌、不透明令牌。

不透明令牌则是令牌本身不存储任何信息,比如一串 UUID,因此资源服务拿到这个令牌必须调调用认证授权服务的接口进行令牌的校验,高并发的情况下延迟很高,性能很低。

透明令牌本身就存储这部分用户信息,比如 JWT,资源服务可以调用自身的服务对该令牌进行校验解析,不必调用认证服务的接口去校验令牌。

JWT 有三部分组成,分别是头部、载荷、签名。

Header 包含两部分信息
typ: 表示令牌类型,通常是 “JWT”。
alg: 表示使用的签名算法,例如 HS256(HMAC-SHA256)或 RS256(RSA-SHA256)。

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

这个头部数据会被 Base64Url 编码。

Payload 是存放实际需要传输的信息(声明,claims)的部分。声明分为三种类型
Registered Claims:预定义的标准声明,非强制。常见的声明包括:
iss(Issuer):签发人
sub(Subject):主题
aud(Audience):受众
exp(Expiration):过期时间
iat(Issued At):签发时间
Public Claims:自定义的声明,但需要避免冲突,通常使用 URI 作为命名空间。
Private Claims:应用中定义的声明,仅供双方使用。

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true,
  "exp": 1618474796
}

负载部分同样会经过 Base64Url 编码。

Signature 签名是用来验证消息在传输过程中是否被篡改的部分。它由以下三部分组成:
编码后的头部
编码后的负载
一个密钥(用于对称加密,或使用私钥/公钥对,非对称加密)

在 JWT 的签名部分中,密钥本身并不包含在签名中。签名使用密钥进行生成,但密钥本身不会直接嵌入到 JWT 令牌中。这是出于安全考虑,因为密钥是用来验证令牌合法性的关键,而不是传递给客户端的。

将上述三部分通过指定算法进行签名计算。
生成签名的公式:

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

签名的生成使用了密钥(例如一个 secret key 或 private key),但这个密钥仅用于服务器端的签名生成和验证,客户端永远不会看到这个密钥。
如果使用对称加密(如 HS256),同一个密钥用于签名和验证。
如果使用非对称加密(如 RS256),私钥用于签名,公钥用于验证。
生成的签名将与头部和负载部分一起传输,用于验证令牌的完整性。

JWT 结构示例

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

JWT 的优势在于它是自包含的,包含了验证和信息传递所需的所有内容,不需要服务器在每个请求时进行存储,因此非常适合分布式系统中的身份认证。

使用阿里云 kms 对 JWT 进行签名(非对称方式)
在阿里云 KMS 控制台上创建一个密钥,记录下密钥 ID。
私钥用于服务器端签名,公钥用于客户端验证签名。私钥通常不会被直接获取,而是在签名操作中使用 KMS API。

在阿里云 KMS 中,公钥的使用与验签过程是通过 KMS API 的 VerifyRequest 来实现的,而不是直接在代码中传递公钥。这是因为阿里云 KMS 设计成一种安全的密钥管理服务,签名和验签的过程是通过调用 KMS API 完成的,因此在上面的验签代码中并没有涉及到公钥的获取,虽然在验签的代码中并没有直接引用公钥,但 KMS 会内部处理密钥的使用,这种设计确保了密钥不会直接暴露,增强了安全性。使用了阿里云,就没有必要再导出公钥了。

JWT 的存储

在 JWT(JSON Web Token)架构中,通常不需要将 ID Token 和 Access Token 存储在 Redis 中,原因如下:

  • JWT 是自包含的:它们包含了必要的信息(如 sub 字段中的用户 ID、exp 过期时间、权限范围等)。通过 JWT 的签名机制,服务器可以在不存储 Token 的情况下验证它的有效性。
  • Token 的无状态性:JWT 在无状态分布式系统中非常有用,服务器只需验证 Token 签名,无需在后端数据库或缓存中存储 Token。

尽管 JWT 是无状态的,有些情况下出于安全、控制、审计或撤销 Token 的需求,可能需要将 ID Token 和 Access Token 存储在 Redis 中。

  • Token 撤销机制:如果系统需要在特定情况下(如用户注销、异常活动检测等)主动撤销某些 Token,则可以在 Redis 中存储 Token,并在需要时将其从 Redis 中删除或标记为无效。JWT 本身是无状态的,一旦签发,直到过期前都有效,因此无法通过无状态的方式主动撤销。如果使用 Redis 存储 Token,可以控制其生命周期。
  • 黑名单或 Token 失效控制:为了实现更细粒度的 Token 控制(如在用户修改密码或注销后立即失效 Token),可以通过 Redis 维护一个黑名单或失效列表。如果某个 Token 被标记为无效,服务器可以在解析 Token 时检查 Redis 来判断其有效性。
  • Refresh Token 旋转机制:虽然 Access Token 和 ID Token一般不需要存储,但如果启用了 Refresh Token 旋转(每次使用 Refresh Token 刷新时生成新的 Token),可能需要在 Redis 中存储旧的 Access Token 和 ID Token 以防止其被继续使用,尤其是当希望在生成新的 Token 后让旧 Token 失效时。

实际应用中的建议

  • 无状态实现优先:在大多数情况下,利用 JWT 的无状态特性就足够了,无需将 Access Token 和 ID Token 存储在 Redis 中。只要保证签名正确、Token 未过期,服务器可以无状态地处理认证和授权。
  • 引入状态控制的场景:在有特殊需求的场景下(如 Token 撤销、黑名单、主动失效等),可以考虑将 Token 存储在 Redis 中,并搭配其他机制(如 RefreshToken 的轮换机制、Token 的撤销机制)来增强安全性。

因此,除非有特定的安全需求需要控制 Token 的状态或失效,否则不建议将 JWT 的 Access Token 和 ID Token 存储在 Redis 中,尽量利用 JWT 的无状态设计来减少系统的复杂度。

在JWT(JSON Web Token)中,Refresh Token 放在 Redis 中保存是一个非常常见且推荐的做法。

  • 生成 Refresh Token:用户登录后,服务器生成 Refresh Token,并将其存储在 Redis 中,设置相应的过期时间。
  • 验证 Refresh Token:当 Access Token 过期时,客户端使用 Refresh Token 向服务器请求新的 Access Token。
  • 更新 Redis 中的 Refresh Token:如果 Refresh Token 有效,服务器生成新的 Access Token 和(可选的)新的 Refresh Token,并更新Redis 中的记录。
  • 自动过期管理:Redis 自动管理 Refresh Token 的过期,避免过期 Token 的滥用。

这里需要注意的是:Refresh Token 本身是有过期时间字段的,在判断其是否过期的时候需要进行双重判断,先判断 redis 中是否过期,然后再判断本身是否过期。

无缝刷新 JWT Token

在 Spring Cloud 微服务架构中,使用 JWT 进行用户认证时,Access Token 通常设置较短的有效期,而 Refresh Token 则用于在 Access Token 过期时生成新的 Token,以保证用户不需要重新登录。要实现 Access Token 和 ID Token 的无缝刷新,并确保客户端无感知,可以遵循以下流程和策略。

自动刷新 Token:当 Access Token 快要过期时,服务器端自动使用 Refresh Token 刷新,并重新生成新的 Access Token 和 ID Token。

客户端无感知:客户端无需主动刷新 Token,而是在发送请求时,系统自动判断 Access Token 的有效性,必要时自动刷新 Token。

最小化刷新延迟:确保在每个 API 请求中,如果 Access Token 即将过期或已过期,系统会透明地处理 Token 刷新并将新的 Access Token 返回给客户端。

自动刷新 Token 的机制,在系统微服务的网关或拦截器中实现 Token 的自动刷新逻辑。
在网关或过滤器中检查 Access Token 是否快要过期
每次客户端发起请求时,先通过一个拦截器或过滤器来检查 Access Token 的有效性。解析 JWT 中的过期时间(exp 字段),判断是否快要过期或已经过期。
如果 Access Token 还有几分钟才过期,则继续使用,不做任何操作。
如果 Access Token 已经过期或接近过期,则使用 Refresh Token 刷新。

权限动态变更
由于 JWT 中的权限是嵌入在令牌中的,如果用户的权限发生了变化,需要一种方式来确保新的权限能够生效。通常有两种方法来处理权限变更:
强制刷新令牌:当用户权限发生变更时,强制要求用户重新获取新的令牌。
外部权限检查:不依赖 JWT 中的权限信息,而是在每次请求时,从数据库或缓存中动态查询用户权限。

### JSON Web Token (JWT) 介绍 JSON Web Token(JWT)是一种开放标准(RFC 7519),旨在作为各方之间安全传输信息的方式。这种令牌以紧凑的形式表示声明,使得其非常适合在网络应用环境中使用[^3]。 ### 使用场景 #### 授权(Authorization) 一旦用户登录成功,每个后续请求都会附带一个JWT。这种方式允许用户访问特定的路由、服务和资源,前提是这些都已被授予给对应的令牌持有者。单点登录(SSO)是利用JWT的一个典型例子,在这个过程中,用户的登录状态可以在多个应用程序间共享而不需要重新认证,极大地提升了用户体验并减少了系统的复杂度[^2]。 #### 信息交换(Information Exchange) 除了用于授权之外,JWT还提供了一种可靠的方法来进行两方之间的数据传递。通过对消息进行签名,发送者的身份能够得到确认;同时,由于签名依赖于头部和负载的内容生成,接收方可确信接收到的信息未经篡改[^4]。 ### 实现方式 创建一个典型的JWT涉及三个部分:头部(header)、载荷(payload)以及签名(signature)。这三个组件由点号连接形成最终的token字符串: - **Header**: 包含所采用的加密算法和其他元数据。 - **Payload**: 存储声明(claims),即有关实体(通常是用户)及其他数据的信息集合。常见的注册声明包括`iss`(发行人)`sub`(主题)`aud`(观众)`exp`(到期时间)[^1]。 - **Signature**: 利用指定的秘密或私钥对前面两个部分编码后的组合进行哈希运算得出的结果。这一步骤确保了只有拥有相应密钥的一方才可验证或修改该Token。 ```javascript // 创建 JWT 示例代码 const jwt = require('jsonwebtoken'); let payload = { iss: 'example.org', sub: 'johndoe@example.com', aud: 'service.example.com', exp: Math.floor(Date.now() / 1000) + (60 * 60), }; let secretKey = 'your_secret_key'; let token = jwt.sign(payload, secretKey); console.log(`Generated JWT: ${token}`); ``` ### 安全性 为了保障JWT的安全性,需要注意几个方面: - **密钥管理**:保持签名密钥的安全至关重要,绝不在客户端公开此秘密。 - **HTTPS协议**:始终通过HTTPS渠道传送JWT,以此防范中间人攻击的风险。 - **设置合理的过期期限**:合理规划Token的有效期间(exp claim),防止长时间有效的凭证被恶意利用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值