如何在 ASP.NET Core 5.0 中获取我的授权策略要求中的用户输入参数?

How to get user input parameters in my authorization policy requirement in ASP.NET Core 5.0?

我想授权用户只能查看他们自己的资源(例如:Audits 实体)。所以在 AuditController 我有:

[MyAuthorize(Policy = nameof(ValidUserToSeeAuditAuthorizationHandler))]
[HttpGet]
public async Task<JsonResult<AuditView>> GetByIdAsync(Guid id)
{
    // my business to fetch the audit info based by its id
    // ...      
    return result;
}

然后我创建了我的 RequirementAuthorizationHandler classes:

public class ValidUserToSeeAuditRequirment : IAuthorizationRequirement
{
    public ValidUserToSeeAuditRequirment(Guid auditId)
    {
        auditId = auditId;
    }


    public Guid AuditId { get; }
}

public class ValidUserToSeeAuditAuthorizationHandler : AuthorizationHandler<ValidUserToSeeAuditRequirment>
{
    private readonly AppUserManager _userManager;
    private readonly IUnitOfWork _appDbContext;

    public ValidUserToSeeAuditAuthorizationHandler(AppUserManager userManager, IUnitOfWork appDbContext)
    {
        _userManager = userManager;
        _appDbContext = appDbContext;
    }

    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ValidUserToSeeAuditRequirment requirement)
    {
        if (!context.User.IsAuthenticated())
        {
            context.Fail();
            return Task.CompletedTask;
        }

        var theAudit = _appDbContext.Set<Audit>().SingleOrDefault(x => x.Id == requirement.AuditId);
        var authenticatedUserId = Convert.ToInt32(context.User.GetSubjectId());
                    
        // If the authenticated user created the audit, then he/she is valid to see it
        if (theAudit.SubjectauthenticatedUserId == authenticatedUserId)
        {
            // valid
            context.Succeed(requirement);
            return Task.CompletedTask;
        }

        // he/she is not authorized to see the resource (audit)
        context.Fail();
        return Task.CompletedTask;
    }
}

但是在Startupclass我想配置授权策略。如何配置我的 Requirement class 以从控制器的操作方法获取用户输入参数?

services.AddAuthorization(options =>
{
    // another policies
    // ...
    
    options.AddPolicy(name: nameof(ValidUserToSeeAuditAuthorizationHandler),
        policy =>
        {
            policy.RequireAuthenticatedUser();
            policy.AddRequirements(new ValidUserToSeeAuditRequirment( /****** HERE, how to pass the controller action method parameters ******/));
        });
});

services.AddTransient<IAuthorizationHandler, ValidUserToSeeAuditAuthorizationHandler>();

您可以自定义一个AuthorizationPolicy提供者来获取参数。

public class CustomAuthorizepolicyProvider: DefaultAuthorizationPolicyProvider
{
    public CustomAuthorizepolicyProvider(IOptions<AuthorizationOptions> options):base(options)
    {

    }
    public override Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
    {
        
        if (policyName=="[specified plicyname]")
            {
                var authorizePolicy = new AuthorizationPolicyBuilder();
                authorizePolicy.AddRequirements(new ValidUserToSeeAuditRequirment(/* give the parameter*/)).Build();
                return Task.FromResult(authorizePolicy);
            }
        return base.GetPolicyAsync(policyName);
    }
}

注入启动。注:是单例。

services.AddSingleton<IAuthorizationPolicyProvider,CustomAuthorizepolicyProvider>();

我建议您使用 IAuthorizationRequirement 和 AuthorizationHandler 方法。 AuthorizationHandler 的实例(其中 T 是要求)在您的启动中注册为单例。因此,您可以将 IHttpRequestAccessor 注入处理程序,使其具有访问请求的能力。这最终是这样的

    public class YourAuthorizationHandler : AuthorizationHandler<YourAuthorizationRequirement>
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    YourAuthorizationHandler(IHttpContextAccessor httpContextAccessor)
   {
      _httpContextAccessor = httpContextAccessor;
   }

   protected async override Task HandleRequirementAsync(AuthorizationHandlerContext context, YourAuthorizationRequriement requirement)
   {
       var userIdInClaim = context.User.Claims.Where(claim => claimType == NameIdentifier).FirstOrDefault();
       var request = _httpContextAccessor.HttpContext.Request;
       request.EnableBuffering(); // allows the request to be read again

       // read the request from request.Body assuming an HTTP POST. It will depend.
       // do your logic checking the content here.
       return context.Succeed(requirement); // Assuming things are what you want.

   }
}

这没有看起来那么复杂。

读这个Introduction to authorization in ASP.NET Core

我最终得到了这个解决方案:

/// <summary>
/// 
/// </summary>
public class ValidUserToSeeAuditRequirment : IAuthorizationRequirement
{
    
}

/// <summary>
/// Only an Admin and the authorized user can see the Audit
/// </summary>
public class ValidUserToSeeAuditAuthorizationHandler : AuthorizationHandler<ValidUserToSeeAuditRequirment>
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public ValidUserToSeeAuditAuthorizationHandler(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }
}

protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ValidUserToSeeAuditRequirment requirement)
{
    // If he has the Admin Role, then he can see the Audit
    if (context.User.HasClaim(x => x.Type.ToUpperInvariant() == "ROLE" && x.Value.ToUpperInvariant() == "ADMIN"))
    {
        context.Succeed(requirement);
        return;
    }

    // Get the audit id from the Routing
    var auditIdFromRoute = _httpContextAccessor.HttpContext.GetRouteData()?.Values["id"].ToString();
    if (auditIdFromRoute is null || !Guid.TryParse(auditIdFromRoute, out Guid requestingAuditId))
    {
        context.Fail();
        return;
    }

    // get the authenticated user
    var userId = Convert.ToInt32(context.User.GetSubjectId());
        
    // check if the user has authorized to see the audit
    if(isUserAllowToSeeAudit(userId, requestingAuditId))
    {
        context.Succeed(requirement);
        return;
    }
    
    context.Fail();
}

private bool isUserAllowToSeeAudit(int userId, Guid auditId)
{
    // ...
}