Duende Identity Server改用数据库后缺失Profile声明问题求助
问题背景
测试Duende Identity Server的6_JS_with_backend示例对接Google IDP时,内存模式运行正常;切换为Entity Framework数据库模式后,返回的声明中缺失name、givenname、familyname等Profile相关信息,切回内存模式则恢复正常。数据库已通过EF迁移成功创建,无执行错误,JavaScript客户端未做任何修改。
相关代码
ConfigureServices配置代码
public static WebApplication ConfigureServices(this WebApplicationBuilder builder) { var migrationsAssembly = typeof(Program).Assembly.GetName().Name; builder.Services.AddRazorPages(); string connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(connectionString)); builder.Services.AddIdentity<ApplicationUser, ApplicationRole>() .AddEntityFrameworkStores<ApplicationDbContext>(); builder.Services.AddIdentityServer(options => { options.EmitStaticAudienceClaim = true; }) .AddConfigurationStoreCache() .AddConfigurationStore(options => { options.EnablePooling = true; options.ConfigureDbContext = builder => { builder.UseSqlServer(connectionString, configure => { configure.MigrationsAssembly(migrationsAssembly); }); }; }) .AddOperationalStore(options => { options.EnablePooling = true; options.ConfigureDbContext = builder => { builder.UseSqlServer(connectionString, configure => { configure.MigrationsAssembly(migrationsAssembly); }); }; options.EnableTokenCleanup = true; options.TokenCleanupInterval = (int)TimeSpan.FromDays(20).TotalSeconds; }); builder.Services.AddIdentityServerConfiguration(opt => opt.LicenseKey = "<TEST>").AddClientConfigurationStore(); builder.Services.AddConfigurationDbContext<ConfigurationDbContext>(options => { options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString); }); var configuration = builder.Configuration; // Put in Keyvault builder.Services.AddAuthentication() .AddGoogle("Google", options => { options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; options.ClientId = configuration["Authentication:Google:ClientId"]!; options.ClientSecret = configuration["Authentication:Google:ClientSecret"]!; }); return builder.Build(); }
ApplicationRole类
using Microsoft.AspNetCore.Identity; namespace IdentityServer.Models { public class ApplicationRole : IdentityRole { } }
ApplicationUser类
using Microsoft.AspNetCore.Identity; namespace IdentityServer.Models { public class ApplicationUser : IdentityUser { public string? TenantId { get; set; } public string? License { get; set; } } }
结果与数据库截图
内存模式下返回结果:

数据库模式下返回结果:

数据库配置截图(client、clientscopes、identityresources已存在):

问题分析与解决办法
核心原因
内存模式下IdentityServer默认会自动加载标准Identity资源(如profile)并包含对应声明,但切换到数据库模式后,需确保数据库中IdentityResources的配置完整,且客户端已授权对应范围。
解决步骤
检查IdentityResources的UserClaims配置
查看数据库IdentityResources表中Profile资源的UserClaims字段,确认是否包含name、given_name、family_name等声明类型。若缺失,需手动添加这些声明到该资源的UserClaims集合中。确认客户端AllowedScopes包含profile范围
检查Clients表中对应客户端的AllowedScopes字段,确保已添加profile范围,否则IdentityServer不会返回该范围下的声明。显式配置Google认证的声明映射
修改Google认证配置,显式请求Profile相关范围并映射声明:.AddGoogle("Google", options => { options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; options.ClientId = configuration["Authentication:Google:ClientId"]!; options.ClientSecret = configuration["Authentication:Google:ClientSecret"]!; // 显式请求Profile范围 options.Scope.Add("profile"); // 映射Google返回的JSON声明到标准Claim类型 options.ClaimActions.MapJsonKey(JwtClaimTypes.GivenName, "given_name"); options.ClaimActions.MapJsonKey(JwtClaimTypes.FamilyName, "family_name"); options.ClaimActions.MapJsonKey(JwtClaimTypes.Name, "name"); });初始化标准Identity资源到数据库
若数据库未自动初始化标准资源,可在应用启动时添加种子数据:using (var scope = app.Services.CreateScope()) { var context = scope.ServiceProvider.GetRequiredService<ConfigurationDbContext>(); if (!context.IdentityResources.Any(r => r.Name == "profile")) { var profileResource = new IdentityResource("profile", "User Profile", new List<string> { JwtClaimTypes.Name, JwtClaimTypes.GivenName, JwtClaimTypes.FamilyName }); context.IdentityResources.Add(profileResource.ToEntity()); context.SaveChanges(); } }配置Identity的Claim类型映射
确保ASP.NET Core Identity使用标准JWT Claim类型:builder.Services.Configure<IdentityOptions>(options => { options.ClaimsIdentity.UserNameClaimType = JwtClaimTypes.Name; options.ClaimsIdentity.GivenNameClaimType = JwtClaimTypes.GivenName; options.ClaimsIdentity.FamilyNameClaimType = JwtClaimTypes.FamilyName; });
内容的提问来源于stack exchange,提问作者CloudAnywhere

