【问题标题】:.NET Core Custom Authorization Dependency Injection.NET Core 自定义授权依赖注入
【发布时间】:2017-01-09 21:25:36
【问题描述】:

我们有这个自定义授权方案,我试图通过单元测试和在 .NET 核心中使用依赖注入来解决它。让我解释一下设置:

我创建了一个接口 IStsHttpClient 和类 StsHttpClient。此类连接到创建和解码令牌的内部 Web 服务。这恰好有 1 个方法“DecodeToken(string token)”,构造函数非常简单 - 它接受从 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?? */));
            });
        });

【问题讨论】:

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


    【解决方案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());
                });
            });
    

    【讨论】:

    • 我最终意识到在这之后的一两天我可以使用中间件来做我想做的事。
    • 什么是BuzzStsAuthorizationRequirement?它似乎是一种没有返回类型的方法。不能是构造函数。
    【解决方案2】:

    对于希望在 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

    【讨论】:

      猜你喜欢
      • 2018-11-16
      • 2016-05-22
      • 1970-01-01
      • 2021-10-23
      • 1970-01-01
      • 2021-02-02
      • 1970-01-01
      • 2018-09-14
      • 2020-09-20
      相关资源
      最近更新 更多