Asp.net-Web-Api2

使用 OWIN 和 ASP.NET WEB API 的基本身份驗證中間件

  • July 22, 2015

我創建了一個 ASP.NET WEB API 2.2 項目。我為 Visual Studio 中可用的個人帳戶使用了基於 Windows Identity Foundation 的模板,請參見此處

Web 客戶端(用 angularJS 編寫)使用帶有 Web 瀏覽器 cookie 的 OAUTH 實現來儲存令牌和刷新令牌。我們受益於有用的UserManagerRoleManager類來管理使用者及其角色。OAUTH 和 Web 瀏覽器客戶端一切正常。

但是,對於基於桌面的客戶端的一些追溯兼容性問題,我還需要支持基本身份驗證。理想情況下,我希望*$$ Authorize $$,$$ Authorize(Role = “administrators”) $$*等屬性與 OAUTH 和基本身份驗證方案一起使用。

因此,按照LeastPrivilege中的程式碼,我創建了一個繼承自AuthenticationMiddleware的 OWIN BasicAuthenticationMiddleware。我來到了以下實現。對於BasicAuthenticationMiddleWare,只有 Handler 與 Leastprivilege 的程式碼相比發生了變化。實際上我們使用 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 文件中,“個人使用者”模板插入了以下幾行。

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

引用自:https://stackoverflow.com/questions/31549855