为 Azure App Service WebApi OWIN 启用基本身份验证

Enable Basic Authentication for Azure App Service WebApi OWIN

我有一个 ASP.Net 4.5.2 MVC5 WebApi 5.2.3 应用程序,它通常通过 OWIN 管道使用 OAuth/Jwt 安全性。现在,我有一个外部服务 web-hook,它通过 POST 调用我的一个 WebApi 控制器。此 webhook 服务仅支持基于 HTTPS 的基本身份验证。这很好,如果我只能让我的过滤器(或其他)工作。

我的过滤器基于一个很好的 example by Rick Strahl 但是,当我添加这个 class 然后我的 [MyBasicAuthFilter] 在我的 ApiController 操作之上时,我得到的只是一条服务器错误 500 消息,其中包含 { "Message":"An error has occurred."}。

当我从 POST 调用中删除 "Authorization: Basic dXNlcasdfasfdsfasd=" header 时,代码会正常进入我的新过滤器。哦,太好了 :-[。因此,管道中较早的部分不喜欢这个 header 存在的事实。阅读了一下后,我怀疑 IIS 设置。但是在尝试通过我的 Web.Config 强制 Azure App Service IIS 并迅速阻止对我网站的所有访问之后,我想我应该来这里寻求一些提示。

Ideas/Tips?

好的,这是关于这里发生的事情的独家新闻。现有的 JsonWebTokenValidationHandler(消息处理程序)仅在 header 中寻找 "Bearer" 令牌。如果出现任何其他令牌,那么它会将其标记为 return HttpStatus 500 的潜在攻击。如果 header 中根本没有令牌,那么它将 return 挑战 HttpStatus 401 . 无论如何,我的简约 BasicAuth 要求(在 JWT 令牌之上)的最佳解决方案是我 JsonWebTokenValidationHandler.cs 的以下(完整)内容。这现在可以处理 Bearer (JWT) 令牌和 Basic Auth 令牌。欢迎评论。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web;

public class JsonWebTokenValidationHandler : DelegatingHandler
{
    public string SymmetricKey { get; set; } // A0 client secret

    public string Audience { get; set; } // A0 client ID

    public string Issuer { get; set; } // A0 our A0 domain

    private static bool TryRetrieveToken(HttpRequestMessage request, out string token)
    {
        token = null;
        IEnumerable<string> authzHeaders;

        if (!request.Headers.TryGetValues("Authorization", out authzHeaders) || authzHeaders.Count() > 1)
        {
            // Fail if no Authorization header or more than one Authorization headers  
            // are found in the HTTP request  
            return false;
        }

        // Remove the bearer token scheme prefix and return the rest as ACS token  
        var bearerToken = authzHeaders.ElementAt(0);
        token = bearerToken.StartsWith("Bearer ") ? bearerToken.Substring(7) : bearerToken;

        return true;
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        string token;
        HttpResponseMessage errorResponse = null;

        if (TryRetrieveToken(request, out token))
        {
            try
            {
                // ++
                // Added BasicAuth to our JWT Auth
                // If it is a BasicAuth token, then check it first and bypass JWT validation
                // See Asp.Net WebApi security book for infos on this.
                //
                if (token.Contains("Basic"))
                {
                    var authHeader = Encoding.Default.GetString(Convert.FromBase64String(token.Substring(6)));

                    // find first : as password allows for :
                    int idx = authHeader.IndexOf(':');
                    if (idx < 0)
                        return null;

                    string username = authHeader.Substring(0, idx);
                    string password = authHeader.Substring(idx + 1);

                    // Check the user's validity and reuse the JWT challenge 401 exception if not valid.
                    if (!ValidateBasicAuthUser(username, password))
                    {
                        throw new JsonWebToken.TokenValidationException(
                            $"Username or password missmatch for username: {username}");
                    }

                    // If OK, then let them in and continue
                    var identity = new BasicAuthenticationIdentity(username, password);
                    var principal = new GenericPrincipal(identity, null);

                    Thread.CurrentPrincipal = principal;
                    if (HttpContext.Current != null)
                    {
                        HttpContext.Current.User = Thread.CurrentPrincipal;
                    }
                }
                else
                {
                    // --

                    // Handle JWT Tokens here or return server error 500 if it is not a valid token.
                    var secret = this.SymmetricKey.Replace('-', '+').Replace('_', '/');

                    Thread.CurrentPrincipal = JsonWebToken.ValidateToken(
                        token,
                        secret,
                        audience: this.Audience,
                        checkExpiration: true,
                        issuer: this.Issuer);

                    if (HttpContext.Current != null)
                    {
                        HttpContext.Current.User = Thread.CurrentPrincipal;
                    }
                }
            }
            catch (JWT.SignatureVerificationException ex)
            {
                errorResponse = request.CreateErrorResponse(HttpStatusCode.Unauthorized, ex);
            }
            catch (JsonWebToken.TokenValidationException ex)
            {
                errorResponse = request.CreateErrorResponse(HttpStatusCode.Unauthorized, ex);
            }
            catch (Exception ex)
            {
                errorResponse = request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex);
            }
        }

        return errorResponse != null ?
            Task.FromResult(errorResponse) :
            base.SendAsync(request, cancellationToken);
    }

    /// <summary>
    /// rht: Check our BasicAuth passwords e.g. from Cb WebHook
    /// Usage: http://weblog.west-wind.com/posts/2013/Apr/18/A-WebAPI-Basic-Authentication-Authorization-Filter
    /// </summary>
    /// <param name="username"></param>
    /// <param name="password"></param>
    /// <returns></returns>
    private bool ValidateBasicAuthUser(string username, string password)
    {
        if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
            return false;

        var ok = false;
        //
        // Our valid usernames and passwords
        //
        switch (username)
        {
            case "your user 1 here":
                {
                    if (password == "your password here") ok = true;
                }
                break;
            case "your user 2 here or from DB, whatever":
                {
                    if (password == "pwd") ok = true;
                }
                break;
        }

        return ok;
    }
} // cl

//
// See: http://weblog.west-wind.com/posts/2013/Apr/18/A-WebAPI-Basic-Authentication-Authorization-Filter
// And Asp.Net WebApi Security book.
public class BasicAuthenticationIdentity : GenericIdentity
{
    public BasicAuthenticationIdentity(string name, string password)
        : base(name, "Basic")
    {
        this.Password = password;
    }

    /// <summary>
    /// Basic Auth Password for custom authentication
    /// </summary>
    public string Password { get; set; }
}