You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 09:11:21