简体   繁体   中英

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

I've got a couple of authorization polcies registered:

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

& then I set a default authorization policy across all endpoints:

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

On the endpoints where I want to specify the mutating policy, I currently do the following:

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

What I really want is a bit more control to declaritively apply the mutating policy based on the HttpVerb (ie POST, PUT, PATCH, DELETE).

Any idea on how to achieve that? Bonus points for allowing me to use other attributes on the controller method/class, not just [HttpPost] etc.

NB: I've seen solutions floating around that involve casting the content (and seem to revolve around a single access policy). I'd really rather stick with multiple access policies.

If I get stuck, I might end up writing a convention test for it.

You can implement a custom RequireAuthorization extension that takes HTTP verb filtering function as an argument and checks each endpoint metadata for 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());
        }
    }

And then use this extension next to the original one:

        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);
            });
        }


The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM