如何在Asp.Net Core 2.0中用IdentityServer4混合流代用户调用API并解决名称为空问题
这种情况我之前也碰到过,核心问题一般是access token里缺少name声明,或者API没有正确将token中的声明映射到User.Identity.Name。咱们一步步来排查解决:
1. 先确认Access Token里有没有name字段
这是最基础的一步,你可以把Web应用获取到的access token复制到jwt.io(本地解析就行,不用上传),查看Payload部分是否包含name键值对。如果没有,那问题出在IdentityServer的配置上;如果有,那就是API的认证配置没处理好。
2. 配置IdentityServer确保颁发的Token包含name声明
2.1 确保IdentityResources包含Profile资源
Profile资源默认包含name声明,在你的Config类里要加上:
public static IEnumerable<IdentityResource> GetIdentityResources() { return new List<IdentityResource> { new IdentityResources.OpenId(), // 必须的OpenID资源 new IdentityResources.Profile() // 包含name、family_name等声明 }; }
2.2 配置API资源包含name声明
让API明确接收name这个用户声明:
public static IEnumerable<ApiResource> GetApiResources() { return new List<ApiResource> { new ApiResource("api1", "My API") { UserClaims = { "name" } // 告诉IdentityServer要把name声明包含到access token里 } }; }
2.3 客户端配置要请求正确的Scope
Web应用作为客户端,必须请求openid、profile以及你的API scope,不然IdentityServer不会颁发包含这些声明的token:
public static IEnumerable<Client> GetClients() { return new List<Client> { new Client { ClientId = "webapp", ClientName = "Web Application", AllowedGrantTypes = GrantTypes.AuthorizationCode, ClientSecrets = { new Secret("secret".Sha256()) }, RedirectUris = { "https://localhost:5001/signin-oidc" }, PostLogoutRedirectUris = { "https://localhost:5001/signout-callback-oidc" }, AllowedScopes = { "openid", "profile", "api1" // 你的API scope }, AllowOfflineAccess = true } }; }
3. 检查Web应用的认证配置
确保Web应用在获取token时请求了正确的scope,并且保存token以便调用API:
services.AddAuthentication(options => { options.DefaultScheme = "Cookies"; options.DefaultChallengeScheme = "oidc"; }) .AddCookie("Cookies") .AddOpenIdConnect("oidc", options => { options.Authority = "https://localhost:5000"; options.ClientId = "webapp"; options.ClientSecret = "secret"; options.ResponseType = "code id_token"; // 显式添加需要的scope options.Scope.Add("profile"); options.Scope.Add("api1"); options.Scope.Add("offline_access"); options.GetClaimsFromUserInfoEndpoint = true; options.SaveTokens = true; // 必须开启,这样才能拿到access token调用API });
4. 配置API正确映射声明到User.Identity.Name
ASP.NET Core默认的NameClaimType是http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name,但IdentityServer颁发的token里用的是短名称name,所以需要在API的认证配置里手动映射:
services.AddAuthentication("Bearer") .AddIdentityServerAuthentication(options => { options.Authority = "https://localhost:5000"; options.RequireHttpsMetadata = false; options.ApiName = "api1"; // 把token里的"name"声明映射到User.Identity.Name options.NameClaimType = "name"; });
5. (可选)自定义ProfileService确保返回name声明
如果你的用户系统里name不是默认的UserName,或者需要自定义声明内容,可以实现IProfileService来控制返回的声明:
public class CustomProfileService : IProfileService { private readonly UserManager<ApplicationUser> _userManager; public CustomProfileService(UserManager<ApplicationUser> userManager) { _userManager = userManager; } public async Task GetProfileDataAsync(ProfileDataRequestContext context) { var user = await _userManager.GetUserAsync(context.Subject); if (user == null) return; // 添加name声明,这里可以换成用户的真实姓名等 var claims = new List<Claim> { new Claim("name", user.UserName) }; context.IssuedClaims.AddRange(claims); } public async Task IsActiveAsync(IsActiveContext context) { var user = await _userManager.GetUserAsync(context.Subject); context.IsActive = user != null; } }
然后在Startup里注册这个服务:
services.AddTransient<IProfileService, CustomProfileService>();
最后验证
做完上面的步骤后,重新启动所有服务,获取新的access token,再去API里查看User.Identity.Name应该就能拿到正确的用户名了。如果还是不行,再用jwt.io检查token里的name字段是否存在,逐步排查每一步的配置有没有遗漏。
内容的提问来源于stack exchange,提问作者Zzz

