一、概述

      Basic认证是一种较为简单的HTTP认证方式,客户端通过明文(Base64编码格式)传输用户名和密码到服务端进行认证,通常需要配合HTTPS来保证信息传输的安全。

二、剖析

       1.当打开需要认证的页面时,会弹出一个对话框,要求输入用户名和密码

2.使用Fiddler监听请求,可以看到在未进行认证或认证失败的情况下,服务端会返回401 Unauthorized给客户端,并附带Challenge(质询),即在Response Header中添加WWW-Authenticate标头,浏览器识别到Basic后弹出对话框
Realm表示Web服务器中受保护文档的安全域(比如公司财务信息域和公司员工信息域),用来指示需要哪个域的用户名和密码,用" "包括起来(截图中没有,但最好加上)。

3.输入正确的用户名和密码,认证成功后,浏览器会将凭据信息缓存起来,那么以后再进入时,无需重复手动输入用户名和密码。
查看HTTP请求,可以看到Request Header中添加了Authorization标头,格式为:Authorization: <type> <credentials>

  • 类型为“Basic”
  • 凭证为“MTIzOjEyMw==”,是通过将“用户名:密码”格式的字符串经过的Base64编码得到的。而Base64不属于加密范畴,可以被逆向解码,等同于明文,因此Basic传输认证信息是不安全的

三、缺陷

1.用户名和密码明文(Base64)传输,需要配合HTTPS来保证信息传输的安全。
      2.即使密码被强加密,第三方仍可通过加密后的用户名和密码进行重放攻击。
      3.没有提供任何针对代理和中间节点的防护措施。
      4.假冒服务器很容易骗过认证,诱导用户输入用户名和密码。
        接下来,我会带大家一起去了解更为安全的摘要认证——Digest。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

介绍了Basic认证的工作原理和流程,接下来就赶紧通过代码来实践一下,以下教程基于ASP.NET Core WebApi框架。如有兴趣,可查看源码 https://gitee.com/jiajianjun/DotNetStackLibraries/tree/master/DotNetStackLibraries/AspNetCore.Authentication.Basic。

四、准备工作

在开始之前,先把最基本的用户名密码校验逻辑准备好,只有一个认证方法:

public class UserService
    {
          public static User Authenticate(string userName, string password)
         {
            //用户名、密码不为空且相等时认证成功
            if (!string.IsNullOrEmpty(userName)
                && !string.IsNullOrEmpty(password)
                && userName == password)
            {
                return new User()
                {
                    UserName = userName,
                    Password = password
                };
            }

return null;
         }
     }

public class User
     {
          public string UserName { get; set; }
          public string Password { get; set; }
      }

五、编码

1.首先,先确定使用的认证方案为Basic,并提供默认的的Realm

public const string AuthenticationScheme = "Basic";

public const string AuthenticationRealm = "Test Realm";

2.然后,解析HTTP Request获取到Authorization标头

private string GetCredentials(HttpRequest request)
         {
                 string credentials = null;

string authorization = request.Headers[HeaderNames.Authorization];
                 //请求中存在 Authorization 标头且认证方式为 Basic
                 if (authorization?.StartsWith(AuthenticationScheme, StringComparison.OrdinalIgnoreCase) == true)
                 {
                         credentials = authorization.Substring(AuthenticationScheme.Length).Trim();
                 } 
                 return credentials;
         }

3.接着通过Base64逆向解码,得到要认证的用户名和密码。如果认证失败,则返回401 Unauthorized(不推荐返回403 Forbidden,因为这会导致用户在不刷新页面的情况下无法重新尝试认证);如果认证成功,继续处理请求。

public class AuthorizationFilterAttribute : Attribute, IAuthorizationFilter
        {
                public void OnAuthorization(AuthorizationFilterContext context)
                {
                       //请求允许匿名访问
                       if (context.Filters.Any(item => item is IAllowAnonymousFilter)) return;

var credentials = GetCredentials(context.HttpContext.Request);
                       //已获取到凭证
                       if(credentials != null)
                       {
                             try
                             {
                                  //Base64逆向解码得到用户名和密码
                                  credentials = Encoding.UTF8.GetString(Convert.FromBase64String(credentials));
                                  var data = credentials.Split(':');
                                  if (data.Length == 2)
                                  {
                                        var userName = data[0];
                                        var password = data[1];
                                        var user = UserService.Authenticate(userName, password);
                                        //认证成功
                                        if (user != null) return;
                                  }
                             }
                            catch { }
                       }

//认证失败返回401
                      context.Result = new UnauthorizedResult();
                      //添加质询
                      AddChallenge(context.HttpContext.Response);
                  }
    
                  private void AddChallenge(HttpResponse response)
             => response.Headers.Append(HeaderNames.WWWAuthenticate, $"{ AuthenticationScheme } realm=\"{

AuthenticationRealm }\"");
           }

4.最后,在需要认证的Action上加上过滤器[AuthorizationFilter],大功告成!自己测试一下吧

六、封装中间件

ASP.NET Core相比ASP.NET最大的突破大概就是插件配置化了——通过将各个功能封装成中间件,应用AOP的设计思想配置到应用程序中。以下封装采用Jwt Bearer封装规范(.Net Core 2.2 类库)。

Nuget: Microsoft.AspNetCore.Authentication

1、首先封装常量

public static class BasicDefaults

{

public const string AuthenticationScheme = "Basic";

}

2.然后封装Basic认证的Options,包括Realm和事件,继承自Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions。在事件内部,我们定义了认证行为和质询行为,分别用来校验认证是否通过和在HTTP Response中添加质询信息。我们将认证逻辑封装成一个委托,与认证行为独立开来,方便用户使用委托自定义认证规则。

public class BasicOptions : AuthenticationSchemeOptions
       {
             public string Realm { get; set; }
             public new BasicEvents Events
             {
                   get => (BasicEvents)base.Events;
                   set => base.Events = value;
             }
       }

public class BasicEvents
       {
             public Func<ValidateCredentialsContext, Task> OnValidateCredentials { get; set; } = context => Task.CompletedTask;

public Func<BasicChallengeContext, Task> OnChallenge { get; set; } = context => Task.CompletedTask;

public virtual Task ValidateCredentials(ValidateCredentialsContext context) => OnValidateCredentials(context);

public virtual Task Challenge(BasicChallengeContext context) => OnChallenge(context);
        }

/// <summary>
        /// 封装认证参数信息上下文
        /// </summary>
        public class ValidateCredentialsContext : ResultContext<BasicAuthenticationOptions>
        {
             public ValidateCredentialsContext(HttpContext context, AuthenticationScheme scheme, BasicAuthenticationOptions   options) : base(context, scheme, options) { }
             public string UserName { get; set; }
             public string Password { get; set; }
         }

public class BasicChallengeContext : PropertiesContext<BasicOptions>
         {
               public BasicChallengeContext(
               HttpContext context,
               AuthenticationScheme scheme,
               BasicOptions options,
               AuthenticationProperties properties): base(context, scheme, options, properties)  { } 
               /// <summary>
               /// 在认证期间出现的异常
               /// </summary>
               public Exception AuthenticateFailure { get; set; }

/// <summary>
               /// 指定是否已被处理,如果已处理,则跳过默认认证逻辑
               /// </summary>
               public bool Handled { get; private set; }

/// <summary>
               /// 跳过默认认证逻辑
               /// </summary>
               public void HandleResponse() => Handled = true;
          }

3.接下来,就是对认证过程处理的封装了,需要继承自Microsoft.AspNetCore.Authentication.AuthenticationHandler

public class BasicHandler : AuthenticationHandler<BasicOptions>
          {
                   public BasicHandler(IOptionsMonitor<BasicOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) {  }

protected new BasicEvents Events
                   {
                          get => (BasicEvents)base.Events;
                          set => base.Events = value;
                   }
    
                   /// <summary>
                   /// 确保创建的 Event 类型是 BasicEvents
                   /// </summary>
                   /// <returns></returns>    
                   protected override Task<object> CreateEventsAsync() => Task.FromResult<object>(new BasicEvents());

protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
                   {
                          var credentials = GetCredentials(Request);
                          f(credentials == null)
                          {
                                return AuthenticateResult.NoResult();
                          }

try
                          {
                                credentials = Encoding.UTF8.GetString(Convert.FromBase64String(credentials));
                                var data = credentials.Split(':');
                                if(data.Length != 2)
                                {
                                       return AuthenticateResult.Fail("Invalid credentials, error format.");
                                }

var validateCredentialsContext = new ValidateCredentialsContext(Context, Scheme, Options)
                                {
                                       UserName = data[0],
                                       Password = data[1]
                                 };
                                await Events.ValidateCredentials(validateCredentialsContext);

//认证通过
                                if(validateCredentialsContext.Result?.Succeeded == true)
                                {
                                       var ticket = new AuthenticationTicket(validateCredentialsContext.Principal, Scheme.Name);
                                       return AuthenticateResult.Success(ticket);
                                }

return AuthenticateResult.NoResult();
                          }
                          catch(FormatException)
                         {
                                return AuthenticateResult.Fail("Invalid credentials, error format.");
                          }
                         catch(Exception ex)
                         {
                                return AuthenticateResult.Fail(ex.Message);
                         }
                   }

protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
                   {
                               var authResult = await HandleAuthenticateOnceSafeAsync();
                               var challengeContext = new BasicChallengeContext(Context, Scheme, Options, properties)
                               {
                                      AuthenticateFailure = authResult?.Failure
                               };
                               await Events.Challenge(challengeContext);
                               //质询已处理
                               if (challengeContext.Handled) return;
    
                               var challengeValue = $"{ BasicDefaults.AuthenticationScheme } realm=\"{ Options.Realm }\"";
                               var error = challengeContext.AuthenticateFailure?.Message;
                               if(!string.IsNullOrWhiteSpace(error))
                               {
                                     //将错误信息封装到内部
                                     challengeValue += $" error=\"{ error }\"";
                                }
    
                                Response.StatusCode = (int)HttpStatusCode.Unauthorized;
                                Response.Headers.Append(HeaderNames.WWWAuthenticate, challengeValue);
                   }

private string GetCredentials(HttpRequest request)
                   {
                          string credentials = null;

string authorization = request.Headers[HeaderNames.Authorization];
                          //存在 Authorization 标头
                          if (authorization != null)
                          {
                                 var scheme = BasicDefaults.AuthenticationScheme;
                                 if (authorization.StartsWith(scheme, StringComparison.OrdinalIgnoreCase))
                                 {
                                          credentials = authorization.Substring(scheme.Length).Trim();
                                 }
                           }

return credentials;
                      }
               }

4.最后,就是要把封装的接口暴露给用户了,这里使用扩展方法的形式,虽然有4个方法,但实际上都是重载,是同一种行为。

public static class BasicExtensions
     {
            public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder)
            => builder.AddBasic(BasicDefaults.AuthenticationScheme, _ => { });

public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, Action<BasicOptions> configureOptions)
             => builder.AddBasic(BasicDefaults.AuthenticationScheme, configureOptions);

public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, string authenticationScheme,              Action<BasicOptions> configureOptions)
             => builder.AddBasic(authenticationScheme, displayName: null, configureOptions: configureOptions);

public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, string authenticationScheme, string   displayName, Action<BasicOptions> configureOptions)
              => builder.AddScheme<BasicOptions, BasicHandler>(authenticationScheme, displayName, configureOptions);
      }

5.Basic认证库已经封装好了,我们创建一个ASP.NET Core WebApi程序来测试一下吧。

//在 ConfigureServices 中配置认证中间件
     public void ConfigureServices(IServiceCollection services)
     {
           services.AddAuthentication(BasicDefaults.AuthenticationScheme).AddBasic(options =>
           {
                  options.Realm = "Test Realm";   
                  options.Events = new BasicEvents
                  {
                         OnValidateCredentials = context =>
                         {
                              var user = UserService.Authenticate(context.UserName, context.Password);
                              if (user != null)
                              {
                                    //将用户信息封装到HttpContext
                                    var claim = new Claim(ClaimTypes.Name, context.UserName);
                                    var identity = new ClaimsIdentity(BasicDefaults.AuthenticationScheme);
                                    identity.AddClaim(claim);

context.Principal = new ClaimsPrincipal(identity);
                                   context.Success();
                              }
                              return Task.CompletedTask;
                         }
                   };
             });
       }

//在 Configure 中启用认证中间件
       public void Configure(IApplicationBuilder app, IHostingEnvironment env)
       {
               app.UseAuthentication();
        }

对了,一定要记得为需要认证的Action添加[Authorize]特性,否则前面做的一切都是徒劳+_+

HTTP认证之基本认证——Basic相关推荐

  1. HTTP的几种认证方式之BASIC 认证(基本认证)

    目录 1.BASIC 认证(基本认证)的步骤 2.BASIC 认证的的缺点 3.Java + SpringBoot 实现 BASIC 认证的Demo 4.测试 5.注意事项 6.Java + Spri ...

  2. 认证失败: 不能认证到服务器: 被拒绝的 Basic 挑战

    ubuntu 12.04,结果svn checkout的时候报: GNOME keyring [(null)] 的密码: svn: 方法 OPTIONS 失败于 "http://xxxxxx ...

  3. (chap8 确认访问用户身份的认证) DIGES认证(摘要认证)

    1. 定义 为了弥补BASIC不加密的缺点,DIGEST同样适用质询/响应的方式,但不会像BASIC直接发送明文,给对方的只是相应摘要以及知讯码产生的计算结果,所以比起BASIC认证,密码泄露的可能性 ...

  4. [转]asp.net权限认证:摘要认证(digest authentication)

    本文转自:http://www.cnblogs.com/lanxiaoke/p/6357501.html 摘要认证简单介绍 摘要认证是对基本认证的改进,即是用摘要代替账户密码,从而防止明文传输中账户密 ...

  5. RTSP鉴权认证之基础认证和摘要认证

    RTSP认证类型 基本认证(basic authentication):http 1.0提出的认证方案,其消息传输不经过加密转换因此存在严重的安全隐患: 摘要认证(digest authenticat ...

  6. asp.net权限认证:摘要认证(digest authentication)

    摘要认证简单介绍 摘要认证是对基本认证的改进,即是用摘要代替账户密码,从而防止明文传输中账户密码的泄露 之前对摘要认证也不是很熟悉,还得感谢圆中的 parry 贡献的博文:ASP.NET Web AP ...

  7. Https单向认证和双向认证介绍

    一.Http HyperText Transfer Protocol,超文本传输协议,是互联网上使用最广泛的一种协议,所有WWW文件必须遵循的标准.HTTP协议传输的数据都是未加密的,也就是明文的,因 ...

  8. 干货 | 图解 https 单向认证和双向认证!

    一.Http HyperText Transfer Protocol,超文本传输协议,是互联网上使用最广泛的一种协议,所有WWW文件必须遵循的标准.HTTP协议传输的数据都是未加密的,也就是明文的,因 ...

  9. 常用的认证机制之session认证和token认证

    一.session认证 1.session认证的过程: 前端输入用户名和密码进行登录操作,后端拿到用户名和密码后,会把md5进行加密,加密之后,拿上加密后的密文到用户表中查找密文是否一致,判断用户是否 ...

  10. Springboot整合shiro基于url身份认证和授权认证

    你还不会shiro吗? 前奏 shiro核心配置文件(rolesFilter可选). 身份认证 多表登录源如何操作? 授权管理 如何解决界面多角色/资源问题 访问效果 权限管理在日常开发中很重要,所以 ...

最新文章

  1. 超棒整理 | Python 关键字知识点大放送
  2. 【IEEE出版-EI检索】第三届IEEE信息与计算机前沿技术国际学术会议
  3. 完美解决Informix的中文乱码问题
  4. 学习NGUI前的准备NGUI的相关信息
  5. 软件工程概论 课堂练习【图书馆系统的类图】
  6. Building JavaScript Games for Phones Tablets and Desktop(3)-创造一个游戏世界
  7. [SPOJ - FTOUR2] Free tour II(点分治 + 背包dp + 启发式合并)
  8. 行上下移动_这要是在我家,我是不会把上下铺这样设计的,看着特别,打扫困难...
  9. 设计模式16_策略模式
  10. wamp 403 禁止访问
  11. 2022计算机Java二级考试四十五套题真题【收藏版】(一周裸考计划)
  12. 整合阿里云视频播放器——Coding在线(十四)
  13. 物联时代,二维码技术在各行业有哪些应用?
  14. 计算机系统处理器好坏怎么看,台式电脑CPU怎么看好坏 CPU天梯图2019年2月最新版...
  15. BigDecimal表示0.1
  16. ngx-datatable
  17. 领域驱动设计——项目分层与项目落地
  18. Slidworks2018基础到实战设计视频教程 产品建模 渲染 钣金设计
  19. 【深度学习】详细的神经网络架构图
  20. 在UE4中完美导入MMD的动作,表情;基本导入镜头,材质---最详细教程

热门文章

  1. FreeCAD 常用技巧
  2. 如何将图片批量合并成PDF?
  3. 英语语法总结_01 五种基本句型
  4. WebService学习教程(一)
  5. 2021防爆电气考试题库
  6. GAMIT 分步进行基线解算流程
  7. win10快捷方式出现白色图标处理方法
  8. 展厅 智能 中控 安卓
  9. 招聘计算机教师面试自我介绍,教师招聘面试的自我介绍
  10. 在线SQL转文本工具