刷新令牌与过期
实现安全的令牌续期。
刷新令牌与过期 是 CoddyKit 上的免费 C# Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C# Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C# Academy 课程共包含 4 节课。
有效期问题
短时有效的访问令牌很安全,但使用起来不方便:每 15 分钟强制用户重新登录会带来糟糕的用户体验。
解决方案是使用刷新令牌——一种有效期更长的独立凭据,用于获取新的访问令牌,而无需重新输入密码。
// access token: 15 minutes
// refresh token: 7-30 days什么是刷新令牌
刷新令牌通常是一个不透明的随机字符串(不是 JWT)。由于它的有效期较长,因此必须存储在服务器端,以便撤销。
请使用加密安全的 RNG 生成它。
public static string GenerateRefreshToken()
{
var bytes = new byte[64];
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(bytes);
return Convert.ToBase64String(bytes);
}存储刷新令牌
为每个刷新令牌持久化存储其所有者、有效期和撤销标志。这样就可以在用户注销或令牌被盗时使其失效。
public class RefreshToken
{
public int Id { get; set; }
public string Token { get; set; } = default!;
public int UserId { get; set; }
public DateTime ExpiresAt { get; set; }
public bool IsRevoked { get; set; }
}登录时签发两个令牌
登录成功后,返回短时有效的访问令牌和一个新的刷新令牌。将刷新令牌保存到数据库中。
var access = tokenService.CreateAccessToken(user);
var refresh = new RefreshToken
{
Token = TokenService.GenerateRefreshToken(),
UserId = user.Id,
ExpiresAt = DateTime.UtcNow.AddDays(7)
};
db.RefreshTokens.Add(refresh);
await db.SaveChangesAsync();
return Results.Ok(new { access, refresh = refresh.Token });刷新端点
专用的 /refresh 端点接受刷新令牌,并在令牌有效时签发全新的访问令牌。
app.MapPost("/refresh", async (RefreshDto dto, AppDb db, TokenService tokens) =>
{
var stored = await db.RefreshTokens
.SingleOrDefaultAsync(r => r.Token == dto.RefreshToken);
if (stored is null || stored.IsRevoked ||
stored.ExpiresAt < DateTime.UtcNow)
return Results.Unauthorized();
var user = await db.Users.FindAsync(stored.UserId);
var access = tokens.CreateAccessToken(user!);
return Results.Ok(new { access });
});刷新令牌轮换
轮换可以强化整个流程:每次使用刷新令牌时,都撤销当前令牌并签发一个新的令牌。这样,被盗且已经使用过的令牌就会失效。
stored.IsRevoked = true; // burn the old one
var next = new RefreshToken
{
Token = TokenService.GenerateRefreshToken(),
UserId = stored.UserId,
ExpiresAt = DateTime.UtcNow.AddDays(7)
};
db.RefreshTokens.Add(next);
await db.SaveChangesAsync();检测令牌重用
借助轮换机制,您可以检测令牌是否被盗:如果再次提交已经撤销的令牌,应将其视为安全漏洞,并撤销该用户的整个令牌家族。
if (stored.IsRevoked)
{
// This token was already used - likely stolen.
await RevokeAllUserTokens(stored.UserId, db);
return Results.Unauthorized();
}注销与撤销
注销只需撤销当前刷新令牌即可。访问令牌会在几分钟内自行过期。
app.MapPost("/logout", async (RefreshDto dto, AppDb db) =>
{
var stored = await db.RefreshTokens
.SingleOrDefaultAsync(r => r.Token == dto.RefreshToken);
if (stored is not null) stored.IsRevoked = true;
await db.SaveChangesAsync();
return Results.Ok();
}).RequireAuthorization();在客户端何处存储令牌
对于浏览器,最安全的方式是将刷新令牌存储在HttpOnly、Secure Cookie 中,使 JavaScript 无法读取它,从而降低 XSS 窃取的风险。
短时有效的访问令牌可以保存在内存中。
Response.Cookies.Append("refresh", refresh.Token, new CookieOptions
{
HttpOnly = true,
Secure = true,
SameSite = SameSiteMode.Strict,
Expires = refresh.ExpiresAt
});清理已过期的令牌
刷新令牌会不断累积。请运行定期后台任务(例如托管服务),删除已过期或已撤销的行。
await db.RefreshTokens
.Where(r => r.ExpiresAt < DateTime.UtcNow || r.IsRevoked)
.ExecuteDeleteAsync();选择有效期
在安全性和便利性之间取得平衡:
- 访问令牌:5 到 15 分钟,限制令牌泄露后的暴露时间。
- 刷新令牌:数小时到数周,具体取决于敏感程度。
- 对于有效期超过一天的令牌,请使用轮换机制。
AccessTokenLifetime = TimeSpan.FromMinutes(15);
RefreshTokenLifetime = TimeSpan.FromDays(7);快速检查
测试您对刷新流程的理解。
回顾
您已经实现了刷新令牌流程:
- 签发一个短时有效的访问令牌,以及一个较长、不可读、存储在服务器端的刷新令牌。
/refresh端点使用有效的刷新令牌换取新的访问令牌。- 轮换和重用检测机制可以增强对令牌被盗的防护。
- 将刷新令牌存储在 HttpOnly Cookie 中,并清理已过期的行。
JWT 认证课程到此完成。
常见问题解答
「刷新令牌与过期」课时是免费的吗?
是的 — 「刷新令牌与过期」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C# Academy 课程的其余内容,请升级到 CoddyKit PRO。 C# Academy 课程共包含 4 节课。
「刷新令牌与过期」这节课中我会学到什么?
实现安全的令牌续期。 你通过在浏览器中直接运行的动手代码来练习 C# Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 C# Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 C# Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「刷新令牌与过期」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 C# Academy 课中编写并运行代码吗?
能。每节 C# Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。