如何扩展ASP.NET Core 2.0角色以实现多场所差异化权限管控?
实现ASP.NET Core 2.0多维度权限控制(基于场所+操作的细粒度授权)
针对你提出的多维度权限控制需求(结合归属场所、CRUD操作、跨场所授权),在ASP.NET Core 2.0中可以通过自定义授权策略+数据模型设计来实现,完全摆脱单一角色的限制。下面是一套完整的落地方案:
1. 核心数据模型设计
首先需要梳理清楚权限的关联关系:用户-场所-操作权限,所以需要设计以下几个核心模型:
// 场所实体:存储所有归属场所信息 public class Location { public int Id { get; set; } public string Name { get; set; } public string Description { get; set; } // 可添加层级字段(如ParentId)支持场所上下级关联 } // 权限项实体:对应CRUD等操作权限 public class Permission { public int Id { get; set; } public string Code { get; set; } // 唯一标识,如"Create", "Read", "Update", "Delete" public string DisplayName { get; set; } // 显示名称,如"创建权限" } // 用户-场所-权限关联表:核心关联实体,记录用户在指定场所拥有的具体权限 public class UserLocationPermission { public int UserId { get; set; } public int LocationId { get; set; } public int PermissionId { get; set; } // 导航属性(EF Core关联用) public ApplicationUser User { get; set; } public Location Location { get; set; } public Permission Permission { get; set; } }
同时,记得在ApplicationDbContext中配置这些实体的DbSet以及关联关系,比如UserLocationPermission的复合主键:
protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // 配置用户-场所-权限的复合主键 modelBuilder.Entity<UserLocationPermission>() .HasKey(ulp => new { ulp.UserId, ulp.LocationId, ulp.PermissionId }); }
2. 自定义权限授权Handler
ASP.NET Core的默认角色授权无法满足这种多维度判断,所以需要实现自定义的IAuthorizationHandler来处理场所+操作的权限校验:
定义权限需求类
// 权限需求:传递需要校验的操作权限码和场所ID public class LocationPermissionRequirement : IAuthorizationRequirement { public string PermissionCode { get; } public int? TargetLocationId { get; } // 可为空,为空则校验用户所有有权限的场所 public LocationPermissionRequirement(string permissionCode, int? targetLocationId = null) { PermissionCode = permissionCode ?? throw new ArgumentNullException(nameof(permissionCode)); TargetLocationId = targetLocationId; } }
实现授权Handler
public class LocationPermissionHandler : AuthorizationHandler<LocationPermissionRequirement> { private readonly ApplicationDbContext _dbContext; private readonly IHttpContextAccessor _httpContextAccessor; public LocationPermissionHandler(ApplicationDbContext dbContext, IHttpContextAccessor httpContextAccessor) { _dbContext = dbContext; _httpContextAccessor = httpContextAccessor; } protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, LocationPermissionRequirement requirement) { // 获取当前用户ID var userIdClaim = context.User.FindFirst(ClaimTypes.NameIdentifier); if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out int userId)) { context.Fail(); return; } // 获取当前请求对应的场所ID(这里假设从路由参数获取,可根据业务调整为请求头/Session等) int currentLocationId; if (!int.TryParse(_httpContextAccessor.HttpContext.Request.RouteValues["locationId"]?.ToString(), out currentLocationId)) { // 如果没有指定场所ID,可以判断用户是否有全局权限,或者直接拒绝 context.Fail(); return; } // 校验用户在当前场所是否拥有指定权限 var hasPermission = await _dbContext.UserLocationPermissions .Include(ulp => ulp.Permission) .AnyAsync(ulp => ulp.UserId == userId && ulp.LocationId == currentLocationId && ulp.Permission.Code == requirement.PermissionCode); if (hasPermission) { context.Succeed(requirement); } else { context.Fail(); } } }
3. 注册授权服务
在Startup.cs的ConfigureServices方法中注册自定义授权策略和Handler:
public void ConfigureServices(IServiceCollection services) { // ... 其他服务注册 // 注册授权服务并定义权限策略 services.AddAuthorization(options => { options.AddPolicy("CreateAtLocation", policy => policy.Requirements.Add(new LocationPermissionRequirement("Create"))); options.AddPolicy("ReadAtLocation", policy => policy.Requirements.Add(new LocationPermissionRequirement("Read"))); options.AddPolicy("UpdateAtLocation", policy => policy.Requirements.Add(new LocationPermissionRequirement("Update"))); options.AddPolicy("DeleteAtLocation", policy => policy.Requirements.Add(new LocationPermissionRequirement("Delete"))); }); // 注册自定义授权Handler services.AddScoped<IAuthorizationHandler, LocationPermissionHandler>(); // 注册HttpContextAccessor(用于获取当前请求上下文) services.AddHttpContextAccessor(); }
4. 在控制器中使用权限校验
直接在Action或控制器上添加[Authorize]特性并指定对应的策略即可:
[Authorize(Policy = "ReadAtLocation")] public async Task<IActionResult> GetLocationEntities(int locationId) { // 业务逻辑:获取指定场所下的实体数据 var entities = await _dbContext.Entities.Where(e => e.LocationId == locationId).ToListAsync(); return Ok(entities); } [Authorize(Policy = "CreateAtLocation")] [HttpPost] public async Task<IActionResult> CreateEntity(int locationId, [FromBody] EntityDto dto) { // 业务逻辑:在指定场所下创建实体 var entity = new Entity { LocationId = locationId, Name = dto.Name, // 其他属性赋值 }; _dbContext.Entities.Add(entity); await _dbContext.SaveChangesAsync(); return CreatedAtAction(nameof(GetLocationEntities), new { locationId, id = entity.Id }, entity); }
5. 权限管理与默认权限配置
- 默认权限分配:在用户创建时,根据其岗位职责分配默认归属场所,并自动添加该场所的默认权限(比如Read权限),可以在用户注册逻辑或后台创建用户时实现。
- 跨场所授权:开发一个权限管理后台页面,允许管理员选择用户、场所、操作权限进行关联,直接操作
UserLocationPermission表即可完成权限授予。 - 性能优化:为了避免每次授权都查询数据库,可以在用户登录时将其所有权限(用户-场所-权限集合)缓存到内存或Redis中,授权Handler优先从缓存中校验权限。
内容的提问来源于stack exchange,提问作者Adam Wells
相关产品推荐
相关产品推荐

