非EF场景下ASP.NET Core Identity角色检索自定义方案咨询
我们团队正在将旧AS400应用迁移到ASP.NET Core,仍使用DB2表(不使用Entity Framework)。几张共享表作为用户角色与权限的中心,供旧RPG应用和新.NET应用共用,目前已实现无EF的ASP.NET Core Identity。
我尝试自定义并覆盖用户角色的获取逻辑:
- 原本想在RoleStore类中重写相关方法,但断点从未触发
- 尝试重写
IsInRole方法,同样未触发
当前测试方式:
- 在Home控制器使用
[Authorize(Roles = "Administrator")] - 通过
AuthorizationPolicyBuilder的RequireRole("~")配置全局角色策略过滤器(两种方式都针对CustomClaimsPrincipal测试过)
最终计划用[Authorize(Policy="")]做控制器层面的角色校验,但当前需要先实现从DB2自定义角色权限的逻辑,明确其在角色授权和策略授权中的调用方式。项目目标框架为.NET 5.0。
以下是我用于设置CustomClaimsPrincipal以重写IsInRole方法的Startup.cs基础代码:
public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddTransient<IUserStore<ApplicationUser>, UserStore>(); services.AddTransient<IRoleStore<ApplicationRole>, RoleStore>(); services.AddIdentity<ApplicationUser, ApplicationRole>() .AddDefaultTokenProviders(); services.AddControllersWithViews( config => { var policy = new AuthorizationPolicyBuilder(); policy.RequireRole("Test"); config.Filters.Add(new AuthorizeFilter(policy.Build())); } ); services.AddTransient<IClaimsTransformation, ClaimsTransformer>(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); }); } } public class CustomClaimsPrincipal : ClaimsPrincipal { public CustomClaimsPrincipal(IPrincipal principal) : base(principal) { } public override bool IsInRole(string role) { // ... return base.IsInRole(role); } } public class ClaimsTransformer : IClaimsTransformation { public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal) { var customPrincipal = new CustomClaimsPrincipal(principal) as ClaimsPrincipal; return Task.FromResult(customPrincipal); } }
问题分析与解决方案
1. 自定义IsInRole未触发的原因
ASP.NET Core Identity的角色校验默认依赖Claims集合中的ClaimTypes.Role声明,而非直接调用ClaimsPrincipal.IsInRole方法。使用[Authorize(Roles)]或RequireRole时,授权系统会直接检查Claims,不会触发你重写的IsInRole方法。
2. 角色授权的正确实现(适配[Authorize(Roles)])
修改ClaimsTransformer,在Claims转换阶段从DB2查询用户角色并注入Claims集合,让授权系统直接识别:
public class ClaimsTransformer : IClaimsTransformation { private readonly IUserStore<ApplicationUser> _userStore; // 注入你封装的DB2角色查询服务 private readonly IDbRoleService _dbRoleService; public ClaimsTransformer(IUserStore<ApplicationUser> userStore, IDbRoleService dbRoleService) { _userStore = userStore; _dbRoleService = dbRoleService; } public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal) { if (!principal.Identity.IsAuthenticated) { return principal; } var userId = principal.FindFirstValue(ClaimTypes.NameIdentifier); var user = await _userStore.FindByIdAsync(userId, CancellationToken.None); if (user == null) { return principal; } // 从DB2获取用户所有角色 var userRoles = await _dbRoleService.GetUserRoles(user.Id); var identity = principal.Identity as ClaimsIdentity; foreach (var role in userRoles) { if (!identity.HasClaim(c => c.Type == ClaimTypes.Role && c.Value == role)) { identity.AddClaim(new Claim(ClaimTypes.Role, role)); } } return principal; } }
完成后,[Authorize(Roles = "Administrator")]和全局RequireRole策略会自动生效。
3. 策略授权的自定义实现(适配[Authorize(Policy)])
如果最终要使用策略授权,可创建自定义授权要求与处理程序,直接从DB2校验权限:
步骤1:定义权限要求
public class PermissionRequirement : IAuthorizationRequirement { public string PermissionCode { get; } public PermissionRequirement(string permissionCode) { PermissionCode = permissionCode; } }
步骤2:实现授权处理程序
public class PermissionHandler : AuthorizationHandler<PermissionRequirement> { private readonly IDbPermissionService _dbPermissionService; public PermissionHandler(IDbPermissionService dbPermissionService) { _dbPermissionService = dbPermissionService; } protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement) { var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier); if (string.IsNullOrEmpty(userId)) { context.Fail(); return; } // 从DB2校验用户是否拥有指定权限 bool hasPermission = await _dbPermissionService.CheckUserPermission(userId, requirement.PermissionCode); if (hasPermission) { context.Succeed(requirement); } else { context.Fail(); } } }
步骤3:注册策略与服务
在Startup.ConfigureServices中添加:
// 注册自定义服务 services.AddScoped<IDbRoleService, DbRoleService>(); services.AddScoped<IDbPermissionService, DbPermissionService>(); services.AddScoped<IAuthorizationHandler, PermissionHandler>(); // 配置授权策略 services.AddAuthorization(options => { options.AddPolicy("AdminPolicy", policy => policy.Requirements.Add(new PermissionRequirement("ADMIN_ACCESS"))); options.AddPolicy("TestPolicy", policy => policy.Requirements.Add(new PermissionRequirement("TEST_ACCESS"))); }); // 全局策略示例(替换原RequireRole配置) services.AddControllersWithViews(config => { var globalPolicy = new AuthorizationPolicyBuilder() .AddRequirements(new PermissionRequirement("TEST_ACCESS")) .Build(); config.Filters.Add(new AuthorizeFilter(globalPolicy)); });
之后就可以通过[Authorize(Policy = "AdminPolicy")]在控制器/方法上实现权限校验。
4. RoleStore方法未触发的说明
RoleStore的方法仅在调用UserManager的角色相关API(如UserManager.IsInRoleAsync)时才会触发。如果要复用RoleStore的逻辑,可在ClaimsTransformer中调用UserManager.IsInRoleAsync来获取角色,而非直接查询DB2。
内容的提问来源于stack exchange,提问作者eaglei22

