我们有这个自定义授权方案,我试图通过在.NET核心中进行单元测试和使用依赖注入来解决这个问题。让我解释一下设置:
我创建了一个接口IStsHttpClient和类StsHttpClient。此类连接到创建&的内部Web服务。解码令牌。这恰好有1个方法" DecodeToken(字符串标记)"构造函数非常简单 - 它接受从DI加载的选项对象。
然后我的AuthorizationHandler理论上只使用IStsHttpClient来调用和解码令牌。我的问题是,根据在线示例,我不知道如何正确指定/构建授权处理程序(请参阅下面的代码)。
在此处验证代码:
public class MyAuthorizationRequirement : AuthorizationHandler<MyAuthorizationRequirement >, IAuthorizationRequirement
{
const string Bearer = "Bearer ";
readonly IStsHttpClient _client;
public BuzzStsAuthorizationRequirement([FromServices]IStsHttpClient client)
{
_client = client;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, MyStsAuthorizationRequirement requirement)
{
/* remaining code omitted - but this will call IStsHttpClient.Decode() */
我的Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.Configure<StsHttpOptions>(Configuration.GetSection("StsConfigurationInfo"));
services.AddScoped<IStsHttpClient , StsHttpClient >();
services.AddAuthorization(options =>
{
options.AddPolicy("Authorize", policy =>
{
/* initialize this differently?? */
policy.AddRequirements(new MyStsAuthorizationRequirement( /* somethign is needed here?? */));
});
});
答案 0 :(得分:1)
尼古拉斯,
您必须在此处分隔您的处理程序和要求。除此之外,请将DI内容保留在处理程序中。要求本身将是DTO或带有标记接口IAuthorizationRequirement的空类。
要求:
public class MyAuthorizationRequirement : IAuthorizationRequirement
{
}
处理程序:
public class MyAuthorizationHandler : AuthorizationHandler<MyAuthorizationRequirement>
{
const string Bearer = "Bearer ";
readonly IStsHttpClient _client;
public BuzzStsAuthorizationRequirement([FromServices]IStsHttpClient client)
{
_client = client;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, MyAuthorizationRequirement requirement)
{
...
}
}
配置:
services.Configure<StsHttpOptions>(Configuration.GetSection("StsConfigurationInfo"));
services.AddScoped<IStsHttpClient , StsHttpClient >();
services.AddAuthorization(options =>
{
options.AddPolicy("Authorize", policy =>
{
policy.AddRequirements(new MyAuthorizationRequirement());
});
});
答案 1 :(得分:0)
对于希望围绕 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