如何在 ASP.Net Core (WebAPI) 中基于 HTTP 谓词和其他属性以声明方式指定授权策略

How to declaritively specify authorization policy based on HTTP verb and other attributes in ASP.Net Core (WebAPI)

我已经注册了几个授权策略:

ConfigureServices()
{
    services.AddAuthorization(authorisationOptions =>
    {
        authorisationOptions.AddPolicy(StandardAuthorizationPolicy.Name, StandardAuthorizationPolicy.Value);
        authorisationOptions.AddPolicy(MutatingActionAuthorizationPolicy.Name, MutatingActionAuthorizationPolicy.Value);
    }); 
}

& 然后我在所有端点上设置默认授权策略:

Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseEndpoints(endpoints =>
    {
        endpoints
            .MapControllers()
            .RequireAuthorization(StandardAuthorizationPolicy.Name); // Declaratively require the standard authorization policy on all controller endpoints
    });
}

在我想要指定变异策略的端点上,我目前执行以下操作:

[HttpPut]
[Authorize(MutatingActionAuthorizationPolicy.Name)] // Because 'PUT'. NOT DECLARATIVE! :-(
public async Task<IActionResult> AddOrUpdateOverride(SourceOverride sourceOverride, CancellationToken cancellationToken)
{
  // ..
}

我真正想要的是更多控制,以声明方式应用基于 HttpVerb 的变异策略(即 POST、PUT、PATCH、DELETE)。

知道如何实现吗?允许我在控制器上使用其他属性的奖励积分 method/class,而不仅仅是 [HttpPost]

注意:我已经看到涉及投射内容的解决方案(并且似乎围绕单一访问策略)。我真的宁愿坚持使用多重访问策略。

如果我遇到困难,我可能最终会为它编写一个约定测试。

您可以实现自定义 RequireAuthorization 扩展,它将 HTTP 动词过滤功能作为参数并检查每个端点元数据的 HttpMethodAttribute

public static class AuthorizationEndpointConventionBuilderExtensions
    {
        /// <summary>
        /// Adds authorization policies with the specified <see cref="IAuthorizeData"/> to the endpoint(s) filtered by supplied filter function
        /// </summary>
        /// <param name="builder">The endpoint convention builder.</param>
        /// <param name="filterOnHttpMethods">Filters http methods that we applying specific policies to</param>
        /// <param name="authorizeData">
        /// A collection of <paramref name="authorizeData"/>. If empty, the default authorization policy will be used.
        /// </param>
        /// <returns>The original convention builder parameter.</returns>
        public static TBuilder RequireAuthorizationForHttpMethods<TBuilder>(this TBuilder builder, Func<IEnumerable<HttpMethod>, bool> filterOnHttpMethods, params IAuthorizeData[] authorizeData)
            where TBuilder : IEndpointConventionBuilder
        {
            if (builder == null)
            {
                throw new ArgumentNullException(nameof(builder));
            }

            if (authorizeData == null)
            {
                throw new ArgumentNullException(nameof(authorizeData));
            }

            if (authorizeData.Length == 0)
            {
                authorizeData = new IAuthorizeData[] { new AuthorizeAttribute(), };
            }

            builder.Add(endpointBuilder =>
            {
                var appliedHttpMethodAttributes = endpointBuilder.Metadata
                .Where(x => x is HttpMethodAttribute)
                .Cast<HttpMethodAttribute>();

                if (appliedHttpMethodAttributes.Any(x => filterOnHttpMethods(x.HttpMethods
                                              .Select(method => new HttpMethod(method)))))
                {
                    foreach (var data in authorizeData)
                    {
                        endpointBuilder.Metadata.Add(data);
                    }
                }
            });
            return builder;
        }

        /// <summary>
        /// Adds authorization policies with the specified names to the endpoint(s) for filtered endpoints that return for filterOnHttpMethod
        /// </summary>
        /// <param name="builder">The endpoint convention builder.</param>
        /// <param name="filterOnHttpMethods">Filters http methods that we applying specific policies to</param>
        /// <param name="policyNames">A collection of policy names. If empty, the default authorization policy will be used.</param>
        /// <returns>The original convention builder parameter.</returns>
        public static TBuilder RequireAuthorizationForHttpMethods<TBuilder>(this TBuilder builder, Func<IEnumerable<HttpMethod>, bool> filterOnHttpMethods, params string[] policyNames)
        where TBuilder : IEndpointConventionBuilder
        {
            if (builder == null)
            {
                throw new ArgumentNullException(nameof(builder));
            }

            if (policyNames == null)
            {
                throw new ArgumentNullException(nameof(policyNames));
            }

            return builder.RequireAuthorizationForHttpMethods(filterOnHttpMethods, policyNames.Select(n => new AuthorizeAttribute(n)).ToArray());
        }
    }

然后在原始扩展旁边使用此扩展:

        app.UseEndpoints(endpoints =>
        {
            var mutatingHttpMethods = new HashSet<HttpMethod>()
            {
                HttpMethod.Post,
                HttpMethod.Put,
                HttpMethod.Delete
            };

            endpoints
                .MapControllers()
                .RequireAuthorization(StandardAuthorizationPolicy.Name)
                .RequireAuthorizationForHttpMethods(httpMethods => 
                 httpMethods.Any(httpMethod => mutatingHttpMethods.Contains(httpMethod)), 
                 MutatingActionAuthorizationPolicy.Name);
            });
        }