Web API令牌授权中用户登出后销毁令牌的解决方案
解决方案:处理登出后令牌仍可使用的问题
这确实是无状态自定义令牌的典型痛点——一旦签发出去,在过期前默认没法主动失效,尤其你还不能用会话这种有状态方案,也没法用OWIN。结合你的Web API架构,给你几个实用的解决思路:
1. 维护分布式令牌黑名单/撤销列表
这是最通用的无状态解决方案,适配需要扩展的场景:
- 选一个分布式存储(比如Redis、SQL Server)来存放已撤销的令牌。用户登出时,把当前令牌存入这个列表,设置过期时间和令牌本身的15分钟有效期一致,避免列表无限膨胀。
- 在你的
SendAsync验证逻辑里,新增一步检查:解密验证通过后,先查黑名单里有没有当前令牌,如果有直接返回未授权。 - 代码示例(假设用Redis):
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { AuthenticationHeaderValue authorization = request.Headers.Authorization; if (authorization != null) { string token = authorization.Parameter; // 先检查黑名单 var redis = ConnectionMultiplexer.Connect("your-redis-connection"); var db = redis.GetDatabase(); if (db.KeyExists(token)) { return Task.FromResult(request.CreateResponse(HttpStatusCode.Unauthorized, "Token has been revoked")); } // 再做原有的解密验证逻辑 var decryptedData = Encryption.DecryptData(token); if (decryptedData.Equals(ConfigurationManager.AppSettings["TokenKey"].ToString())) { // 返回响应 } } // 其他逻辑... }
- 优势:完全无状态,支持横向扩展,对现有代码改动小;Redis的查询性能极高,几乎不会影响API响应速度。
2. 缩短访问令牌有效期 + 搭配刷新令牌
通过缩小令牌的有效窗口降低风险,同时用刷新令牌保障用户体验:
- 把访问令牌的有效期缩短到1-2分钟(越短越安全),同时在用户登录时额外签发一个刷新令牌(有效期可以设为1小时或更久)。
- 网站端用访问令牌调用API,当访问令牌过期时,用刷新令牌向API的专门接口请求新的访问令牌。用户登出时,把刷新令牌加入黑名单,使其无法再获取新的访问令牌。
- 注意:刷新令牌要存在网站的HttpOnly Cookie里,避免XSS攻击,同时最好设置为一次性使用(即每次用刷新令牌换访问令牌时,签发新的刷新令牌并作废旧的)。
3. 令牌嵌入用户动态状态标识
利用现有用户数据库实现令牌失效验证,不需要额外存储:
- 在加密的令牌内容里加入一个
TokenVersion(令牌版本号)字段,同时在用户表中新增对应的TokenVersion字段,默认值设为1。 - 用户登出时,更新该用户的
TokenVersion(比如加1)。API验证令牌时,解密出TokenVersion,和数据库中用户的当前版本号对比,如果不一致,说明令牌已失效。 - 代码示例:
// 假设解密后得到的TokenData包含Username和TokenVersion protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { AuthenticationHeaderValue authorization = request.Headers.Authorization; if (authorization != null) { string token = authorization.Parameter; TokenData tokenData = Encryption.DecryptToken(token); // 自定义解密方法,返回包含用户信息和版本号的对象 using(var db = new YourDbContext()){ var user = db.Users.FirstOrDefault(u => u.Username == tokenData.Username); if(user == null || user.TokenVersion != tokenData.TokenVersion){ return Task.FromResult(request.CreateResponse(HttpStatusCode.Unauthorized, "Token is invalid or revoked")); } } // 原有的密钥、过期时间验证逻辑... // 返回响应 } // 其他逻辑... }
- 优势:不需要额外存储资源,利用现有数据库;劣势:每次API请求都要查询用户表,性能略低于Redis方案,适合用户量不大的场景。
推荐方案
如果你的项目需要扩展,优先选分布式黑名单方案(Redis),性能和扩展性都最优;如果不想引入额外存储,令牌版本号方案是不错的替代。
内容的提问来源于stack exchange,提问作者Ravi Kiran
相关产品推荐
相关产品推荐

