ABP中自定义UserClaimsPrincipalFactory的CreateAsync未触发问题
问题
我的身份提供商(IDP)在sub声明里返回的是用户名,但ABP框架要求用户ID必须是GUID格式,这导致CurrentUser.IsAuthenticated无法设为true,且用户ID始终为null。我尝试继承AbpUserClaimsPrincipalFactory重写CreateAsync方法,想把NameIdentifier声明替换成从数据库获取的用户GUID,但这个自定义工厂的CreateAsync方法从来没被调用过。
我的代码实现如下:
自定义ClaimsPrincipal工厂:
public class BaselineUserClaimsPrincipalFactory : AbpUserClaimsPrincipalFactory { private readonly ICoreApiService _coreApiService; public BaselineUserClaimsPrincipalFactory( UserManager<IdentityUser> userManager, RoleManager<IdentityRole> roleManager, IOptions<IdentityOptions> options, ICurrentPrincipalAccessor currentPrincipalAccessor, IAbpClaimsPrincipalFactory abpClaimsPrincipalFactory, ICoreApiService coreApiService) : base( userManager, roleManager, options, currentPrincipalAccessor, abpClaimsPrincipalFactory) { _coreApiService = coreApiService; } [UnitOfWork] public async override Task<ClaimsPrincipal> CreateAsync(IdentityUser user) { var principal = await base.CreateAsync(user); var identity = principal.Identities.First(); if (identity != null) { // 省略获取user_guid的逻辑 identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user_guid)); } return principal; } }
模块配置:
public override void PreConfigureServices(ServiceConfigurationContext context) { PreConfigure<IdentityBuilder>(builder => builder.AddClaimsPrincipalFactory<BaselineUserClaimsPrincipalFactory>()); }
原因
AbpUserClaimsPrincipalFactory的CreateAsync方法只在本地用户生成ClaimsPrincipal的时候才会触发,而外部IDP登录的场景下,ABP是通过Claims转换管道处理声明的,根本不会调用这个工厂方法。
解决方案
换用IClaimsTransformation来处理外部登录的声明映射,步骤如下:
- 自定义Claims转换类:
public class BaselineClaimsTransformation : IClaimsTransformation { private readonly ICoreApiService _coreApiService; private readonly ICurrentTenant _currentTenant; public BaselineClaimsTransformation(ICoreApiService coreApiService, ICurrentTenant currentTenant) { _coreApiService = coreApiService; _currentTenant = currentTenant; } public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal) { // 只处理已认证的外部用户 if (!principal.Identity.IsAuthenticated) { return principal; } var identity = principal.Identities.First(); // 从sub声明或者原NameIdentifier里拿用户名 var userName = identity.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? identity.FindFirst("sub")?.Value; if (string.IsNullOrEmpty(userName)) { return principal; } // 调用API或从数据库获取对应用户的GUID var userGuid = await _coreApiService.GetUserGuidByUserNameAsync(userName, _currentTenant.Id); if (!string.IsNullOrEmpty(userGuid)) { // 移除原来的NameIdentifier声明 var existingClaim = identity.FindFirst(ClaimTypes.NameIdentifier); if (existingClaim != null) { identity.RemoveClaim(existingClaim); } // 添加正确的用户GUID作为NameIdentifier identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userGuid)); // 顺便加上ABP需要的UserId声明,确保框架能识别 identity.AddClaim(new Claim(AbpClaimTypes.UserId, userGuid)); } return principal; } }
- 在模块的
ConfigureServices方法里注册这个转换类:
public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddTransient<IClaimsTransformation, BaselineClaimsTransformation>(); }
另一种方案(OpenID Connect场景)
如果你的项目用的是OpenID Connect,也可以直接在OIDC配置里处理声明映射:
public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddAuthentication() .AddOpenIdConnect("oidc", options => { // 其他OIDC配置... options.Events = new OpenIdConnectEvents { OnTokenValidated = async context => { // 从sub声明获取用户名 var userName = context.Principal.FindFirst("sub")?.Value; if (string.IsNullOrEmpty(userName)) return; // 获取服务实例 var coreApiService = context.HttpContext.RequestServices.GetRequiredService<ICoreApiService>(); var userGuid = await coreApiService.GetUserGuidByUserNameAsync(userName); var identity = context.Principal.Identities.First(); // 替换NameIdentifier声明 var existingClaim = identity.FindFirst(ClaimTypes.NameIdentifier); if (existingClaim != null) { identity.RemoveClaim(existingClaim); } identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userGuid)); } }; }); }
内容的提问来源于stack exchange,提问作者asipahio
相关产品推荐
相关产品推荐

