zoukankan      html  css  js  c++  java
  • HTTP Basic认证

    一、概述

    1、理解Http的无状态特性

    HTTP是一个无状态的协议,WEB服务器在处理所有传入HTTP请求时,根本就不知道某个请求是否是一个用户的第一次请求与后续请求,或者是另一个用户的请求。 WEB服务器每次在处理请求时,都会按照用户所访问的资源所对应的处理代码,从头到尾执行一遍,然后输出响应内容,WEB服务器根本不会记住已处理了哪些用户的请求,因此,我们通常说HTTP协议是无状态的。

    2、为什么需要认证

    虽然HTTP协议与WEB服务器是无状态,但我们的业务需求却要求有状态,典型的就是用户登录, 在这种业务需求中,要求WEB服务器端能区分某个请求是不是一个已登录用户发起的,或者当前请求是哪个用户发出的。 在开发WEB应用程序时,我们通常会使用Cookie来保存一些简单的数据供服务端维持必要的状态。总的来说,加入认证的根本原因就是确保请求的合法性以及资源的安全性,如下图:

    二、HTTP Basic认证

    http认证根据凭证协议的不同,划分为不同的方式。常用的方式有:

    • HTTP基本认证
    • HTTP摘要认证
    • HTTP Bearer认证

    本篇文章介绍HTTP基本认证。

    1、原理解析

    下面通过图详细的了解下HTTP Basic认证过程:

    WWW-Authenticate格式如下:WWW-Authenticate: <type> realm=<realm> 其中:

    • WWW-Authenticate 定义了使用何种验证方式去获取对资源的连接,即告诉客户端需要提供凭证才能获取资源。
    • <type>是认证方案,常见的有Basic 、Bearer、 Digest等。
    • Realm指资源的描述。

    上图过程2认证失败,返回401的fiddler抓包情况如下:

    浏览器根据WWW-Authenticate响应头会弹出一个登录验证的对话框,要求客户端提供用户名和密码进行认证。如下图:

    上图步骤3,浏览器将输入的用户名密码用Base64进行编码后,采用非加密的明文方式传送给服务器。格式如下:Authorization: <type> <credentials>。抓包结果如下: 

    服务端认证凭证成功后,返回200。

    2、优缺点

    HTTP Basic认证的缺点:
    1.用户名和密码明文(Base64)传输,容易泄露用户信息,尽量配合HTTPS来保证信息传输的安全。
    2.容易遭到重放攻击

    三、HTTP Baice认证示例

    1、新建netcore mvc项目

    2、新建User类

    记录用户信息

    namespace HttpBasicAuthentication.Models
    {
        public class User
        {
            public string UserName { get; set; }
            public string Password { get; set; }
        }
    }

    3、新建UserAuthenticate.cs

    添加具体的认证逻辑

    using HttpBasicAuthentication.Models;
    
    namespace HttpBasicAuthentication.Services
    {
        /// <summary>
        /// 认证逻辑
        /// </summary>
        public class UserAuthenticate
        {
            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;
            }
        }
    }

    4、新建BasicDefaults.cs

    指定默认认证方案

    namespace HttpBasicAuthentication.Models
    {
        /// <summary>
        /// 指定认证方案和默认的realme
        /// </summary>
        public class BasicDefaults
        {
            public const string AuthenticationScheme = "Basic";
            public const string AuthenticationRealm = "天气资源";
        }
    }

    5、新建BasicOptions.cs

    封装Basic认证的Options,包括Realm和事件

    using Microsoft.AspNetCore.Authentication;
    using Microsoft.AspNetCore.Http;
    using System;
    using System.Threading.Tasks;
    
    namespace HttpBasicAuthentication.Middleware
    {
        public class BasicOptions : AuthenticationSchemeOptions
        {
            /// <summary>
            /// realme
            /// </summary>
            public string WeatherForecast { get; set; }
            /// <summary>
            /// 事件
            /// </summary>
            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<AuthenticationSchemeOptions>
        {
            public ValidateCredentialsContext(HttpContext context, AuthenticationScheme scheme, AuthenticationSchemeOptions 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;
        }
    }

    6、新建BasicHandler.cs

    封装服务端质询、验证逻辑

    using HttpBasicAuthentication.Models;
    using Microsoft.AspNetCore.Authentication;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.Logging;
    using Microsoft.Extensions.Options;
    using Microsoft.Net.Http.Headers;
    using System;
    using System.Linq;
    using System.Net;
    using System.Text;
    using System.Text.Encodings.Web;
    using System.Threading.Tasks;
    
    namespace HttpBasicAuthentication.Middleware
    {
        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);
                if (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.WeatherForecast }"";
                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;
            }
        }
    }

    7、新建BasicExtensions.cs

    将接口暴露

    using HttpBasicAuthentication.Models;
    using Microsoft.AspNetCore.Authentication;
    using System;
    
    namespace HttpBasicAuthentication.Middleware
    {
        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);
        }
    }

    8、Startup.cs中配置中间件

    在 ConfigureServices 中配置认证中间件

    public void ConfigureServices(IServiceCollection services)
    {
      services.AddControllersWithViews();
      services.AddAuthentication(BasicDefaults.AuthenticationScheme)
      .AddBasic(options =>
      {
        options.WeatherForecast = BasicDefaults.AuthenticationRealm;
        options.Events = new BasicEvents
        {
          OnValidateCredentials = context =>
          {
            var user = UserAuthenticate.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, IWebHostEnvironment env)
    {
      app.UseAuthentication();
      app.UseAuthorization();
    }

    9、Action加入认证

    在HomeController的action上加入[Authorize]。

    using HttpBasicAuthentication.Models;
    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Logging;
    using System.Diagnostics;
    
    namespace HttpBasicAuthentication.Controllers
    {
        public class HomeController : Controller
        {
            private readonly ILogger<HomeController> _logger;
    
            public HomeController(ILogger<HomeController> logger)
            {
                _logger = logger;
            }
            [Authorize]
            public IActionResult Index()
            {
                return View();
            }
            [Authorize]
            public IActionResult Privacy()
            {
                return View();
            }
            [Authorize]
            [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
            public IActionResult Error()
            {
                return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
            }
        }
    }

    10、最终项目目录及运行效果

    运行项目,效果如下:

    输入用户名和密码(用户名和密码一致即可),点击登录,认证成功,记录用户信息并进行重定向:

    四、源码下载

    源码:https://github.com/qiuxianhu/AuthenticationAndAuthorization

  • 相关阅读:
    (webservice,ajax,jmail)Tip:动态调用webservice,ajaxpro的使用要点以及使用jmail接口使用注意事项
    (virus)Error Cleaner, Privacy Protector, Spyware&Malware Protection的罪恶
    (tips,javascript,office)客户端操作excel文档的注意事项
    (iis,asp.net)Tip:错误"由于 ASP.NET 进程标识对全局程序集缓存没有读权限,因此未能执行请求。错误: 0x80070005 拒绝访问"的解决办法
    (javascript)常用农历(12生肖年,天干地支,节气)
    (Life)质量和服务_由购买联想笔记本想到的
    (enjoyment,basketball)随便说说我们的NBA
    (javascript)再说document.body.scrollTop的使用问题
    IE7下使用htmleditor的问题
    POJ 1077 Eight(BFS + A* + 逆序对剪枝)
  • 原文地址:https://www.cnblogs.com/qtiger/p/14867023.html
Copyright © 2011-2022 走看看