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

CookieAuthentication持续重建Cookie与身份验证票据问题排查

Blazor Server认证Cookie重复重建问题排查与解决

环境与配置

  • Blazor Server应用,默认认证方案为Cookie,挑战方案为OpenIdConnect
  • 基于SQL Server分布式缓存实现ITicketStore
  • 访问令牌有效期48小时,刷新令牌有效期30天

Cookie配置代码

services.AddOptions<CookieAuthenticationOptions>(CookieAuthenticationDefaults.AuthenticationScheme)
    .Configure<ITicketStore>((options, store) =>
    {
        options.ExpireTimeSpan = TimeSpan.FromDays(14);
        options.SlidingExpiration = true;
        options.SessionStore = store;
    });

认证服务配置代码

services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
    options.Authority = authority;
    options.ClientId = clientId;
    options.ClientSecret = clientSecret;
    options.ResponseType = OpenIdConnectResponseType.Code;
    options.ResponseMode = OpenIdConnectResponseMode.FormPost;
    options.GetClaimsFromUserInfoEndpoint = true;
    options.MapInboundClaims = false;
    options.SaveTokens = true;
    options.UseTokenLifetime = false;
    options.UseSecurityTokenValidator = true;
    options.Scope.Add(OpenIdConnectScope.OpenIdProfile);
    options.Scope.Add(OpenIdConnectScope.Email);
    options.Scope.Add(OpenIdConnectScope.OfflineAccess);
    options.TokenValidationParameters = new TokenValidationParameters
    {
        NameClaimType = "name",
        RoleClaimType = "role"
    };
});

登录逻辑代码

public async Task OnGet(string redirectUri)
{
    await HttpContext.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties 
    { 
        RedirectUri = redirectUri,                
        IsPersistent = true,
    });
}

问题现象

用户每次访问应用时,认证Cookie都会被重建,分布式缓存中持续新增身份验证票据条目。已配置Cookie为持久化且有效期14天,该现象不符合预期。

更新:ITicketStore实现代码

public class AuthenticationTicketStore(
    IDistributedCache cache,
    ILogger<AuthenticationTicketStore> logger) : ITicketStore
{
    private const string KeyPrefix = "AuthSessionStore-";
    private readonly TicketSerializer ticketSerializer = TicketSerializer.Default;

    public async Task<string> StoreAsync(AuthenticationTicket ticket)
    {
        var key = $"{KeyPrefix}{Guid.NewGuid():N}";
        await RenewAsync(key, ticket);

        return key;
    }

    public Task RenewAsync(string key, AuthenticationTicket ticket)
    {
        if (ticket == null)
        {
            throw new ArgumentNullException(nameof(ticket));
        }

        var options = new DistributedCacheEntryOptions();

        var expiresUtc = ticket.Properties.ExpiresUtc;
        if (expiresUtc.HasValue)
        {
            options.SetAbsoluteExpiration(expiresUtc.Value);
        }

        if (ticket.Properties.AllowRefresh ?? false)
        {
            options.SetSlidingExpiration(TimeSpan.FromMinutes(60));
        }

        return cache.SetAsync(key, ticketSerializer.Serialize(ticket), options);
    }

    public async Task<AuthenticationTicket> RetrieveAsync(string key)
    {
        var value = await cache.GetAsync(key);
        return value != null ? ticketSerializer.Deserialize(value) : null;
    }

    public Task RemoveAsync(string key) => cache.RemoveAsync(key);
}

核心问题分析

  1. 配置覆盖问题:使用AddOptions配置CookieAuthenticationOptions后,后续调用AddCookie会重置默认配置,导致SessionStore、ExpireTimeSpan等设置未生效,中间件无法复用原有缓存票据,只能每次创建新票据。
  2. 票据过期时间未同步:虽然设置了UseTokenLifetime = false,但OpenIdConnect中间件未将Cookie的14天有效期同步到AuthenticationTicket的ExpiresUtc属性,SlidingExpiration逻辑失效,导致每次请求都触发票据重建。
  3. 缓存调用逻辑异常:正常滑动过期时,中间件应调用RenewAsync更新现有缓存条目,而非StoreAsync生成新Key。若StoreAsync被频繁调用,说明中间件判定当前票据无效,需要创建新票据。

解决方案

1. 合并Cookie配置,避免覆盖

将CookieAuthenticationOptions的配置直接写入AddCookie方法,确保配置生效:

services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
    options.ExpireTimeSpan = TimeSpan.FromDays(14);
    options.SlidingExpiration = true;
    // 直接注入SessionStore,避免配置被覆盖
    options.SessionStore = sp => sp.GetRequiredService<ITicketStore>();
})
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
    // 原有OpenIdConnect配置...
});

2. 强制设置票据过期时间

在OpenIdConnect的OnTokenValidated事件中,手动指定票据的过期时间,确保与Cookie有效期一致:

options.Events = new OpenIdConnectEvents
{
    OnTokenValidated = context =>
    {
        // 覆盖ID令牌的有效期,使用Cookie配置的14天
        context.Properties.ExpiresUtc = DateTimeOffset.UtcNow.AddDays(14);
        context.Properties.IsPersistent = true;
        context.Properties.AllowRefresh = true;
        return Task.CompletedTask;
    }
};

3. 验证ITicketStore注册

确保ITicketStore已正确注册到DI容器:

services.AddScoped<ITicketStore, AuthenticationTicketStore>();

4. 排查缓存读取逻辑

在RetrieveAsync方法中添加日志,检查缓存中是否能正确读取到原有票据,确认缓存Key是否匹配、缓存是否提前失效。

内容的提问来源于stack exchange,提问作者Ivan Debono

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 15:34:53