对于希望在 C#9 NetCore5 中围绕现有权限处理程序包装授权的其他人,我找到了以下解决方案,它允许我使用库存依赖注入容器将服务注入到 AuthorizationHandler 中。
对我来说,这需要 5 个新类和对 Startup.cs 的一些更改
以下是我的 PermissionPolicyProvider.cs,这将代表通用权限,而不是策略(我稍后会过滤权限)
using System.Data;
using System.Threading.Tasks;
using App.Models;
using App.Services.Permissions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
namespace App.Permissions
{
class PermissionAuthorizationHandler : AuthorizationHandler<PermissionRequirement>
{
private readonly AppUserManager<AppUser> _appUserManager;
public PermissionAuthorizationHandler(UserManager<AppUser> userManager)
{
_appUserManager = (AppUserManager<AppUser>)userManager;
}
#nullable enable
// public virtual async Task HandleAsync(AuthorizationHandlerContext context)
// {
// foreach (var req in context.Requirements.OfType<TRequirement>())
// {
// await HandleRequirementAsync(context, req);
// }
// }
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
{
var permissionsService = (PermissionService?) _appUserManager.Services.GetService(typeof(PermissionService))
?? throw new NoNullAllowedException("Null found when accessing PermissionService");
if (await permissionsService.Permitted(requirement.Permission))
{
context.Succeed(requirement);
}
}
#nullable disable
}
}
接下来是我的 PermissionPolicyProvider.cs,此代码允许我们过滤策略并在收到时动态构建权限。
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;
namespace App.Permissions
{
internal class PermissionPolicyProvider : IAuthorizationPolicyProvider
{
public DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; }
public PermissionPolicyProvider(IOptions<AuthorizationOptions> options) =>
FallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
public Task<AuthorizationPolicy> GetFallbackPolicyAsync() => FallbackPolicyProvider.GetDefaultPolicyAsync();
public Task<AuthorizationPolicy> GetDefaultPolicyAsync() => FallbackPolicyProvider.GetDefaultPolicyAsync();
// Dynamically creates a policy with a requirement that contains the permission.
// The policy name must match the permission that is needed.
/// <summary>
///
/// </summary>
/// <param name="policyName"></param>
/// <returns></returns>
public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{
if (! policyName.StartsWith("Permission", StringComparison.OrdinalIgnoreCase))
{
// If it doesn't start with permission, then it's a policy.
// pass policies onward to default provider
return FallbackPolicyProvider.GetPolicyAsync(policyName);
}
var policy = new AuthorizationPolicyBuilder();
policy.AddRequirements(new PermissionRequirement(policyName));
return Task.FromResult(policy.Build());
}
}
}
接下来是 PermissionAuthorizationHandler.cs,这是微软希望您自定义数据库检查的地方,因此如果您不想分离服务层,您可以在此之后停止。请注意,您可以一次处理一个权限,也可以一次处理所有权限(注意注释掉的代码)。
using System.Data;
using System.Threading.Tasks;
using App.Models;
using App.Services.Permissions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
namespace App.Permissions
{
class PermissionAuthorizationHandler : AuthorizationHandler<PermissionRequirement>
{
private readonly AppUserManager<AppUser> _appUserManager;
public PermissionAuthorizationHandler(UserManager<AppUser> userManager)
{
_appUserManager = (AppUserManager<AppUser>)userManager;
}
#nullable enable
// public virtual async Task HandleAsync(AuthorizationHandlerContext context)
// {
// foreach (var req in context.Requirements.OfType<TRequirement>())
// {
// await HandleRequirementAsync(context, req);
// }
// }
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
{
var permissionsService = (PermissionService?) _appUserManager.Services.GetService(typeof(PermissionService))
?? throw new NoNullAllowedException("Null found when accessing PermissionService");
if (await permissionsService.Permitted(requirement.Permission))
{
context.Succeed(requirement);
}
}
#nullable disable
}
}
如果您不希望服务层分离,这是您的最后一步。您只需要正确注册所有服务。将以下内容添加到您的 Startup.cs
services.AddDbContext<PokeflexContext>
(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddSingleton<IAuthorizationPolicyProvider, PermissionPolicyProvider>();
services.AddScoped<IAuthorizationHandler, PermissionAuthorizationHandler>();
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<PokeflexContext>()
.AddUserManager<UserManager<IdentityUser>>()
.AddDefaultTokenProviders();
为了分离出服务层,我们需要扩展 UserManager。 UserManager 实际上可以访问注入到您的应用程序中的整个服务层,但它会将其隐藏在私有修饰符下。我们的解决方案很简单:扩展 UserManager 并覆盖构造函数以将我们的服务传递给公共变量。这是我作为 AppUserManager 的自定义版本
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace App.Permissions
{
public class AppUserManager<TUser> : UserManager<TUser> where TUser : class
{
public IServiceProvider Services;
public AppUserManager(IUserStore<TUser> store,
IOptions<IdentityOptions> optionsAccessor, IPasswordHasher<TUser> passwordHasher,
IEnumerable<IUserValidator<TUser>> userValidators,
IEnumerable<IPasswordValidator<TUser>> passwordValidators,
ILookupNormalizer keyNormalizer, IdentityErrorDescriber errors,
IServiceProvider services, ILogger<UserManager<TUser>> logger)
: base(store, optionsAccessor, passwordHasher, userValidators,
passwordValidators, keyNormalizer, errors, services, logger)
{
Services = services;
}
}
}
最后一步,我们需要再次更新 Startup.cs 以引用我们的自定义类型。我们还在此处添加另一行,以确保如果有人在端点内请求我们的服务而不是作为属性,他们将获得我们的自定义 AppUserManager。我最终生成的 ConfigureServices 内容如下
services.AddDbContext<PokeflexContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddTransient<PermissionService>();
services.AddSingleton<IAuthorizationPolicyProvider, PermissionPolicyProvider>();
services.AddScoped<IAuthorizationHandler, PermissionAuthorizationHandler>();
services.AddIdentity<AppUser, IdentityRole>()
.AddEntityFrameworkStores<PokeflexContext>()
.AddUserManager<AppUserManager<AppUser>>()
.AddDefaultTokenProviders();
services.AddScoped(s => s.GetService<AppUserManager<AppUser>>());
如果您已经熟悉服务配置,那么您可能不需要以下内容,但这是我创建的一个简单服务,授权处理程序可以通过 DI 访问。
using System.Threading.Tasks;
using App.Data;
using Microsoft.EntityFrameworkCore;
namespace App.Services.Permissions
{
public class PermissionService
{
private PokeflexContext _dbContext;
public PermissionService(PokeflexContext dbContext)
{
_dbContext = dbContext;
}
public virtual async Task<bool> Permitted(string permission)
{
return await _dbContext.AppUsers.AnyAsync();
}
}
}
有关许可的更多信息,请访问:https://github.com/iammukeshm/PermissionManagement.MVC