使用 OWIN 和 ASP.NET WEB API 的基本身份验证中间件

Basic Authentication Middleware with OWIN and ASP.NET WEB API

我创建了一个 ASP.NET WEB API 2.2 项目。我为 visual studio see it here.

中可用的个人帐户使用了基于 Windows Identity Foundation 的模板

Web 客户端(在 angularJS 中编写)使用带有 Web 浏览器 cookie 的 OAUTH 实现来存储令牌和刷新令牌。我们受益于有用的 UserManagerRoleManager 类 来管理用户及其角色。 OAUTH 和网络浏览器客户端一切正常。

但是,对于与基于桌面的客户端的某些追溯兼容性问题,我还需要支持基本身份验证。理想情况下,我希望 [Authorize][Authorize(Role = "administrators")] 等属性与 OAUTH 一起使用和基本身份验证方案。

因此,根据 LeastPrivilege 中的代码,我创建了一个继承自 AuthenticationMiddleware 的 OWIN BasicAuthenticationMiddleware。 我来到了以下实现。对于 BasicAuthenticationMiddleWare,与 Leastprivilege 的代码相比,只有 Handler 发生了变化。实际上我们使用 ClaimsIdentity 而不是一系列 Claim.

 class BasicAuthenticationHandler: AuthenticationHandler<BasicAuthenticationOptions>
{
    private readonly string _challenge;

    public BasicAuthenticationHandler(BasicAuthenticationOptions options)
    {
        _challenge = "Basic realm=" + options.Realm;
    }

    protected override async Task<AuthenticationTicket> AuthenticateCoreAsync()
    {
        var authzValue = Request.Headers.Get("Authorization");
        if (string.IsNullOrEmpty(authzValue) || !authzValue.StartsWith("Basic ", StringComparison.OrdinalIgnoreCase))
        {
            return null;
        }

        var token = authzValue.Substring("Basic ".Length).Trim();
        var claimsIdentity = await TryGetPrincipalFromBasicCredentials(token, Options.CredentialValidationFunction);

        if (claimsIdentity == null)
        {
            return null;
        }
        else
        {
            Request.User = new ClaimsPrincipal(claimsIdentity);
            return new AuthenticationTicket(claimsIdentity, new AuthenticationProperties());
        }
    }

    protected override Task ApplyResponseChallengeAsync()
    {
        if (Response.StatusCode == 401)
        {
            var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode);
            if (challenge != null)
            {
                Response.Headers.AppendValues("WWW-Authenticate", _challenge);
            }
        }

        return Task.FromResult<object>(null);
    }

    async Task<ClaimsIdentity> TryGetPrincipalFromBasicCredentials(string credentials,
        BasicAuthenticationMiddleware.CredentialValidationFunction validate)
    {
        string pair;
        try
        {
            pair = Encoding.UTF8.GetString(
                Convert.FromBase64String(credentials));
        }
        catch (FormatException)
        {
            return null;
        }
        catch (ArgumentException)
        {
            return null;
        }

        var ix = pair.IndexOf(':');
        if (ix == -1)
        {
            return null;
        }

        var username = pair.Substring(0, ix);
        var pw = pair.Substring(ix + 1);

        return await validate(username, pw);
    }

然后在 Startup.Auth 中我声明了以下用于验证身份验证的委托(简单地检查用户是否存在以及密码是否正确并生成关联的 ClaimsIdentity

 public void ConfigureAuth(IAppBuilder app)
    {
        app.CreatePerOwinContext(DbContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);

        Func<string, string, Task<ClaimsIdentity>> validationCallback = (string userName, string password) =>
        {
            using (DbContext dbContext = new DbContext())
            using(UserStore<ApplicationUser> userStore = new UserStore<ApplicationUser>(dbContext))
            using(ApplicationUserManager userManager = new ApplicationUserManager(userStore))
            {
                var user = userManager.FindByName(userName);
                if (user == null)
                {
                    return null;
                }
                bool ok = userManager.CheckPassword(user, password);
                if (!ok)
                {
                    return null;
                }
                ClaimsIdentity claimsIdentity = userManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
                return Task.FromResult(claimsIdentity);
            }
        };

        var basicAuthOptions = new BasicAuthenticationOptions("KMailWebManager", new BasicAuthenticationMiddleware.CredentialValidationFunction(validationCallback));
        app.UseBasicAuthentication(basicAuthOptions);
        // Configure the application for OAuth based flow
        PublicClientId = "self";
        OAuthOptions = new OAuthAuthorizationServerOptions
        {
            TokenEndpointPath = new PathString("/Token"),
            Provider = new ApplicationOAuthProvider(PublicClientId),
            //If the AccessTokenExpireTimeSpan is changed, also change the ExpiresUtc in the RefreshTokenProvider.cs.
            AccessTokenExpireTimeSpan = TimeSpan.FromHours(2),
            AllowInsecureHttp = true,
            RefreshTokenProvider = new RefreshTokenProvider()
        };

        // Enable the application to use bearer tokens to authenticate users
        app.UseOAuthBearerTokens(OAuthOptions);
    }

但是,即使设置了 Handler 的 AuthenticationAsyncCore 方法中的 Request.User [Authorize] 属性未按预期工作:每次我尝试使用基本身份验证方案时都以错误 401 未授权进行响应。 知道出了什么问题吗?

我找到了罪魁祸首,在 WebApiConfig.cs 文件中 'individual user' 模板插入了以下行。

//// Web API configuration and services
//// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

因此我们还必须注册我们的 BasicAuthenticationMiddleware

config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
config.Filters.Add(new HostAuthenticationFilter(BasicAuthenticationOptions.BasicAuthenticationType));

其中 BasicAuthenticationType 是传递给 BasicAuthenticationOptions

的基本构造函数的常量字符串 "Basic"
public class BasicAuthenticationOptions : AuthenticationOptions
{
    public const string BasicAuthenticationType = "Basic";

    public BasicAuthenticationMiddleware.CredentialValidationFunction CredentialValidationFunction { get; private set; }

    public BasicAuthenticationOptions( BasicAuthenticationMiddleware.CredentialValidationFunction validationFunction)
        : base(BasicAuthenticationType)
    {
        CredentialValidationFunction = validationFunction;
    }
}