How to tell [Authorize] attribute to use Basic aut

2019-06-07 01:22发布

问题:

I'm following along Chapter 8 in Pro ASP .NET Web API Security by Badri L., trying to implement basic authentication for a web application that will be consumed by HTTP/JS clients.

I've added the following Authentication Handler to my WebAPI project:

public class AuthenticationHandler : DelegatingHandler
    {
        private const string SCHEME = "Basic";
        protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
                                                                               System.Threading.CancellationToken
                                                                                   cancellationToken)
        {
            try
            {
                // Request Processing
                var headers = request.Headers;
                if (headers.Authorization != null && SCHEME.Equals(headers.Authorization.Scheme))
                {
                    Encoding encoding = Encoding.GetEncoding("iso-8859-1");
                    // etc

When I decorate methods in my API with [Authorize] and set a breakpoint at the if statement above, headers.Authorization is null upon the first request. If I continue on this break, the if statement gets hit again, this time with headers.Authorization.Scheme as "Negotiate", instead of "Basic":

I have registered my Handler in WebApiConfig:

config.MessageHandlers.Add(new AuthenticationHandler());

But I'm at a loss as to why the Authorize attribute is not respecting basic authentication, or why - since the scheme is not "basic" and the if() in my handler returns false - I'm getting data from my API controller when I should be getting 401 Unauthorized.

I have not specified any authenticationType in my web.config.

Any idea what I'm doing wrong?

Edit: Full Handler:

public class AuthenticationHandler : DelegatingHandler
    {
        private const string SCHEME = "Basic";
        protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
                                                                               System.Threading.CancellationToken
                                                                                   cancellationToken)
        {
            try
            {
                // Request Processing
                var headers = request.Headers;
                if (headers.Authorization != null && SCHEME.Equals(headers.Authorization.Scheme))
                {
                    Encoding encoding = Encoding.GetEncoding("iso-8859-1");
                    string credentials = encoding.GetString(Convert.FromBase64String(headers.Authorization.Parameter));
                    string[] parts = credentials.Split(':');
                    string userId = parts[0].Trim();
                    string password = parts[1].Trim();
                    // TODO: Authentication of userId and Pasword against credentials store here
                    if (true)
                    {
                        var claims = new List<Claim>
                            {
                                new Claim(ClaimTypes.Name, userId),
                                new Claim(ClaimTypes.AuthenticationMethod, AuthenticationMethods.Password)
                            };
                        var principal = new ClaimsPrincipal(new[] {new ClaimsIdentity(claims, SCHEME)});
                        Thread.CurrentPrincipal = principal;
                        if (HttpContext.Current != null)
                            HttpContext.Current.User = principal;
                    }

                }

                var response = await base.SendAsync(request, cancellationToken);
                // Response processing
                if (response.StatusCode == HttpStatusCode.Unauthorized)
                {
                    response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue(SCHEME));
                }
                return response;
            }
            catch (Exception)
            {
                // Error processing
                var response = request.CreateResponse(HttpStatusCode.Unauthorized);
                response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue(SCHEME));
                return response;
            }
        }

    }

回答1:

When I decorate methods in my API with [Authorize] and set a breakpoint at the if statement above, headers.Authorization is null upon the first request.

This is expected. This is how it is supposed to work. Browser will show the popup to get credentials from the user only when it receives a 401. Subsequent request will have the authorization header with credentials in the basic scheme.

If I continue on this break, the if statement gets hit again, this time with headers.Authorization.Scheme as "Negotiate", instead of "Basic":

Yes, as answered by Dominick (is it Dominick?), you have Windows Authentication enabled and that is the reason for you getting the Negotiate scheme back from the browser. You must disable all authentication methods either in the config or using IIS manager.

But I'm at a loss as to why the Authorize attribute is not respecting basic authentication, or why - since the scheme is not "basic" and the if() in my handler returns false - I'm getting data from my API controller when I should be getting 401 Unauthorized.

Authorize attribute knows nothing about basic authentication. All it cares is if the identity is authenticated or not. Since you have anonymous authentication enabled (I guess that is the case), Authorize attribute is happy and there is no 401 for the message handler response handling part to add the WWW-Authenticate response header indicating that web API expects credentials in the Basic scheme.



回答2:

Looks like you have Windows authentication enabled for your app in IIS. Disable all authentication methods in config (system.web and system.webServer) and allow anonymous since you do your own authentication in the message handler.



回答3:

i think you need to register the handler on global.asa

This article looks good: http://byterot.blogspot.com.br/2012/05/aspnet-web-api-series-messagehandler.html

your global.asa.cs would have something like this:

public static void Application_Start(GlobalFilterCollection filters) {
   //...
   GlobalConfiguration.Configuration.MessageHandlers.Add(new AuthenticationHandler());
}