Identity Server 4单用户单令牌限制:新登录后旧会话失效需求咨询
实现单用户单有效令牌的方案
要实现用户新登录后旧令牌失效、仅保留当前会话令牌的需求,需要从IdentityServer4服务端配置和API资源端验证配置两方面入手:
一、IdentityServer4服务端配置调整
1. 配置客户端的令牌与会话策略
在定义客户端(Client)时,设置以下关键参数,确保每次新登录生成独立会话,且旧会话令牌可被撤销:
new Client { ClientId = "your-client-id", // 其他基础配置... AccessTokenType = AccessTokenType.Jwt, // 若用Reference令牌,API会实时验证,撤销更及时 RefreshTokenUsage = TokenUsage.OneTimeOnly, // 每次刷新令牌后旧refresh token失效 AbsoluteRefreshTokenLifetime = 86400, // 令牌绝对过期时间,根据业务调整 SlidingRefreshTokenLifetime = 3600, // 滑动过期时间 AllowOfflineAccess = true, // 启用refresh token支持 UpdateAccessTokenClaimsOnRefresh = true, EnableLocalLogin = true, // 关键:设置每次登录生成新的会话ID,旧会话标记为无效 AlwaysSendClientClaims = true, AlwaysIncludeUserClaimsInIdToken = true }
2. 启用会话跟踪与令牌撤销
在IdentityServer的启动配置中,启用会话存储(生产环境建议用分布式缓存如Redis),并在登录逻辑中主动撤销旧会话:
services.AddIdentityServer() .AddInMemoryClients(Clients) .AddInMemoryApiResources(ApiResources) .AddInMemoryIdentityResources(IdentityResources) .AddAspNetIdentity<ApplicationUser>() .AddDistributedCache(options => { // 配置分布式缓存,示例为Redis options.UseRedis("your-redis-connection-string"); }) .AddOperationalStore(options => { options.ConfigureDbContext = builder => builder.UseSqlServer(Configuration.GetConnectionString("IdentityServerDb")); options.EnableTokenCleanup = true; options.TokenCleanupInterval = 3600; // 定期清理过期令牌 });
登录成功后主动清理旧会话:
// 在登录逻辑中,获取用户所有旧会话并撤销 var user = await _userManager.FindByNameAsync(username); var sessions = await _interaction.GetAllUserSessionsAsync(user.Id); foreach (var session in sessions.Where(s => s.SessionId != context.SessionId)) { await _interaction.RevokeSessionAsync(session.SessionId); }
二、API资源端验证配置调整
修改现有API验证配置,确保能及时识别失效的旧令牌:
services.AddAuthentication("Bearer") .AddIdentityServerAuthentication("Bearer", options => { options.Authority = Configuration["IdentityServerUri"]; options.EnableCaching = true; options.CacheDuration = TimeSpan.FromMinutes(1); // 缩短缓存时间,加快失效感知 options.DiscoveryDocumentRefreshInterval = TimeSpan.FromMinutes(5); // 关键配置:启用严格的令牌生命周期验证 options.TokenValidationParameters = new TokenValidationParameters { ValidateLifetime = true, ClockSkew = TimeSpan.Zero, // 关闭时钟偏差,严格验证过期时间 NameClaimType = JwtClaimTypes.Name, RoleClaimType = JwtClaimTypes.Role }; // 若使用Reference令牌,启用验证端点实时验证 // options.ValidationMode = ValidationMode.ValidationEndpoint; });
可选:添加自定义会话验证策略
如果用JWT令牌,可添加自定义授权策略,实时验证会话有效性:
// 添加自定义授权策略 services.AddAuthorization(options => { options.AddPolicy("SingleActiveSession", policy => { policy.RequireAuthenticatedUser(); policy.AddRequirements(new SingleActiveSessionRequirement()); }); }); // 实现自定义需求处理程序 public class SingleActiveSessionHandler : AuthorizationHandler<SingleActiveSessionRequirement> { private readonly IHttpClientFactory _httpClientFactory; private readonly IConfiguration _configuration; public SingleActiveSessionHandler(IHttpClientFactory httpClientFactory, IConfiguration configuration) { _httpClientFactory = httpClientFactory; _configuration = configuration; } protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, SingleActiveSessionRequirement requirement) { var sessionId = context.User.FindFirst(JwtClaimTypes.SessionId)?.Value; if (string.IsNullOrEmpty(sessionId)) { context.Fail(); return; } // 调用IdentityServer的会话验证接口,检查会话是否有效 var client = _httpClientFactory.CreateClient(); var response = await client.GetAsync($"{_configuration["IdentityServerUri"]}/connect/checksession?session_id={sessionId}"); context.Succeed(response.IsSuccessStatusCode ? requirement : null); } } // 注册处理程序 services.AddScoped<IAuthorizationHandler, SingleActiveSessionHandler>();
在API接口上应用该策略:
[Authorize(Policy = "SingleActiveSession")] [ApiController] [Route("api/[controller]")] public class YourController : ControllerBase { // 接口逻辑 }
三、关键注意事项
- 令牌类型选择:Reference令牌失效即时但性能有损耗;JWT令牌性能好,需依赖会话ID验证或令牌黑名单确保旧令牌及时失效。
- 缓存策略:API端缓存时长不宜过长,避免旧令牌失效后仍被缓存通过验证。
- 会话存储:生产环境必须用分布式缓存存储会话和令牌信息,避免集群环境下状态不一致。
内容的提问来源于stack exchange,提问作者Mohsin Ali
相关产品推荐
相关产品推荐

