You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在ASP.NET Core Web API(EF Core)中实现动态数据库授权

解决方案

1. 重构通用权限验证模型

替换单个权限对应单个Requirement的冗余写法,创建通用PermissionRequirement承载权限名称:

public class PermissionRequirement : IAuthorizationRequirement
{
    // 存储需要验证的策略名(即权限码,如"UserListRead")
    public string PermissionName { get; }

    public PermissionRequirement(string permissionName)
    {
        PermissionName = permissionName ?? throw new ArgumentNullException(nameof(permissionName));
    }
}

2. 实现通用授权处理程序

编写统一的PermissionAuthorizationHandler,处理所有权限的验证逻辑,无需为每个权限单独写Handler:

public class PermissionAuthorizationHandler : AuthorizationHandler<PermissionRequirement>
{
    private readonly DataContext _context;
    private readonly UserManager<UserArkiva> _userManager;
    private readonly IAuthService _authService;
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly IMemoryCache _cache;
    private readonly CompanyWithPrivilegesMethod _privilegeService;

    // 仅注入必要服务,避免冗余依赖
    public PermissionAuthorizationHandler(DataContext context,
                                         UserManager<UserArkiva> userManager,
                                         IAuthService authService,
                                         IHttpContextAccessor httpContextAccessor,
                                         IMemoryCache cache,
                                         CompanyWithPrivilegesMethod privilegeService)
    {
        _context = context;
        _userManager = userManager;
        _authService = authService;
        _httpContextAccessor = httpContextAccessor;
        _cache = cache;
        _privilegeService = privilegeService;
    }

    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
    {
        var httpContext = _httpContextAccessor.HttpContext;
        if (httpContext == null)
        {
            context.Fail();
            return;
        }

        // 获取当前登录用户名(异步调用避免死锁)
        var userNameResult = await _authService.GetUserNameFromToken(httpContext);
        if (string.IsNullOrEmpty(userNameResult.UserName))
        {
            context.Fail();
            return;
        }

        // 从缓存或数据库获取用户权限列表,减少DB查询压力
        var cacheKey = $"UserPermissions_{userNameResult.UserName}";
        var userPermissions = await _cache.GetOrCreateAsync(cacheKey, async entry =>
        {
            // 缓存5分钟,可根据实际权限变更频率调整
            entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);
            return await _privilegeService.GetCompaniesWithPrivilegesAsync(userNameResult.UserName);
        });

        // 验证用户是否拥有当前策略对应的权限
        if (userPermissions.Any(p => p.PrivilegeName == requirement.PermissionName))
        {
            context.Succeed(requirement);
        }
        else
        {
            context.Fail();
        }
    }
}

3. 动态批量注册权限策略

在Program.cs中从数据库读取所有权限码,循环注册策略,无需手动添加50+个:

var builder = WebApplication.CreateBuilder(args);

// 注册必要服务
builder.Services.AddDbContext<DataContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddIdentity<UserArkiva, Roles>()
    .AddEntityFrameworkStores<DataContext>();

builder.Services.AddAuthorization();
// 注册通用授权处理程序和权限服务
builder.Services.AddScoped<IAuthorizationHandler, PermissionAuthorizationHandler>();
builder.Services.AddScoped<CompanyWithPrivilegesMethod>();
builder.Services.AddMemoryCache();
builder.Services.AddHttpContextAccessor();

var app = builder.Build();

// 动态注册权限策略
using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    var context = services.GetRequiredService<DataContext>();
    
    // 从数据库读取所有权限码(假设权限表为Permissions,字段为Name)
    var permissions = await context.Permissions.Select(p => p.Name).ToListAsync();
    
    var authorizationOptions = services.GetRequiredService<IOptions<AuthorizationOptions>>().Value;
    foreach (var permission in permissions)
    {
        authorizationOptions.AddPolicy(permission, policy =>
        {
            policy.Requirements.Add(new PermissionRequirement(permission));
        });
    }
}

// 后续中间件配置
app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();
app.Run();

4. 关键问题解决说明

  • 获取当前策略名:通过PermissionRequirement.PermissionName直接拿到当前要验证的策略名称(如"UserListRead"),无需额外处理。
  • Requirement为null的问题:原代码中UserListReadRequirement无构造函数且未正确初始化,改用通用Requirement后,每个策略都会创建带权限名的实例,Handler能正常接收参数。
  • 避免Token过长:不在Token中存储权限列表,仅保留用户标识,每次授权时从数据库(或缓存)拉取最新权限,既精简Token,又实现权限动态生效(无需重新登录)。

5. 额外优化建议

  • 将GetCompaniesWithPrivileges改为异步方法,避免同步调用导致的性能问题。
  • 权限缓存时间可根据业务调整,若权限变更频繁可缩短缓存时长,或提供权限变更时主动清除缓存的逻辑。
  • 确保CompanyWithPrivilegesMethod通过依赖注入获取服务,而非在Handler中手动实例化,符合DI设计原则。

内容的提问来源于stack exchange,提问作者Leo Ramadani

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 12:37:27