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

如何控制ASP.NET Core Cookie认证序列化以减小Cookie体积?

解决ASP.NET Core OIDC Cookie过大导致Chrome拒绝的问题

针对你遇到的ASP.NET Core 2.2 MVC OIDC Cookie体积超标、被拆分后Chrome偶尔拒绝的问题,结合你分析出的几个核心原因,我给你整理了针对性的解决方案:


1. 重新解析id_token生成精简Claim,避免冗余序列化

默认情况下,ASP.NET Core OIDC中间件会给每个Claim带上完整的颁发者URI和XML数据类型URI(比如http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name),这些冗余信息会大幅增加Cookie体积。我们可以在OnTokenValidated事件里手动解析id_token,生成只包含必要信息的精简Claim:

.AddOpenIdConnect("Oidc", o =>
{
    // 你的其他OIDC配置...
    o.Events = new OpenIdConnectEvents
    {
        OnTokenValidated = async context =>
        {
            var jwtHandler = new JwtSecurityTokenHandler();
            if (jwtHandler.ReadJwtToken(context.SecurityToken.RawData) is JwtSecurityToken jwtToken)
            {
                // 只保留你需要的声明,自定义ValueType和Issuer(用短标识替代长URI)
                var trimmedClaims = jwtToken.Claims
                    .Where(c => new[] { "sub", "name", "email", "role" }.Contains(c.Type)) // 按需过滤
                    .Select(c => new Claim(c.Type, c.Value, ClaimValueTypes.String, "MyAppIssuer"));

                // 替换原Identity,只保留精简后的Claim
                var newIdentity = new ClaimsIdentity(trimmedClaims, context.Principal.Identity.AuthenticationType);
                context.Principal = new ClaimsPrincipal(newIdentity);
            }
            await Task.CompletedTask;
        }
    };
});

这样每个Claim的序列化体积会从原来的~1900字节大幅压缩,直接解决冗余URI的问题。


2. 配置User Info Endpoint获取精准身份资源

ASP.NET Core OIDC中间件默认会调用User Info Endpoint,但你可以通过配置Scope和事件处理来获取并只保留你需要的声明:

.AddOpenIdConnect("Oidc", o =>
{
    // 清空默认Scope,只添加你需要的资源
    o.Scope.Clear();
    o.Scope.Add("openid"); // 必须的基础Scope
    o.Scope.Add("profile"); // 按需添加
    o.Scope.Add("email");

    // 启用User Info Endpoint调用
    o.GetClaimsFromUserInfoEndpoint = true;

    o.Events.OnUserInformationReceived = context =>
    {
        // 过滤冗余声明,避免重复添加
        var existingClaimTypes = context.Principal.Claims.Select(c => c.Type).ToList();
        foreach (var claim in context.User.RootElement.EnumerateObject())
        {
            var claimType = claim.Name;
            if (!existingClaimTypes.Contains(claimType))
            {
                context.Principal.Identities.First().AddClaim(
                    new Claim(claimType, claim.Value.ToString(), ClaimValueTypes.String));
            }
        }

        // 移除不需要的默认声明(比如一些OIDC自带的冗余字段)
        var redundantClaims = context.Principal.Claims.Where(c => c.Type.StartsWith("nonce") || c.Type == "aud").ToList();
        foreach (var c in redundantClaims)
        {
            context.Principal.Identities.First().RemoveClaim(c);
        }
        return Task.CompletedTask;
    };
});

通过这种方式,你可以精准控制从User Info Endpoint获取的内容,避免不必要的声明占用Cookie空间。


3. 自定义高效的Claim序列化方式

ASP.NET Core默认的Ticket序列化方式比较冗余,你可以替换成更高效的JSON序列化(比如用Newtonsoft.Json):

首先实现自定义的ITicketSerializer:

public class JsonTicketSerializer : ITicketSerializer
{
    private readonly JsonSerializerSettings _settings = new JsonSerializerSettings
    {
        TypeNameHandling = TypeNameHandling.All,
        Formatting = Formatting.None,
        ContractResolver = new CamelCasePropertyNamesContractResolver()
    };

    public byte[] Serialize(AuthenticationTicket ticket)
    {
        var json = JsonConvert.SerializeObject(ticket, _settings);
        return Encoding.UTF8.GetBytes(json);
    }

    public AuthenticationTicket Deserialize(byte[] data)
    {
        var json = Encoding.UTF8.GetString(data);
        return JsonConvert.DeserializeObject<AuthenticationTicket>(json, _settings);
    }
}

然后在Cookie配置中替换默认的序列化器:

services.AddAuthentication()
    .AddCookie("Cookies", o =>
    {
        // 你的其他Cookie配置...
        var dataProtector = services.BuildServiceProvider().GetDataProtector(
            "Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware", 
            "Cookies", 
            "v2");
        o.TicketDataFormat = new TicketDataFormat(dataProtector, new JsonTicketSerializer());
    })
    .AddOpenIdConnect("Oidc", o => { /* 你的OIDC配置 */ });

JSON序列化比默认的二进制序列化更紧凑,能有效减少Cookie体积。


4. 避免access_token等的双重Base64编码

默认情况下,OIDC中间件会把已Base64编码的token(比如JWT)直接存在AuthenticationProperties中,序列化时会再次进行Base64编码,导致体积翻倍。解决方法有两种:

方案一:将token存储到服务器端(推荐)

把access_token、id_token存到Redis等缓存中,Cookie里只存缓存的key:

o.Events.OnTokenValidated = async context =>
{
    // 用Redis存储access_token
    var redisKey = $"token:{Guid.NewGuid()}";
    await _redisCache.SetStringAsync(redisKey, context.TokenResponse.AccessToken, 
        new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30) });
    
    // 把key存入Cookie,移除原始token条目
    context.Properties.SetString("access_token_key", redisKey);
    context.Properties.Items.Remove(".Token.access_token");
    context.Properties.Items.Remove(".Token.id_token");

    await Task.CompletedTask;
};

登出时通过key从Redis取出token即可完成OIDC登出流程。

方案二:解码后存储原始内容

如果必须存在Cookie里,先解码token的payload再存储,避免双重编码:

o.Events.OnTokenValidated = async context =>
{
    var jwtHandler = new JwtSecurityTokenHandler();
    var jwtToken = jwtHandler.ReadJwtToken(context.SecurityToken.RawData);
    
    // 存储payload的JSON字符串,而非Base64的原始token
    context.Properties.SetString("id_token_payload", JsonConvert.SerializeObject(jwtToken.Payload));
    context.Properties.Items.Remove(".Token.id_token");

    await Task.CompletedTask;
};

额外优化:限制单个Cookie大小

在Cookie配置中设置单个Cookie的最大大小,避免分片:

.AddCookie("Cookies", o =>
{
    o.Cookie.SizeLimit = 4096; // Chrome的单个Cookie大小限制为4096字节
    // 其他配置...
})

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 07:51:28