授权中间件不识别通过 MVC 约定添加的策略

Authorization middleware doesn't recognize policies added through MVC conventions

这是一个 ASP.NET 核心 3.1 网络应用程序。我正在使用改编自 this blog post:

的代码向我的 ApiController read/write 方法添加策略
public class DefaultApiAuthorizationConvention : IActionModelConvention
{
    private readonly string _apiReadPolicyName;
    private readonly string _apiWritePolicyName;

    public DefaultApiAuthorizationConvention(string apiReadPolicyName = Constants.POLICY_API_READ, string apiWritePolicyName = Constants.POLICY_API_WRITE)
    {
        _apiReadPolicyName = apiReadPolicyName;
        _apiWritePolicyName = apiWritePolicyName;
    }

    public void Apply(ActionModel action)
    {
        // only apply to API controllers
        if (!action.Controller.Attributes.Any(a => a is ApiControllerAttribute))
            return;

        // Require API Write policy for POST/PUT/DELETE methods, and API Read policy for GET methods
        if (action.Attributes.Any(a => a is HttpPostAttribute || a is HttpPutAttribute || a is HttpDeleteAttribute))
        {
            action.Filters.Add(new AuthorizeFilter(policy: _apiWritePolicyName));
        }
        else if (action.Attributes.Any(a => a is HttpGetAttribute))
        {
            action.Filters.Add(new AuthorizeFilter(policy: _apiReadPolicyName));
        }
    }
}

添加了约定并在 Startup.cs 中注册了策略,这一切都很好。我还定义了默认和后备策略。

services.AddMvc(options =>
{
    options.Conventions.Add(new AppAuth.DefaultApiAuthorizationConvention());
});
services.AddAuthorization(options =>
{
    options.FallbackPolicy = options.DefaultPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .RequireRole(WebConstants.ROLE_SITE_ADMIN, WebConstants.ROLE_SITE_BROWSER)
        .Build();
    // general API policy
    options.AddPolicy(AppAuth.Constants.POLICY_API_READ, policy => 
        policy.RequireAuthenticatedUser()
              .RequireRole(WebConstants.ROLE_SITE_ADMIN, WebConstants.ROLE_API_READ));
    options.AddPolicy(AppAuth.Constants.POLICY_API_WRITE, policy => 
        policy.RequireAuthenticatedUser()
              .RequireRole(WebConstants.ROLE_SITE_ADMIN, WebConstants.ROLE_API_WRITE));
    // other policies omitted
});

控制器就像这样,控制器或方法上没有 [Authorize] 属性。

[ApiController]
[Route("api/[controller]")]
public class ValuesController : ControllerBase
{
    [HttpGet]
    public async Task<IActionResult> Get()
    {
        return Ok(await _service.Get());
    }
}

问题是当根据策略属于 ROLE_API_READ 的用户调用 API 方法时,AuthorizationMiddleware 总是将他们标记为未授权。

查看中间件源码,发现:

var authorizeData = endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>() ?? Array.Empty<IAuthorizeData>();

我在我的项目中复制了一份class以便调试,当我在这里设置一个断点时,authorizeData变成了一个空数组。那么我们检查 EndpointMetadataCollection class:

public IReadOnlyList<T> GetOrderedMetadata<T>() where T : class
{
    if (_cache.TryGetValue(typeof(T), out var result))
    {
        return (T[])result;
    }
    return GetOrderedMetadataSlow<T>(); // private method that gets the metadata real-time
}

这个_cache变量在构造函数中设置,包含IAuthorizeData的空数组:

Metadata 对象本身包含约定添加的 AuthorizeFilter

我确实联系了博客作者,令人惊讶的是他似乎不是 Stack Overflow,但我想把它开放给更广泛的社区,看看我的选择是什么。

在此先感谢您的帮助。

过滤器与端点元数据不同。您需要编写一个约定来改变 ActionModel:

上的 SelectorModel
public class DefaultApiAuthorizationConvention : IActionModelConvention
{
    private readonly string _apiReadPolicyName;
    private readonly string _apiWritePolicyName;

    public DefaultApiAuthorizationConvention(string apiReadPolicyName = Constants.POLICY_API_READ, string apiWritePolicyName = Constants.POLICY_API_WRITE)
    {
        _apiReadPolicyName = apiReadPolicyName;
        _apiWritePolicyName = apiWritePolicyName;
    }

    public void Apply(ActionModel action)
    {
        // only apply to API controllers
        if (!action.Controller.Attributes.Any(a => a is ApiControllerAttribute))
            return;

        // Require API Write policy for POST/PUT/DELETE methods, and API Read policy for GET methods
        if (action.Attributes.Any(a => a is HttpPostAttribute || a is HttpPutAttribute || a is HttpDeleteAttribute))
        {
            foreach (var s in action.Selectors)
            {
                s.EndpointMetadata.Add(new AuthorizeAttribute(_apiWritePolicyName));
            }
        }
        else if (action.Attributes.Any(a => a is HttpGetAttribute))
        {
            foreach (var s in action.Selectors)
            {
                s.EndpointMetadata.Add(new AuthorizeAttribute(_apiReadPolicyName));
            }
        }
    }

PS:Auth 在 ASP.NET Core 3.0 中被重写为基于端点元数据,默认情况下不再使用 MVC 过滤器。