ASP.NET Core自定义授权属性

时间:2019-05-30 13:08:44

标签: c# asp.net-core-webapi

我有这种自定义授权配置。问题是我要检查子模块以及它是否具有权限。我还有一个登录控制器,用于构建声明等,并添加它们。但是我想我在这里也缺少一些东西。我应该向当前的主要用户添加声明,但我不知道如何:

这是我在登录控制器中的登录操作

[HttpGet("login")]
    public IActionResult Login()
    {
        var userName = _httpContextAccessor.HttpContext.User.Identity.Name.GetUserNameFromHttpContext();
        var user = _userClient.GetUserByUserName(userName);
        var loggedIUserDto = new LoggedInUserDto {UserName = userName};
        var claims = new List<Claim>();

        if (user == null)
        {
            return Unauthorized($"User {userName} does not exits in DB"); 
        }

        var userModulesWithSubmodules = _loginClient.GetUserModulesWithSubmodules(userName);
        loggedIUserDto.UserModulesWithSubmodules = userModulesWithSubmodules;

        if (userModulesWithSubmodules.Count == 0)
        {
            return Conflict($"User {userName} has no modules"); 
        }

        foreach (var module in userModulesWithSubmodules)
        {
            foreach (var submodule in module.Submodules)
            {
                var submoduleActionList = new List<string>();
                if (submodule.CanAdd)
                {
                    submoduleActionList.Add("CanAdd");
                }

                if (submodule.CanEdit)
                {
                    submoduleActionList.Add("CanEdit");
                }

                if (submodule.CanRead)
                {
                    submoduleActionList.Add("CanRead");
                }

                if (submodule.CanDelete)
                {
                    submoduleActionList.Add("CanDelete");
                }

                claims.Add(new Claim(ClaimTypes.Name, user.UserName));
                claims.Add(new Claim(submodule.SubmoduleName, string.Join(',', submoduleActionList)));
            }

        }

        var claimsIdentity = new ClaimsIdentity(claims);
        var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);

        Thread.CurrentPrincipal = claimsPrincipal;

        return Ok(loggedIUserDto);
    }

CustomeAuthorize

    public class CustomAuthorize : AuthorizeAttribute
   {
    private SubmoduleActionType _submoduleActionType;
    private SubmoduleType _submoduleType;

    public SubmoduleActionType ActionType;

    public SubmoduleType Type {
        get => _submoduleType;
        set
        {
            _submoduleType = value;
            Policy = $"{_submoduleType.ToString()};{_submoduleActionType.ToString()}";
        }
    }

    public CustomAuthorize(SubmoduleActionType submoduleActionType, SubmoduleType submoduleType)
    {
        _submoduleActionType = submoduleActionType;
        _submoduleType = submoduleType;
    }
}

下面是我的要求类:

    public class SubmoduleTypeRequirement : IAuthorizationRequirement
{
    public SubmoduleActionType? ActionType { get; set; }

    public SubmoduleType? Type { get; set; }

    public SubmoduleTypeRequirement(SubmoduleActionType actionType, SubmoduleType type)
    {
        Type = type;
        ActionType = actionType;
    }
}

这是我的Handler类

  public class SubmoduleAuthorizationHandler : AuthorizationHandler<SubmoduleTypeRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, SubmoduleTypeRequirement submoduleRequirement)
    {
        if (!submoduleRequirement.ActionType.HasValue)
        {
            throw new ArgumentException("No action type provided");
        }

        if (!submoduleRequirement.Type.HasValue)
        {
            throw new ArgumentException("No submodule type provided");
        }

        if (!context.User.HasClaim(uc => uc.Type == submoduleRequirement.Type.ToString()))
        {
            context.Fail();
            return Task.FromResult(0);
        }

        var grantedRights = Convert.ToString(context.User.FindFirst(c => c.Type == submoduleRequirement.Type.ToString()));

        if (grantedRights.Contains(submoduleRequirement.ActionType.ToString()))
        {
            context.Succeed(submoduleRequirement);
        }

        return Task.FromResult(0);
    }
}

最后是一个策略类:

public class SubmodulePolicy : IAuthorizationPolicyProvider
{
    public SubmodulePolicy(IOptions<AuthorizationOptions> options)
    {
        DefaultPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
    }

    public DefaultAuthorizationPolicyProvider DefaultPolicyProvider { get; }  

    public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
    {
        var submoduleTypeAndAction = policyName.Split(";");
        var submoduleTypeString = submoduleTypeAndAction[0];
        var actionTypeString = submoduleTypeAndAction[1];

        var submoduleTypeParsed = System.Enum.TryParse(submoduleTypeString, out SubmoduleType submoduleType);
        var actionTypeParsed = System.Enum.TryParse(actionTypeString, out SubmoduleActionType submoduleActionType);

        if (actionTypeParsed && submoduleTypeParsed)
        {
            var policy = new AuthorizationPolicyBuilder();
            policy.AddRequirements(new SubmoduleTypeRequirement(submoduleActionType, submoduleType));
            return Task.FromResult(policy.Build());
        }

        if (!actionTypeParsed || !submoduleTypeParsed)
        {
            throw new ArgumentException("Cannot parse action or submoduleType from Policy");
        }

        return DefaultPolicyProvider.GetPolicyAsync(policyName);
    }

    public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
    {
        return DefaultPolicyProvider.GetDefaultPolicyAsync();
    }
}

问题是我的代码永远无法到达我的GetPolicy。它总是转到GetDefaultPolicyAsync方法。甚至没有触发该属性。我是否应该在某个地方重新命名“自定义属性”?我不信。无论我在互联网上找到什么,即使在正式文档中也总是有一个论点,但我需要其中两个。

哦,这是我在启动类中的配置

        services.AddTransient<IAuthorizationPolicyProvider, SubmodulePolicy>();
        services.AddSingleton<IAuthorizationHandler, SubmoduleAuthorizationHandler>();
     services.AddAuthorization();

有什么想法吗?仅一个参数有任何限制吗?我也在考虑将其替换为过滤器。

编辑:

根据要求,我启动了课程。只是评论-jwt配置被禁用,因为我在获取带有常量的json文件时遇到问题(获取文件时,系统提示我输入用户名和密码-iis服务器)。该网络api与angular 2+(v7)组合

我的创业班:

   public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }
    public IContainer ApplicationContainer { get; private set; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public IServiceProvider ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication(IISDefaults.AuthenticationScheme);

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
            .AddJsonOptions(options => options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver());

        services.AddTransient<IAuthorizationPolicyProvider, SubmodulePolicy>();
        services.AddSingleton<IAuthorizationHandler, SubmoduleAuthorizationHandler>();

        //var appSettingsSection = Configuration.GetSection("Settings");
        //services.Configure<AppSettings>(appSettingsSection);
        //var appSettings = appSettingsSection.Get<AppSettings>();
        //var key = Encoding.ASCII.GetBytes(appSettings.Secret);

        //services
        //    .AddAuthentication(auth =>
        //    {
        //        auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        //        auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        //    })
        //    .AddJwtBearer(jwtBearer =>
        //    {
        //        jwtBearer.RequireHttpsMetadata = false;
        //        jwtBearer.SaveToken = true;
        //        jwtBearer.TokenValidationParameters = new TokenValidationParameters
        //        {
        //            ValidateIssuerSigningKey = true,
        //            IssuerSigningKey = new SymmetricSecurityKey(key),
        //            ValidateIssuer = false,
        //            ValidateAudience = false
        //        };
        //    });

        services.AddAuthorization();

        services.Configure<IISServerOptions>(options =>
        {
            options.AutomaticAuthentication = true;
        });

        services.AddHttpContextAccessor();

        var container = new ContainerBuilder();

        container.RegisterType<PermissionsClient>()
            .As<IPermissionsClient>()
            .WithParameter("baseServiceUrl", Configuration.GetSection("Settings")["BaseApiUrl"])
            .WithParameter("clientCertThumbprint", Configuration.GetSection("Settings")["BaseApiClientThumbPrint"])
            .SingleInstance();

        container.RegisterType<LoginClient>()
            .As<ILoginClient>()
            .WithParameter("baseServiceUrl", Configuration.GetSection("Settings")["BaseApiUrl"])
            .WithParameter("clientCertThumbprint", Configuration.GetSection("Settings")["BaseApiClientThumbPrint"])
            .SingleInstance();

        container.RegisterType<UserClient>()
            .As<IUserClient>()
            .WithParameter("baseServiceUrl", Configuration.GetSection("Settings")["BaseApiUrl"])
            .WithParameter("clientCertThumbprint", Configuration.GetSection("Settings")["BaseApiClientThumbPrint"])
            .SingleInstance();

        container.RegisterType<SenderClient>()
            .As<ISenderClient>()
            .WithParameter("baseServiceUrl", Configuration.GetSection("Settings")["BaseApiUrl"])
            .WithParameter("clientCertThumbprint", Configuration.GetSection("Settings")["BaseApiClientThumbPrint"])
            .SingleInstance();

        container.Populate(services);
        ApplicationContainer = container.Build();

        return new AutofacServiceProvider(ApplicationContainer);
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseAuthentication();
        app.UseMvc();
    }
}

1 个答案:

答案 0 :(得分:0)

我设法解决了这个问题。问题出在我创建的属性中。这是奇怪的原因,因为文档位于https://docs.microsoft.com/pl-pl/aspnet/core/security/authorization/iauthorizationpolicyprovider?view=aspnetcore-2.2

无论如何,在属性的设置器中设置了策略名称的区域似乎现在对我有用。我不知道为什么在我发现(尽管在继承某些东西时很明显)之后,除了我的自定义参数外,我还可以传递一个策略名称,我尝试再次对其进行测试。当我将策略名称硬编码到我的自定义属性后,突然遇到了GetPolicyAsync方法中的断点。所以我尝试了类似的方法

public class CustomAuthorize : AuthorizeAttribute
{
  private const string _policyName = "SubmodulePolicy"
  private SubmoduleActionType _submoduleActionType;
  private SubmoduleType _submoduleType;

  public SubmoduleActionType ActionType;

  public SubmoduleType Type { get; set;}

  public CustomAuthorize(SubmoduleActionType submoduleActionType, SubmoduleType submoduleType)
  {
    _submoduleActionType = submoduleActionType;
    _submoduleType = submoduleType;
    Policy = $"{_policyName}-{submoduleActionType.ToString()}-{submoduleType.ToString()}"
}
}

突然,它开始起作用。另一件事是,您可以在登录控制器中向用户添加声明,但是如果这是Windows身份验证

public class SubmoduleAuthorizationHandler : AuthorizationHandler<SubmoduleTypeRequirement>
 {
     protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, SubmoduleTypeRequirement submoduleRequirement)
{
    if (!submoduleRequirement.ActionType.HasValue)
    {
        throw new ArgumentException("No action type provided");
    }

    if (!submoduleRequirement.Type.HasValue)
    {
        throw new ArgumentException("No submodule type provided");
    }

    if (!context.User.HasClaim(uc => uc.Type == submoduleRequirement.Type.ToString()))
    {
        context.Fail();
        return Task.FromResult(0);
    }

      var grantedRights = Convert.ToString(context.User.FindFirst(c => c.Type == submoduleRequirement.Type.ToString()));

     if (grantedRights.Contains(submoduleRequirement.ActionType.ToString()))
      {
        context.Succeed(submoduleRequirement);
      }

      return Task.FromResult(0);
  }
 }

在http上下文中,您将获得不同的上下文,然后在login方法中进行更新。因此,我不得不使用此Add custom claims to identity when using Windows authentication并在此处填充声明,而不是登录方法。对于许多人来说,最后一个也许很明显,但是对我而言却不是。也许我也说冒险可以帮助某人。