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

如何清理IdentityServer4中Cookie的冗余声明以节省空间

问题:IdentityServer4 id_token流程下Cookie体积过大的优化与流程验证

我在使用IdentityServer4的id_token流程,目前仅存储约2个自定义声明,但Cookie已接近3623/4093字节的大小限制。显然IdentityServer4向Cookie中注入了大量声明,希望至少移除占用过多空间的nonce声明。

客户端应用配置

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
var openIdConnectConfig = builder.Configuration.GetSection("OpenIdConnect");

builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = "cookies";
    options.DefaultChallengeScheme = "oidc";
})
    .AddCookie("cookies", options =>
    {
        options.Cookie.Name = "MyCookie";
        options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
        options.Cookie.HttpOnly = true;
        options.ExpireTimeSpan = TimeSpan.FromMinutes(5);
    })
    .AddOpenIdConnect("oidc", options =>
    {
        options.Authority = openIdConnectConfig["Authority"];
        options.ClientId = openIdConnectConfig["ClientId"];
        options.ClientSecret = openIdConnectConfig["ClientSecret"];

        //options.Scope.Clear();
        options.Scope.Add("openid");
        options.Scope.Add("profile");

        options.MapInboundClaims = false;
        options.GetClaimsFromUserInfoEndpoint = true;

        options.SaveTokens = false; // what does this even do?

        // is this the correct flow?
        options.ResponseType = "id_token";

        // these have no effect...
        options.ClaimActions.DeleteClaim("nonce");
        options.ClaimActions.DeleteClaim("aud");
        options.ClaimActions.DeleteClaim("azp");
        options.ClaimActions.DeleteClaim("acr");
        options.ClaimActions.DeleteClaim("amr");
        options.ClaimActions.DeleteClaim("iss");
        options.ClaimActions.DeleteClaim("iat");
        options.ClaimActions.DeleteClaim("nbf");
        options.ClaimActions.DeleteClaim("exp");
        options.ClaimActions.DeleteClaim("at_hash");
        options.ClaimActions.DeleteClaim("c_hash");
        options.ClaimActions.DeleteClaim("auth_time");
        options.ClaimActions.DeleteClaim("ipaddr");
        options.ClaimActions.DeleteClaim("platf");
        options.ClaimActions.DeleteClaim("ver");
        options.ClaimActions.DeleteClaim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress");
        options.ClaimActions.DeleteClaim("AspNet.Identity.SecurityStamp");
        options.ClaimActions.DeleteClaim("role");
        options.ClaimActions.DeleteClaim("preferred_username");
        options.ClaimActions.DeleteClaim("email_verified");

        // I added this just so `User` has `Name` property.
        options.TokenValidationParameters = new TokenValidationParameters
        {
            NameClaimType = "name",
            RoleClaimType = "role"
        };

        options.UsePkce = true; 

        options.CallbackPath = "/signin-oidc";
        options.AccessDeniedPath = "/AccessDenied";
        
        // I got this from somewhere when IdentityServer4 was giving some error about "challenge code", not sure if it's doing anything right now.
        options.Events.OnRedirectToIdentityProvider = context =>
        {
            // only modify requests to the authorization endpoint
            if (context.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication)
            {
                // generate code_verifier
                var codeVerifier = CryptoRandom.CreateUniqueId(32);

                // store codeVerifier for later use
                context.Properties.Items.Add("code_verifier", codeVerifier);

                // create code_challenge
                string codeChallenge;
                using (var sha256 = SHA256.Create())
                {
                    var challengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeVerifier));
                    codeChallenge = Base64Url.Encode(challengeBytes);
                }

                // add code_challenge and code_challenge_method to request
                context.ProtocolMessage.Parameters.Add("code_challenge", codeChallenge);
                context.ProtocolMessage.Parameters.Add("code_challenge_method", "S256");
            }

            return Task.CompletedTask;
        };
    });

认证服务器Startup.cs配置

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

var builder = services.AddIdentityServer(options =>
    {
        options.Events.RaiseErrorEvents = true;
        options.Events.RaiseInformationEvents = true;
        options.Events.RaiseFailureEvents = true;
        options.Events.RaiseSuccessEvents = true;

        // see https://identityserver4.readthedocs.io/en/latest/topics/resources.html
        options.EmitStaticAudienceClaim = true;
    })
    .AddInMemoryIdentityResources(Config.IdentityResources)
    .AddInMemoryApiScopes(Config.ApiScopes)
    .AddInMemoryClients(Config.Clients)
    .AddAspNetIdentity<ApplicationUser>()
    .AddProfileService<MyProfileService>();

认证服务器客户端配置

new Client
{
    ClientId = "kiosk_admin",
    ClientName = "Kiosk Admin",

    ClientSecrets = { new Secret("8696ba8c-b6ba-438a-9211-ee62ceea0144".Sha256()) },
    
    AllowPlainTextPkce = true,
    RequirePkce = true,

    // `id_token` flow matches `implicit` gramt type
    AllowedGrantTypes = GrantTypes.Implicit,

    // where to redirect to after login
    RedirectUris = { "https://localhost:7177/signin-oidc" },

    // where to redirect to after logout
    PostLogoutRedirectUris = { "https://localhost:7177/signout-callback-oidc" },

    FrontChannelLogoutUri = "https://localhost:7177/signout-oidc",

    AllowOfflineAccess = true,

    AllowedScopes = new List<string>
    {
        IdentityServerConstants.StandardScopes.OpenId,
        IdentityServerConstants.StandardScopes.Profile,
        "api1"
    }
}

自定义ProfileService实现

public class MyProfileService : IProfileService
{
    protected UserManager<ApplicationUser> _userManager;
    protected ApplicationDbContext _context;

    public MyProfileService(UserManager<ApplicationUser> userManager, ApplicationDbContext context)
    {
        _userManager = userManager;
        _context = context;
    }

    /// <summary>
    /// user to manually inject desired claims
    /// </summary>
    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        //>Processing
        var user = await _userManager.GetUserAsync(context.Subject);

        if (user != null)
        {
            var claims = _context.UserClaims
                        .Where(x => x.UserId == user.Id)
                        .Select(x => new Claim(x.ClaimType, x.ClaimValue))
                        .ToList();

            claims.Add(new Claim("Name", user.UserName));

            context.IssuedClaims.AddRange(claims.AsEnumerable());
            context.IssuedClaims.RemoveAll(c => 
                c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" // we already have an email claim
                || c.Type == "preferred_username" // we already have a name claim
                || c.Type == "amr" // Authentication method reference. It tells you how the user authenticated (e.g., pwd means password). Useful for some scenarios.
                || c.Type == "idp" // Identity provider. Indicates how the user authenticated (e.g., local means they authenticated directly with your server). Useful for some scenarios.
            );
        }
    }

    public async Task IsActiveAsync(IsActiveContext context)
    {
        //>Processing
        var user = await _userManager.GetUserAsync(context.Subject);

        context.IsActive = (user != null) && user.IsActive;
    }
}

我需要一个简单的SSO解决方案,让多个应用共享同一Cookie实现用户认证,同时在各项目中使用基于声明的授权(每个用户角色对应一个独立声明),因此需要为Cookie腾出空间。除了清理冗余声明,若我的实现方式存在错误(比如当前认证流程是否适合我的场景),也请指出。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 14:54:56