ASP.NET Core编程实现基本身份认证

 

概览

       在HTTP中,基本认证(Basic access authentication,简称BA认证)是一种用来允许网页浏览器或其他客户端程序在请求资源时提供用户名和口令形式的身份凭证的一种登录验证方式,不要求cookie,session identifier、login page等标记或载体。  

优点:基本上所有流行的网页浏览器都支持BA认证。

缺点:明文传输**和口令(容易被拦截); 没有对服务器返回的信息提供保护。

 https://en.wikipedia.org/wiki/Basic_access_authentication

特征

        基本身份认证原理不保证传输凭证的安全性,他们仅仅只是被based64编码,并没有encrypted或者hashed。

一般部署在可信任网络,在公开网络中部署BA认证通常要与https结合使用。

因为基本身份认证字段在HTTP Header 中发送,所以web browser需要在一个合理的时间内缓存凭据,缓存策略因浏览器不同而异,IE默认缓存15 分钟。 HTTP不会为客户端提供一个logout方法,但是有很多方法在浏览器中可以清除缓存凭据。

<script>document.execCommand('ClearAuthenticationCache');</script>

 IIS-WebSite-身份认证:

ASP.NET Core编程实现基本身份认证

 

基本身份认证的标准协议

BA认证协议的实施主要依靠约定的请求头/响应头, 典型的浏览器和服务器的BA认证流程,包含以下步骤:

1.浏览器请求一个需要身份认证的页面,但是没有提供用户名和口令。这通常是用户在地址栏输入一个URL,或是打开了一个指向该页面的链接。

2.服务端响应一个401应答码,并提供一个认证域。

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="180.76.176.244"

3.接到应答后,浏览器显示该认证域并提示输入用户名和口令。此时用户可以选择确定或取消。

4.用户输入了用户名和口令后,浏览器会在原请求方式上增加认证消息头 

       Authorization:{base64encode(username+":"+password)},然后重新发送再次尝试。

// 下面的例子显示:认证双方约定在编码字符串前加上了此次认证的方式和一个空格: Basic 

Authorization: Basic RmlzYWRzYWQ6YXNkYWRz

5.服务器接受该认证凭据并返回页面;如果用户凭据非法或无效,服务器可能再次返回401应答码,客户端可以再次提示用户输入口令。

 ASP.NET Core编程实现基本身份认证

 注意:
① 客户端用户名,口令之间用冒号分隔
② 服务端返回认证域,可能会指定凭据字符串的编码方式:

WWW-Authenticate: Basic realm="User Visible Realm", charset="UTF-8"

       此时客户端在准备凭据字符串的时候,要使用指定的charset编码凭据

③ 客户端未认证时,服务端需要给客户端必要的认证域提示,效果如下:

  ASP.NET Core编程实现基本身份认证

基本身份认证编程实践

     项目中利用StaticFileMiddleware+DirectoryBrowserMiddleware做了一个网页文件服务器, 现对该文件服务器设置BA认证。

ASP.NET Core服务端实现BA认证:

     ① [准备工作]  实现基本身份认证的标准协议,含认证方式、认证失败的提示 

     ②  [ 准备工作]  Asp.Net Core 添加以上认证计划 

     ③  实现基本身份认证交互中间件  BasicAuthenticationMiddleware

     ④  启用以上中间件

  // 第①部分:实现BA认证的基本协议

  public static class BasicAuthenticationScheme
 {
   public const string DefaultScheme = "Basic";
 }

  public class BasicAuthenticationOption : AuthenticationSchemeOptions
  {
     public string Realm { get; set; }
     public string UserName { get; set; }
     public string UserPwd { get; set; }
  }

public class BasicAuthenticationHandler : AuthenticationHandler<BasicAuthenticationOption>
   {
       private BasicAuthenticationOption authOptions;
       public BasicAuthenticationHandler(
           IOptionsMonitor<BasicAuthenticationOption> options,
           ILoggerFactory logger,
           UrlEncoder encoder,
           ISystemClock clock)
           : base(options, logger, encoder, clock)
       {
           authOptions = options.CurrentValue;
       }

       /// <summary>
       /// BA认证协议
       /// </summary>
       /// <returns></returns>
       protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
       {
           if (!Request.Headers.ContainsKey("Authorization"))
               return AuthenticateResult.Fail("Missing Authorization Header");
           string username, password;
           try
           {
               var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
               var credentialBytes = Convert.FromBase64String(authHeader.Parameter);
               var credentials = Encoding.UTF8.GetString(credentialBytes).Split(':');
                username = credentials[0];
                password = credentials[1];
                var isValidUser= IsAuthorized(username,password);
               if(isValidUser== false)
               {
                   return AuthenticateResult.Fail("Invalid username or password");
               }
           }
           catch
           {
               return AuthenticateResult.Fail("Invalid Authorization Header");
           }

           var claims = new[] {
               new Claim(ClaimTypes.NameIdentifier,username),
               new Claim(ClaimTypes.Name,username),
           };
           var identity = new ClaimsIdentity(claims, Scheme.Name);
           var principal = new ClaimsPrincipal(identity);
           var ticket = new AuthenticationTicket(principal, Scheme.Name);
           return await Task.FromResult(AuthenticateResult.Success(ticket));
       }

       /// <summary>
       /// 重写该方法以体现身份验证挑战(401)时发生时提示
       /// </summary>
       /// <param name="properties"></param>
       /// <returns></returns>
       protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
       {
           Response.Headers["WWW-Authenticate"] = $"Basic realm=\"{Options.Realm}\",charset=\"utf-8\"";
           await base.HandleChallengeAsync(properties);
       }

       /// <summary>  进行BA认证,此处不关注该方法
       /// override the method to influence what happens when an forbidden response (403) 
       /// </summary>
       /// <param name="properties"></param>
       /// <returns></returns>
       protected override async Task HandleForbiddenAsync(AuthenticationProperties properties)
       {
          await base.HandleForbiddenAsync(properties); 
       }
       //  BA认证:账户、密码匹配逻辑
       private bool IsAuthorized(string username, string password)
       {
           return username.Equals(authOptions.UserName, StringComparison.InvariantCultureIgnoreCase)
                  && password.Equals(authOptions.UserPwd);
       }
   }

 

// 第②部分:添加BA认证计划
services.AddAuthentication(BasicAuthenticationScheme.DefaultScheme) .AddScheme
<BasicAuthenticationOption, BasicAuthenticationHandler>(BasicAuthenticationScheme.DefaultScheme,null);
// 第③部分
public static class BasicAuthentication { public static void UseBasicAuthentication(this IApplicationBuilder app) { app.UseMiddleware<BasicAuthenticationMiddleware>(); } } public class BasicAuthenticationMiddleware { private readonly RequestDelegate _next; private readonly ILogger _logger; public BasicAuthenticationMiddleware(RequestDelegate next, ILoggerFactory LoggerFactory) { _next = next;
_logger
= LoggerFactory.CreateLogger<BasicAuthenticationMiddleware>(); } public async Task Invoke(HttpContext httpContext, IAuthenticationService authenticationService) { var authenticated = await authenticationService.AuthenticateAsync(httpContext, BasicAuthenticationScheme.DefaultScheme); _logger.LogInformation("Access Status:" + authenticated.Succeeded); if (!authenticated.Succeeded) { await authenticationService.ChallengeAsync(httpContext, BasicAuthenticationScheme.DefaultScheme, new AuthenticationProperties { }); return; } await _next(httpContext); } }

 

 

// 第④部分: 这里我使用UseWhen方式注册认证中间件: 对该网站部分资源(_protectedResourceOption.Path)开启基本身份认证
app.UseWhen(
            predicate:x => x.Request.Path.StartsWithSegments(new PathString(_protectedResourceOption.Path)),
            configuration:appBuilder => { appBuilder.UseBasicAuthentication(); }
    );

 

 

客户端:

  1.  添加认证请求Handler
  2.  以上述Handler 配置命名HtttpClient
    /// <summary>
    /// BA认证请求Handler
/// </summary> public class BasicAuthenticationClientHandler : HttpClientHandler { public static string BAHeaderNames = "authorization"; private RemoteBasicAuth _remoteAccount; public BasicAuthenticationClientHandler(RemoteBasicAuth remoteAccount) { _remoteAccount = remoteAccount; AllowAutoRedirect = false; UseCookies = true; } protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var authorization = $"{_remoteAccount.UserName}:{_remoteAccount.Password}"; var authorizationBased64 = "Basic " + Convert.ToBase64String(new ASCIIEncoding().GetBytes(authorization)); request.Headers.Remove(BAHeaderNames); request.Headers.Add(BAHeaderNames, authorizationBased64); return base.SendAsync(request, cancellationToken); } }

 

// 配置命名HttpClient
services.AddHttpClient("eqid-ba-request", x => x.BaseAddress = new Uri(_proxyOption.Scheme +"://"+ _proxyOption.Host+":"+_proxyOption.Port ) ) .ConfigurePrimaryHttpMessageHandler(y => new BasicAuthenticationClientHandler(_remoteAccount){} ) .SetHandlerLifetime(TimeSpan.FromMinutes(2)) .AddPolicyHandler(GetRetryPolicy());

 

That's All .  BA认证是随处可见的基础认证协议,本文期待以最清晰的方式帮助你理解协议,最优雅的方式提供编程实践思路。

 

作者:JulianHuang

感谢您的认真阅读,如有问题请大胆斧正;觉得有用,请下方ASP.NET Core编程实现基本身份认证或加关注。

本文欢迎转载,但请保留此段声明,且在文章页面明显位置注明本文的作者及原文链接。