HTTP认证之基本认证——Basic(二)

2023-05-16

导航

  • HTTP认证之基本认证——Basic(一)
  • HTTP认证之基本认证——Basic(二)
  • HTTP认证之摘要认证——Digest(一)
  • HTTP认证之摘要认证——Digest(二)

在HTTP认证之基本认证——Basic(一)中介绍了Basic认证的工作原理和流程,接下来就赶紧通过代码来实践一下,以下教程基于ASP.NET Core WebApi框架。如有兴趣,可查看源码

一、准备工作

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

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);
        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.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]特性,否则前面做的一切都是徒劳+_+

查看源码

转载于:https://www.cnblogs.com/xiaoxiaotank/p/11016023.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

HTTP认证之基本认证——Basic(二) 的相关文章

  • 把自己限制在HTTP1.0有什么用吗?

    我负责构建一些工具来帮助最终用户测试为什么他们的浏览器可能无法与网站配合使用 我被告知它可能不起作用的原因之一是 需要 HTTP1 1 这一行 我浏览了大多数浏览器选项 只浏览了 IE 版本 6 及更高版本 even 9 允许您禁用 HTT
  • Non-Authoritative-Reason 标头字段 [HTTP]

    当我有响应标头时 我很难找出它的含义Non Authoritative Reason HSTS 我搜索了很多 但只是想出了一些关于 HSTS 从 HTTP 重定向到 HTTPS 的解释 有人能帮我吗 顺便说一句 我正在使用 Chrome T
  • ReverseProxy取决于golang中的request.Body

    我想构建一个 http 反向代理 它检查 HTTP 主体 然后将 HTTP 请求发送到它的上游服务器 你怎么能在 Go 中做到这一点 初始尝试 如下 失败 因为 ReverseProxy 复制传入请求 修改它并发送 但正文已被读取 func
  • Golang 优雅地关闭 HTTP 服务器并进行错误处理

    我正在让我的 HTTP 服务器正常关闭 我从帖子中获取了提示here https stackoverflow com questions 39320025 how to stop http listenandserve 并且到目前为止已经像
  • asp.net core http 如果没有内容类型标头,则删除 `FromBody` 忽略

    我在 http 中使用 bodyDELETE要求 我知道目前删除主体是非标准的 但是允许的 使用时出现问题HttpClient它不允许删除请求的正文 我知道我可以使用SendAsync 但我宁愿让我的 API 更加灵活 我希望这个机构是可选
  • 在 Go 中跟踪 HTTP 请求时指定超时

    我知道通过执行以下操作来指定 HTTP 请求超时的常用方法 httpClient http Client Timeout time Duration 5 time Second 但是 我似乎不知道在跟踪 HTTP 请求时如何执行相同的操作
  • 如何使用 http 将 Android 中的文件从移动设备发送到服务器?

    在android中 如何使用http将文件 数据 从移动设备发送到服务器 很简单 您可以使用 Post 请求并将文件作为二进制 字节数组 提交 String url http yourserver File file new File En
  • MPMoviePlayerController 播放 YouTube 视频

    如何在 iPhone 上的 MPMoviePlayerController 中播放 YouTube 视频 同时避免进入全屏模式 这个问题已经在这里提出 MPMoviePlayerController 正在播放 YouTube 视频吗 htt
  • 如何禁用 HTTP 的 HSTS 标头?

    我已将以下内容插入到我网站的 htaccess 中 以便能够访问HSTS预加载列表 https hstspreload appspot com
  • 在 iOS 中,http 204 响应返回空白页面,有办法阻止这种情况吗?

    以前可能有人问过这个问题 但我似乎找不到解决方案 所以如果是这种情况 我深表歉意 我正在开发一个使用express的简单节点应用程序 其中一个帖子路由返回 http 204 并发送它 下面是我的代码 router post id funct
  • .net core 2.0代理请求总是导致http 407(需要代理身份验证)

    我正在尝试通过 net core 2 0 Web 应用程序中的 WebProxy 发出 HTTP 请求 我得到的代码在 net框架中运行良好 所以我知道 相信 这不是环境问题 我也尝试使用两者来发出请求HttpWebRequest and
  • RestSharp RestClient的默认超时值是多少?

    任何人都知道默认超时值休息锐利 https github com restsharp 休息客户端 RestSharp 在底层使用 HttpWebRequest 它有一个默认超时 https msdn microsoft com en us
  • ASP.NET 中 HTTP 缓存相关标头的有效含义

    我正在 ASP NET 2 0 中开发一个 Web 应用程序 其中涉及通过资源处理程序 ashx 提供图像 我刚刚实现了处理缓存标头和条件 GET 请求 这样我就不必为每个请求提供所有图像 但我不确定我是否完全理解浏览器缓存发生了什么 图像
  • Android - API 请求

    我开发了一个应用程序 它也在 iPhone 上 问题出在 api 请求上 我为所有请求设置了超时 有时会出现 30 60 秒的中断 看起来这个应用程序执行了几个请求 然后就中断了 一直超时 大约 45 秒后一切正常 不知道是服务器问题还是安
  • 面向服务的架构 - AMQP 或 HTTP

    一点背景 非常大的整体 Django 应用程序 所有组件都使用相同的数据库 我们需要分离服务 以便我们可以独立升级系统的某些部分而不影响其余部分 我们使用 RabbitMQ 作为 Celery 的代理 现在我们有两个选择 使用 REST 接
  • python 2.7 中的 HTTP 2 请求

    在 python 中向 HTTP 1 和 HTTP 2 发出请求有什么区别吗 我可以像这样在 python 中进行 HTTP 1 x 调用 url http someURL values param1 key param2 key2 dat
  • 诸如用于测试 HTTP 请求的虚拟 REST 服务器之类的东西? [关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我一直在四处寻找 但找不到任何这样的网站 我想知道是否有一些虚拟服务器可以响应测试 GET 请求并返回
  • $http.get() 与 JSON 数据

    我正在编写一个服务器应用程序 并希望客户端使用正文中的数据来参数化我的 GET 方法 如下所示 http v GET http localhost 3000 url text 123 foo bar GET url HTTP 1 1 Acc
  • Django HTTPS 和 HTTP 会话

    我使用 Django 1 1 1 和 ssl 重定向中间件 通过 HTTPS 创建的会话数据 身份验证等 在站点的 HTTP 部分中不可用 无需将整个站点设置为 HTTPS 即可使其可用的最佳方法是什么 这是设计使然 您无法轻易更改 当通过
  • 从 PCAP 嗅探重建数据

    我试图通过 libpcap 嗅探 HTTP 数据 并在处理 TCP 有效负载后获取所有 http 内容 标头 有效负载 根据我的讨论编写 http 嗅探器 或任何其他应用程序级嗅探器 https stackoverflow com ques

随机推荐

  • 网络机顶盒固件提取、编辑和打包

    提取的话 xff0c 这边有一篇文章可能有用https www znds com tv 649509 1 1 html 首先下载下载来的固件一般是img格式的 xff0c 可以到hdpfans com这个连接下载一个androidTool的
  • 从微软官网下载VisualStudio离线包

    首先要下载VS安装器 xff0c 有社区版 专业版 企业版 xff0c 此处以社区版为例 不同版本的VS xff0c 只需要将cmd命令中exe名称换掉就行了 仅C VisualStudioSetup exe layout D Layout
  • 基于STM32之UART串口通信协议(一)详解

    一 前言 1 简介 写的这篇博客 xff0c 是为了简单讲解一下UART通信协议 xff0c 以及UART能够实现的一些功能 xff0c 还有有关使用STM32CubeMX来配置芯片的一些操作 xff0c 在后面我会以我使用的STM32F4
  • Java内存分配及值、引用的传递

    关于堆栈的内容网上已经有很多资料了 xff0c 这是我找的加上自己理解的一篇说明文 xff1a 一 内存区域类型 1 寄存器 xff1a 最快的存储区 由编译器根据需求进行分配 我们在程序中无法控制 xff1b 2 栈 xff1a 存放基本
  • NEMA-0183(GPRMC GPGGA)详细解释

    NEMA 0183 GPRMC GPGGA 详细解释 nmea数据如下 xff1a GPGGA 121252 000 3937 3032 N 11611 6046 E 1 05 2 0 45 9 M 5 7 M 0000 77 GPRMC
  • [转]printf 函数实现的深入剖析

    研究printf的实现 xff0c 首先来看看printf函数的函数体 int printf const char fmt int i char buf 256 va list arg 61 va list char amp fmt 43
  • http_parser

    最近读了 http parser 的源码 xff0c 记录下 有意思的地方 xff1a 1 协议解析可以不完全解析完 xff0c 但是当前 parser 会记录解析状态 xff0c 这样可以继续解析 2 协议解析首要还是要了解协议本身 xf
  • linux 网络编程 3---(io多路复用,tcp并发)

    1 xff0c io模型 xff1a 阻塞io 非阻塞io io多路复用 xff0c 信号驱动io 阻塞Io与非阻塞io的转换 xff0c 可用fcntl 函数 include lt unistd h gt include lt fcntl
  • 计算机网络socket实验报告,计算机网络socket编程实验报告

    计算机网络socket编程实验报告 由会员分享 xff0c 可在线阅读 xff0c 更多相关 计算机网络socket编程实验报告 4页珍藏版 请在人人文库网上搜索 1 实课程名称实验项目名称实验时间 日期及节次 专业年级姓名验计算机科学与技
  • linux 内核重定位,linux内核netfilter实现url重定向

    include include 34 url redirect h 34 struct sk buff tcp newpack u32 saddr u32 daddr u16 sport u16 dport u32 seq u32 ack
  • can/socket can

    1 概念 参考 xff1a Linux CAN编程详解 can引脚 xff1a cn2 15 xff1a CAN1 H 19 CAN1 L 根据每组报文开头的 11 位标识符 扩展帧为29位标识符 CAN 2 0A 规范 解释数据的含义来决
  • C#不能在匿名方法、lambda表达式、查询表达式或本地函数中使用ref、Out或in参数

    报错信息 Error CS1628 C 不能在匿名方法 lambda表达式 查询表达式或本地函数中使用ref Out或in参数 Error CS1628 Cannot use ref out or in parameter xx insid
  • STL 智能指针

    转自 xff1a https blog csdn net k346k346 article details 81478223 STL一共给我们提供了四种智能指针 xff1a auto ptr unique ptr shared ptr和we
  • 【转】目前最常见的”无线通信(数据)传输技术“有哪些?

    近年来 xff0c 随着电子技术 计算机技术的发展 xff0c 无线通信技术蓬勃发展 xff0c 出现了各种标准的无线数据传输标准 xff0c 它们各有其优缺点和不同的应用场合 xff0c 本文将目前应用的 无线通信种类进行了分析对比 xf
  • 认证 (authentication) 和授权 (authorization) 的区别

    以前一直分不清 authentication 和 authorization xff0c 其实很简单 xff0c 举个例子来说 xff1a 你要登机 xff0c 你需要出示你的 passport 和 ticket xff0c passpor
  • stm32f407VE+enc28j60+lwip2.0.2

    407自带以太网mac模块 xff0c 一般外挂一个PHY芯片就可以实现以太网物理层 xff1b 以下是stm32f407VE 43 enc28j60 43 lwip2 0 2实现最基本的以太网通信功能 1 新建工程 xff0c 此处省略1
  • 关于步进电机的半流设置、衰减设置

    连接 xff1a https zhidao baidu com question 1668040896278315667 html 电流是电机运行的载体 xff0c 但电压的高低也直接影响到电流 xff0c 很多人使用步进电机存在误区 xf
  • 三十二、http与www服务介绍

    一 用户访问百度 xff08 www baidu com xff09 用户访问在url中输入地址后 xff0c 首先会访问本地的缓存和hosts文件 xff0c 如果没有 xff0c 会访问本地DNS xff0c 在就是根域和顶级域名等 x
  • UNIX网络编程卷1 - >环境搭建(ubuntu16.04)

    学习unp网络编程 xff0c 树上的例子均存在 include unp h xff0c 故需要对环境进行配置 1 到资源页下载 www unpbook com 2 解压并将unpv13e移动到相应的文件夹下 因为我是在windows电脑装
  • HTTP认证之基本认证——Basic(二)

    导航 HTTP认证之基本认证 Basic xff08 一 xff09 HTTP认证之基本认证 Basic xff08 二 xff09 HTTP认证之摘要认证 Digest xff08 一 xff09 HTTP认证之摘要认证 Digest x