共计 2786 个字符,预计需要花费 7 分钟才能阅读完成。
问题背景:Token 重复的安全隐患
在 API 开发中,Token 相当于用户的数字身份证。如果每次生成的 Token 相同,会产生两大风险:

- 会话固定攻击 :攻击者可截获固定 Token 长期冒充用户身份
- 数据泄露 :同一 Token 被多设备使用时,无法区分真实请求来源
典型场景:用户登录后获取的 AccessToken 在有效期内容易被重复生成相同值,导致权限控制失效。
技术方案横向对比
1. Guid 方案
var token = Guid.NewGuid().ToString();
- 优点:实现简单,原生支持
- 缺点:
- 无有效负载,需额外存储用户信息
- 无签名验证,容易被伪造
2. 自定义 Token 方案
var rng = new RNGCryptoServiceProvider();
var bytes = new byte[32];
rng.GetBytes(bytes);
var token = Convert.ToBase64String(bytes);
- 优点:随机性强
- 缺点:
- 需要自行实现验证逻辑
- 缺乏标准化字段(如过期时间)
3. JWT 方案(推荐)
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_config["JwtKey"]);
var tokenDescriptor = new SecurityTokenDescriptor
{Subject = new ClaimsIdentity(new[]
{new Claim(ClaimTypes.Name, username)
}),
Expires = DateTime.UtcNow.AddHours(1),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha256Signature)
};
- 优点:
- 标准化结构(Header.Payload.Signature)
- 内置过期机制
- 支持数字签名防篡改
JWT 核心实现
1. 配置密钥管理
在 appsettings.json 中配置:
{
"Jwt": {
"Key": "至少 256 位的复杂密钥",
"Issuer": "yourdomain.com"
}
}
2. Token 生成服务
public string GenerateJwtToken(string userId)
{
var claims = new List<Claim>
{new Claim(JwtRegisteredClaimNames.Sub, userId),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(issuer: _config["Jwt:Issuer"],
claims: claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: creds);
return new JwtSecurityTokenHandler().WriteToken(token);
}
3. Token 验证中间件
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
});
性能测试数据
使用 BenchmarkDotNet 测试不同算法性能(单位:ops/s):
| 算法 | 16 字节密钥 | 32 字节密钥 | 64 字节密钥 |
|---|---|---|---|
| HMACSHA256 | 45,231 | 43,876 | 42,109 |
| HMACSHA384 | 38,765 | 37,654 | 36,221 |
| HMACSHA512 | 32,109 | 31,456 | 30,887 |
结论:推荐使用 HS256 算法,在安全性和性能间取得平衡。
安全实践指南
1. 密钥轮换策略
// 使用多密钥版本管理
var keys = new List<SymmetricSecurityKey>
{new SymmetricSecurityKey(Encoding.UTF8.GetBytes("当前密钥")),
new SymmetricSecurityKey(Encoding.UTF8.GetBytes("旧密钥"))
};
options.TokenValidationParameters.IssuerSigningKeyResolver =
(token, securityToken, kid, parameters) => keys;
2. Token 刷新流程
sequenceDiagram
Client->>Server: 用过期 Token 请求新 Token
Server->>Server: 验证 RefreshToken 有效性
Server-->>Client: 返回新 AccessToken+RefreshToken
3. 防重放攻击
- 在 Payload 中添加 jti(JWT ID)唯一标识
- 服务端维护短期 Token 黑名单
常见问题排查
- Invalid issuer 错误
- 检查 issuer 字符串是否完全匹配(包括末尾斜杠)
-
开发环境与生产环境配置分离
-
Token 过早失效
- 确保服务器时间同步(NTP 服务)
-
检查时钟偏移设置:
options.TokenValidationParameters.ClockSkew = TimeSpan.FromMinutes(1); -
性能瓶颈
- 避免在 JWT Claims 中存储过多数据
- 对于高频验证场景考虑内存缓存
开放性思考
当设计 Token 过期策略时,如何在以下因素间取得平衡:
- 安全性要求(金融类应用 vs 普通应用)
- 用户体验(频繁重新登录)
- 服务端验证开销
是否可以采用动态过期时间?例如根据用户行为风险评估自动调整 Token 有效期。
正文完
