.NET核心自定义授权依赖注入

时间:2016-09-01 21:12:23

标签: c# dependency-injection authorization asp.net-core-webapi

我们有这个自定义授权方案,我试图通过在.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?? */));
            });
        });

2 个答案:

答案 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