access token 和refresh token
Access Token 与 Refresh Token:核心概念、区别及实战用法
在认证授权场景(如 API 调用、系统登录)中,Access Token(访问令牌) 和 Refresh Token(刷新令牌) 是一套 “安全且高效” 的令牌机制,核心目的是:在保障系统安全的前提下,减少用户重复登录的频率,提升体验。
一、核心定义与核心作用
1. Access Token(访问令牌)
- 本质:短期有效的 “通行凭证”,相当于用户登录后拿到的 “临时门禁卡”。
- 核心作用:作为调用受保护资源(如 API 接口、用户数据)的直接凭证。客户端(如前端、第三方服务)每次请求需携带它,服务端验证通过后才允许访问。
- 关键特性:
- 有效期极短(通常 10 分钟~2 小时),过期后立即失效;
- 包含核心权限信息(如 “允许查询数据”“禁止修改数据”),但不包含用户敏感信息(如密码);
- 设计目标是 “短期可用、快速失效”,降低泄露风险。
2. Refresh Token(刷新令牌)
- 本质:长期有效的 “刷新凭证”,相当于用户的 “永久续约凭证”。
- 核心作用:Access Token 过期后,无需用户重新登录,客户端可通过 Refresh Token 向认证服务器申请新的 Access Token。
- 关键特性:
- 有效期很长(通常 7 天~30 天,甚至更久);
- 仅用于 “刷新 Access Token”,不能直接访问业务资源;
- 安全性要求极高,通常存储在服务器端(或客户端的安全存储,如移动端 Keychain),避免泄露。
二、为什么需要 “双令牌”?(核心设计逻辑)
如果只使用 Access Token:
- 若 Access Token 有效期长 → 泄露后风险极高(攻击者可长期访问资源);
- 若 Access Token 有效期短 → 用户需要频繁登录,体验极差。
“双令牌” 机制完美解决这个矛盾:
- Access Token 短期有效:即使泄露,攻击者只能在短时间内滥用;
- Refresh Token 长期有效:用户无需频繁登录,过期后才需重新验证;
- 职责分离:Access Token 管 “访问资源”,Refresh Token 管 “续期 Access Token”,安全性和体验兼顾。
三、核心区别对比表
| 特性 | Access Token | Refresh Token |
|---|---|---|
| 核心用途 | 直接访问受保护资源(如 API) | 申请新的 Access Token(续期) |
| 有效期 | 短期(10min~2h) | 长期(7 天~30 天) |
| 安全性要求 | 中等(短期失效,风险低) | 极高(长期有效,泄露则后果严重) |
| 存储位置 | 客户端(前端 localStorage/cookie、后端内存) | 优先服务器端存储(数据库),客户端仅存加密后的凭证 |
| 携带场景 | 每次业务请求(如 /api/data、/case/export) | 仅 Access Token 过期时,向认证接口请求续期 |
| 包含信息 | 用户身份标识、权限范围(如 role:admin) | 仅包含 “刷新权限” 相关标识(不包含业务权限) |
| 泄露影响 | 短期风险(攻击者临时访问资源) | 长期风险(攻击者可无限续期 Access Token) |
四、典型工作流程(以 API 调用为例)
以你之前关注的 “DeepSeek API 调用”“案件报告导出接口” 为例,双令牌的完整流程如下:
plaintext
┌───────────┐ ┌───────────────┐ ┌───────────────┐
│ 客户端 │ │ 认证服务器 │ │ 业务服务器 │
└─────┬─────┘ └───────┬───────┘ └───────┬───────┘│ │ │
1. 登录请求 →─────────────>│ │(用户名+密码/验证码) │ ││ │ │
2. 发放双令牌 ←─────────────│ │(Access Token + Refresh Token) ││ │ │
3. 业务请求 →────────────────────────────────────>│(携带 Access Token) ││ │ │
4. 验证令牌 →──────────────>│ ││ │ │
5. 验证通过 ←──────────────│ ││ │ │
6. 返回业务数据 ←────────────────────────────────────││ │ │
7. Access Token 过期 →────────────────────────────>│(携带失效 Token) ││ │ │
8. 返回“Token 过期” ←────────────────────────────││ │ │
9. 刷新 Token 请求 →─────────────>│ │(携带 Refresh Token) │ ││ │ │
10. 验证 Refresh Token →──────────│ ││ │ │
11. 发放新 Token ←─────────────│ │(新 Access Token + 可选新 Refresh Token) ││ │ │
12. 重新发起业务请求 →────────────────────────────>│(携带新 Access Token) │
关键步骤说明:
- 步骤 1-2:用户首次登录后,认证服务器验证身份,发放双令牌(Access Token 有效期 30 分钟,Refresh Token 有效期 7 天);
- 步骤 3-6:客户端携带 Access Token 调用业务接口(如导出案件报告),业务服务器验证 Token 有效后返回数据;
- 步骤 7-11:Access Token 过期后,客户端用 Refresh Token 申请新 Token,无需用户重新登录;
- 若 Refresh Token 也过期(如用户 7 天未操作),则客户端引导用户重新登录。
五、Spring Boot 实战:双令牌实现(核心代码)
以下是 Spring Boot 中基于 JWT(JSON Web Token) 实现双令牌的核心代码(生产级简化版),适配 API 接口的认证与续期。
1. 依赖引入(JWT 工具包)
xml
<!-- pom.xml -->
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.11.5</version>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.5</version><scope>runtime</scope>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><version>0.11.5</version><scope>runtime</scope>
</dependency>
2. 配置类(JWT 密钥、有效期)
java
运行
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;@Configuration
@ConfigurationProperties(prefix = "jwt")
public class JwtConfig {// Access Token 密钥(生产环境需用环境变量注入,避免硬编码)private String accessSecret = "your-access-secret-key-32bytes-long";// Refresh Token 密钥(需与 Access Token 密钥不同)private String refreshSecret = "your-refresh-secret-key-32bytes-long";// Access Token 有效期(30 分钟,单位:秒)private long accessExpire = 1800;// Refresh Token 有效期(7 天,单位:秒)private long refreshExpire = 604800;// Getter + Setterpublic String getAccessSecret() { return accessSecret; }public void setAccessSecret(String accessSecret) { this.accessSecret = accessSecret; }public String getRefreshSecret() { return refreshSecret; }public void setRefreshSecret(String refreshSecret) { this.refreshSecret = refreshSecret; }public long getAccessExpire() { return accessExpire; }public void setAccessExpire(long accessExpire) { this.accessExpire = accessExpire; }public long getRefreshExpire() { return refreshExpire; }public void setRefreshExpire(long refreshExpire) { this.refreshExpire = refreshExpire; }
}
3. JWT 工具类(生成 / 验证 Token)
java
运行
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import javax.crypto.SecretKey;
import java.util.Date;@Component
public class JwtUtils {@Autowiredprivate JwtConfig jwtConfig;// 生成 Access Token(包含用户ID、角色)public String generateAccessToken(Long userId, String role) {SecretKey key = Keys.hmacShaKeyFor(jwtConfig.getAccessSecret().getBytes());return Jwts.builder().claim("userId", userId) // 自定义字段:用户ID.claim("role", role) // 自定义字段:角色权限.setExpiration(new Date(System.currentTimeMillis() + jwtConfig.getAccessExpire() * 1000)) // 过期时间.signWith(key) // 签名.compact();}// 生成 Refresh Token(仅包含用户ID,用于续期)public String generateRefreshToken(Long userId) {SecretKey key = Keys.hmacShaKeyFor(jwtConfig.getRefreshSecret().getBytes());return Jwts.builder().claim("userId", userId).setExpiration(new Date(System.currentTimeMillis() + jwtConfig.getRefreshExpire() * 1000)).signWith(key).compact();}// 验证 Access Token 有效性public boolean validateAccessToken(String token) {try {SecretKey key = Keys.hmacShaKeyFor(jwtConfig.getAccessSecret().getBytes());