Access token(访问令牌:以JWT格式无状态存储)和Refresh token(刷新令牌:有状态存储于Redis/DB)区别与联系、Redis黑名单
文章目录
- **1. Access Token**
- **定义**
- - **Access Token**(访问令牌)是短期有效的凭证,用于访问受保护的资源(如 API 接口、数据库等)。
- - **特点**:
- **作用**
- - 直接用于访问资源,例如调用 API 接口时,客户端需在请求头中携带 `Authorization: Bearer <access_token>`。
- **2. Refresh Token**
- **定义**
- - **Refresh Token**(刷新令牌)是长期有效的凭证,用于获取新的 Access Token。
- - **特点**:
- **作用**
- - 在 Access Token 过期时,客户端使用 Refresh Token 向认证服务器请求新的 Access Token,避免用户频繁登录。
- **3. 工作流程**
- **初次登录**
- 1. 用户提交用户名和密码。
- 2. 认证服务器验证成功后,生成并返回:
- 3. 客户端将两个 Token 存储在安全位置(如本地存储或 Cookie)。
- **访问受保护资源**
- 1. 客户端携带 Access Token 发送请求(如 `Authorization: Bearer <access_token>`)。
- 2. 服务端验证 Access Token:
- **刷新 Access Token**
- 1. 客户端检测到 `401` 错误后,使用 Refresh Token 向认证服务器发起刷新请求。
- 2. 认证服务器验证 Refresh Token:
- 3. 客户端更新本地存储的 Token,并重新发送之前失败的请求。
- **重新登录**
- - 若 Refresh Token 也过期或无效,客户端需引导用户重新登录,获取新的 Access Token 和 Refresh Token。
- **4. 安全性设计**
- **Access Token 的安全性**
- - **短时效**:减少被窃取后的影响范围。
- - **加密传输**:通过 HTTPS 传输,防止中间人攻击。
- - **权限最小化**:仅包含必要的权限,避免越权操作。
- **Refresh Token 的安全性**
- - **存储安全**:避免明文存储,可使用加密或服务器端存储(如 Redis)。
- - **限制使用次数**:防止无限刷新(如每次刷新后生成新的 Refresh Token)。
- - **绑定设备或会话**:与客户端 IP、设备指纹等绑定,增强安全性。
- - **及时失效**:用户登出时主动使 Refresh Token 失效。
- **5. 双 Token 机制的优势**
- 1. **安全性**:
- - Access Token 短时效,降低泄露风险。
- - Refresh Token 仅在必要时使用,减少暴露机会。
- 2. **用户体验**:
- - 用户无需频繁登录,通过 Refresh Token 实现“无感刷新”。
- 3. **灵活性**:
- - 可根据业务需求调整 Access Token 和 Refresh Token 的有效期。
- 4. **兼容性**:
- - 支持 OAuth 2.0 协议,适用于 Web、移动端和第三方应用。
- **6. 实际应用场景**
- - **Web 应用**:用户登录后,Access Token 用于 API 调用,Refresh Token 用于后台自动续期。
- - **移动应用**:长期保持登录状态,通过 Refresh Token 避免频繁输入密码。
- - **第三方服务集成**:如微信开放平台,通过双 Token 机制管理用户授权和访问权限。
- **7. 常见问题与解决方案**
- **Q1: 如果 Refresh Token 被盗怎么办?**
- - **解决方案**:
- **Q2: 如何检测 Access Token 是否过期?**
- - **解决方案**:
- **Q3: 是否可以无限刷新 Access Token?**
- - **解决方案**:
- **8. 代码示例(简化版)**
- **生成 Token(服务端)**
- **刷新 Token(客户端)**
- **总结**
- - **Access Token** 是短期凭证,用于直接访问资源,需频繁刷新。
- - **Refresh Token** 是长期凭证,用于安全获取新的 Access Token,减少用户登录频率。
- - 双 Token 机制通过短时令牌和长时令牌的结合,在安全性和用户体验之间取得平衡,是现代身份认证的核心方案之一。
- 补充:关于状态存储最佳实践
- **1. Access Token 的存储:无状态 vs 有状态**
- **推荐方案:无状态存储(如 JWT 自验证)**
- **例外情况:有状态存储**
- **2. Refresh Token 的存储:有状态存储**
- **推荐方案:有状态存储(如 Redis 或数据库)**
- **3. 最佳实践总结**
- **具体建议**
- **4. 示例代码(基于 Spring Security OAuth2)**
- **Access Token(JWT 无状态)**
- **Refresh Token(Redis 有状态)**
- **5. 场景适配建议**
- - **高安全性场景**(如金融系统):
- - **高性能场景**(如内容 API):
- **总结**
- - **Access Token**:**无状态存储(JWT)** 是主流方案,兼顾性能和扩展性。
- - **Refresh Token**:**有状态存储(Redis/DB)** 是必然选择,确保安全性和可控性。
- - **混合方案**:通过 JWT + Redis 黑名单,平衡性能与安全性,适应大多数业务需求。
Access Token 和 Refresh Token 是现代身份认证和授权机制中的核心组件,主要用于在保障安全性的同时提升用户体验。以下是它们的原理及工作流程的详细解释:
1. Access Token
定义
- Access Token(访问令牌)是短期有效的凭证,用于访问受保护的资源(如 API 接口、数据库等)。
- 特点:
- 有效期短:通常为几分钟到几小时(如 15 分钟到 2 小时)。
- 高频传输:每次请求受保护资源时都需要携带。
- 包含用户信息:通常基于 JWT(JSON Web Token)格式,包含用户身份、权限等信息。
- 过期后需重新获取:不能直接续期,需通过 Refresh Token 获取新的 Access Token。
作用
- 直接用于访问资源,例如调用 API 接口时,客户端需在请求头中携带 Authorization: Bearer <access_token>
。
2. Refresh Token
定义
- Refresh Token(刷新令牌)是长期有效的凭证,用于获取新的 Access Token。
- 特点:
- 有效期长:通常为几天到几个月(如 30 天)。
- 低频传输:仅在 Access Token 过期时用于刷新,传输次数较少。
- 不包含用户信息:仅用于身份验证,不直接用于访问资源。
- 安全存储:需加密存储(如 HttpOnly Cookie 或服务器端数据库)。
作用
- 在 Access Token 过期时,客户端使用 Refresh Token 向认证服务器请求新的 Access Token,避免用户频繁登录。
3. 工作流程
初次登录
1. 用户提交用户名和密码。
2. 认证服务器验证成功后,生成并返回:
- Access Token(短期)。
- Refresh Token(长期)。
3. 客户端将两个 Token 存储在安全位置(如本地存储或 Cookie)。
访问受保护资源
1. 客户端携带 Access Token 发送请求(如 Authorization: Bearer <access_token>
)。
2. 服务端验证 Access Token:
- 有效:返回资源数据。
- 过期:返回
401 Unauthorized
错误。
刷新 Access Token
1. 客户端检测到 401
错误后,使用 Refresh Token 向认证服务器发起刷新请求。
2. 认证服务器验证 Refresh Token:
- 有效:生成新的 Access Token(可能同时生成新的 Refresh Token),并返回给客户端。
- 无效:返回
401
或403
,客户端需重新登录。
3. 客户端更新本地存储的 Token,并重新发送之前失败的请求。
重新登录
- 若 Refresh Token 也过期或无效,客户端需引导用户重新登录,获取新的 Access Token 和 Refresh Token。
4. 安全性设计
Access Token 的安全性
- 短时效:减少被窃取后的影响范围。
- 加密传输:通过 HTTPS 传输,防止中间人攻击。
- 权限最小化:仅包含必要的权限,避免越权操作。
Refresh Token 的安全性
- 存储安全:避免明文存储,可使用加密或服务器端存储(如 Redis)。
- 限制使用次数:防止无限刷新(如每次刷新后生成新的 Refresh Token)。
- 绑定设备或会话:与客户端 IP、设备指纹等绑定,增强安全性。
- 及时失效:用户登出时主动使 Refresh Token 失效。
5. 双 Token 机制的优势
1. 安全性:
- Access Token 短时效,降低泄露风险。
- Refresh Token 仅在必要时使用,减少暴露机会。
2. 用户体验:
- 用户无需频繁登录,通过 Refresh Token 实现“无感刷新”。
3. 灵活性:
- 可根据业务需求调整 Access Token 和 Refresh Token 的有效期。
4. 兼容性:
- 支持 OAuth 2.0 协议,适用于 Web、移动端和第三方应用。
6. 实际应用场景
- Web 应用:用户登录后,Access Token 用于 API 调用,Refresh Token 用于后台自动续期。
- 移动应用:长期保持登录状态,通过 Refresh Token 避免频繁输入密码。
- 第三方服务集成:如微信开放平台,通过双 Token 机制管理用户授权和访问权限。
7. 常见问题与解决方案
Q1: 如果 Refresh Token 被盗怎么办?
- 解决方案:
- 限制 Refresh Token 的使用次数,每次刷新后生成新的 Token。
- 绑定用户设备或 IP 地址,异常行为时触发安全策略。
- 提供“强制登出”功能,使所有关联 Token 失效。
Q2: 如何检测 Access Token 是否过期?
- 解决方案:
- 服务端返回
401 Unauthorized
错误时,客户端自动触发 Refresh Token 流程。 - 客户端可主动检查 Token 剩余有效期(如提前 5 分钟刷新)。
Q3: 是否可以无限刷新 Access Token?
- 解决方案:
- 限制 Refresh Token 的总使用次数或最大有效期。
- 每次刷新时生成新的 Refresh Token,旧的 Token 自动失效。
8. 代码示例(简化版)
生成 Token(服务端)
// 使用 JWT 生成 Access Token 和 Refresh Token
String accessToken = Jwts.builder().setSubject(userId).setExpiration(new Date(System.currentTimeMillis() + 15 * 60 * 1000)) // 15分钟.signWith(SignatureAlgorithm.HS256, "secretKey").compact();String refreshToken = Jwts.builder().setSubject(userId).setExpiration(new Date(System.currentTimeMillis() + 30 * 24 * 60 * 60 * 1000)) // 30天.signWith(SignatureAlgorithm.HS256, "secretKey").compact();
刷新 Token(客户端)
// 检测 401 错误后自动刷新
function refreshToken() {fetch('/api/auth/refresh', {method: 'POST',body: JSON.stringify({ refreshToken: storedRefreshToken })}).then(response => response.json()).then(data => {localStorage.setItem('accessToken', data.accessToken);localStorage.setItem('refreshToken', data.refreshToken);retryOriginalRequest(); // 重新发送之前的请求}).catch(() => {// 刷新失败,跳转登录页});
}
总结
- Access Token 是短期凭证,用于直接访问资源,需频繁刷新。
- Refresh Token 是长期凭证,用于安全获取新的 Access Token,减少用户登录频率。
- 双 Token 机制通过短时令牌和长时令牌的结合,在安全性和用户体验之间取得平衡,是现代身份认证的核心方案之一。
补充:关于状态存储最佳实践
在 OAuth2 认证体系中,Access Token 和 Refresh Token 的存储策略需根据其生命周期、安全性和性能需求进行区分。以下是常见的最佳实践和建议:
1. Access Token 的存储:无状态 vs 有状态
推荐方案:无状态存储(如 JWT 自验证)
- 原因:
- Access Token 是短期有效的(通常几分钟到几小时),适合通过 JWT(JSON Web Token) 实现自包含的无状态验证。
- 无需频繁查询数据库或缓存,减少服务器资源消耗,提升性能。
- 适合分布式系统,因为每个服务节点无需共享存储即可独立验证 Token。
- 实现方式:
- 使用 JWT 格式生成 Access Token,包含用户身份、权限、过期时间等信息。
- 服务端通过签名验证 Token 的合法性(无需存储)。
- 适用场景:
- 高并发、低延迟的 API 服务。
- 微服务架构中,多个服务节点无需共享 Token 状态。
例外情况:有状态存储
- 如果 Access Token 需要主动吊销(如用户登出),可结合 黑名单机制 实现有状态存储:
- 将被吊销的 Token 存入 Redis 黑名单(设置短过期时间)。
- 验证 Token 时同时检查黑名单。
2. Refresh Token 的存储:有状态存储
推荐方案:有状态存储(如 Redis 或数据库)
- 原因:
- Refresh Token 是长期有效的(通常几天到几个月),需频繁更新、刷新和主动吊销。
- 安全性要求高,需记录 Token 的状态(是否有效、绑定的用户、设备信息等)。
- 需支持主动失效(如用户登出或安全事件)。
- 实现方式:
- 将 Refresh Token 存储在 Redis 或数据库中,键值格式示例:
key: refresh_token:{token_value} value: {user_id, issued_at, expires_at, client_id, ...}
- 设置合理的过期时间(与 Refresh Token 有效期一致)。
- 登出时删除或标记为失效。
- 将 Refresh Token 存储在 Redis 或数据库中,键值格式示例:
- 安全增强措施:
- 绑定设备或 IP:将 Refresh Token 与用户设备指纹、IP 地址绑定,防止跨设备滥用。
- 限制刷新次数:每次刷新后生成新的 Refresh Token,旧 Token 自动失效。
- 加密存储:对 Token 值进行加密或哈希处理,避免明文存储。
3. 最佳实践总结
Token 类型 | 存储方式 | 理由 |
---|---|---|
Access Token | 无状态(JWT) | 短期有效,自验证,提升性能;适合分布式系统。 |
Refresh Token | 有状态(Redis/DB) | 长期有效,需管理状态(刷新、吊销);需绑定用户和设备信息。 |
具体建议
-
Access Token:
- 使用 JWT 格式,包含用户身份、权限、过期时间(
exp
)。 - 通过 HTTPS 传输,防止中间人攻击。
- 设置较短的过期时间(如 15 分钟)。
- 使用 JWT 格式,包含用户身份、权限、过期时间(
-
Refresh Token:
- 存储在 Redis 或数据库中,设置与 Token 有效期一致的过期时间。
- 每次刷新时生成新的 Refresh Token,旧 Token 失效。
- 登出时主动删除 Refresh Token。
-
混合方案:
- Access Token 无状态:通过 JWT 自验证。
- Refresh Token 有状态:通过 Redis/DB 管理状态。
- 黑名单机制:对被吊销的 Access Token 使用 Redis 黑名单(短过期时间)。
-
安全性增强:
- 绑定设备/IP:将 Refresh Token 与用户设备指纹、IP 地址绑定。
- 加密签名:JWT 使用强签名算法(如 HS256 或 RS256)。
- 限制作用域:Access Token 仅包含必要的权限(最小化原则)。
4. 示例代码(基于 Spring Security OAuth2)
Access Token(JWT 无状态)
// 生成 JWT Access Token
String accessToken = Jwts.builder().setSubject(userId).claim("roles", userRoles) // 权限信息.setExpiration(new Date(System.currentTimeMillis() + 15 * 60 * 1000)) // 15分钟.signWith(SignatureAlgorithm.HS256, "your-secret-key").compact();
Refresh Token(Redis 有状态)
// 存储 Refresh Token 到 Redis
redisTemplate.opsForValue().set("refresh_token:" + refreshTokenValue, userId, Duration.ofDays(30) // 30天过期
);// 验证并刷新 Token
String storedUserId = redisTemplate.opsForValue().get("refresh_token:" + refreshTokenValue);
if (storedUserId != null) {// 生成新的 Access Token 和 Refresh TokenString newAccessToken = generateJwtAccessToken(storedUserId);String newRefreshToken = generateRefreshToken();redisTemplate.opsForValue().set("refresh_token:" + newRefreshToken, storedUserId, Duration.ofDays(30));return newAccessToken + ":" + newRefreshToken;
} else {throw new InvalidTokenException("Invalid refresh token");
}
5. 场景适配建议
- 高安全性场景(如金融系统):
- 使用 Redis 存储 Refresh Token,并绑定设备/IP。
- Access Token 使用 JWT,但通过黑名单机制管理过期。
- 高性能场景(如内容 API):
- Access Token 完全无状态(JWT),Refresh Token 存储在 Redis。
- 设置较短的 Refresh Token 刷新频率。
- 分布式系统:
- 使用 Redis 集群存储 Refresh Token,确保状态一致性。
- Access Token 通过 JWT 自验证,无需跨节点通信。