<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          深入理解 ASP.NET Core: 認(rèn)證

          共 12454字,需瀏覽 25分鐘

           ·

          2020-11-22 21:16

          ASP.NET Core 認(rèn)證

          通常在應(yīng)用程序中,安全分為前后兩個(gè)步驟:驗(yàn)證和授權(quán)。驗(yàn)證負(fù)責(zé)檢查當(dāng)前請(qǐng)求者的身份,而授權(quán)則根據(jù)上一步得到的身份決定當(dāng)前請(qǐng)求者是否能夠訪問(wèn)期望的資源。

          既然安全從驗(yàn)證開始,我們也就從驗(yàn)證開始介紹安全。

          驗(yàn)證的核心概念

          我們先從比較簡(jiǎn)單的場(chǎng)景開始考慮,例如在 Web API 開發(fā)中,需要驗(yàn)證請(qǐng)求方是否提供了安全令牌,安全令牌是否有效。如果無(wú)效,那么 API 端應(yīng)該拒絕提供服務(wù)。在命名空間 Microsoft.AspNetCore.Authentication 下,定義關(guān)于驗(yàn)證的核心接口。對(duì)應(yīng)的程序集是 Microsoft.AspNetCore.Authentication.Abstractions.dll。

          驗(yàn)證接口 IAuthenticationHandler

          在 ASP.NET 下,驗(yàn)證中包含 3 個(gè)基本操作:

          Authenticate 驗(yàn)證

          驗(yàn)證操作負(fù)責(zé)基于當(dāng)前請(qǐng)求的上下文,使用來(lái)自請(qǐng)求中的信息,例如請(qǐng)求頭、Cookie 等等來(lái)構(gòu)造用戶標(biāo)識(shí)。構(gòu)建的結(jié)果是一個(gè) AuthenticateResult 對(duì)象,它指示了驗(yàn)證是否成功,如果成功的話,用戶標(biāo)識(shí)將可以在驗(yàn)證票據(jù)中找到。

          常見(jiàn)的驗(yàn)證包括:

          • 基于 Cookie 的驗(yàn)證,從請(qǐng)求的 Cookie 中驗(yàn)證用戶

          • 基于 JWT Bearer 的驗(yàn)證,從請(qǐng)求頭中提取 JWT 令牌進(jìn)行驗(yàn)證

          Challenge 質(zhì)詢

          在授權(quán)管理階段,如果用戶沒(méi)有得到驗(yàn)證,但所期望訪問(wèn)的資源要求必須得到驗(yàn)證的時(shí)候,授權(quán)服務(wù)會(huì)發(fā)出質(zhì)詢。例如,當(dāng)匿名用戶訪問(wèn)受限資源的時(shí)候,或者當(dāng)用戶點(diǎn)擊登錄鏈接的時(shí)候。授權(quán)服務(wù)會(huì)通過(guò)質(zhì)詢來(lái)相應(yīng)用戶。

          例如

          • 基于 Cookie 的驗(yàn)證會(huì)將用戶重定向到登錄頁(yè)面

          • 基于 JWT 的驗(yàn)證會(huì)返回一個(gè)帶有 www-authenticate: bearer 響應(yīng)頭的 401 響應(yīng)來(lái)提醒客戶端需要提供訪問(wèn)憑據(jù)

          質(zhì)詢操作應(yīng)該讓用戶知道應(yīng)該使用何種驗(yàn)證機(jī)制來(lái)訪問(wèn)請(qǐng)求的資源。

          Forbid 拒絕

          在授權(quán)管理階段,如果用戶已經(jīng)通過(guò)了驗(yàn)證,但是對(duì)于其訪問(wèn)的資源并沒(méi)有得到許可,此時(shí)會(huì)使用拒絕操作。

          例如:

          • Cookie 驗(yàn)證模式下,已經(jīng)登錄但是沒(méi)有訪問(wèn)權(quán)限的用戶,被重定向到一個(gè)提示無(wú)權(quán)訪問(wèn)的頁(yè)面

          • JWT 驗(yàn)證模式下,返回 403

          • 在自定義驗(yàn)證模式下,將沒(méi)有權(quán)限的用戶重定向到申請(qǐng)資源的頁(yè)面

          拒絕訪問(wèn)處理應(yīng)該讓用戶知道:

          • 它已經(jīng)通過(guò)了驗(yàn)證

          • 但是沒(méi)有權(quán)限訪問(wèn)請(qǐng)求的資源

          在這個(gè)場(chǎng)景下,可以看到,驗(yàn)證需要提供的基本功能就包括了驗(yàn)證和驗(yàn)證失敗后的拒絕服務(wù)兩個(gè)操作。在 ASP.NET Core 中,驗(yàn)證被稱為 Authenticate,拒絕被稱為 Forbid。在供消費(fèi)者訪問(wèn)的網(wǎng)站上,如果我們希望在驗(yàn)證失敗后,不是像 API 一樣直接返回一個(gè)錯(cuò)誤頁(yè)面,而是將用戶導(dǎo)航到登錄頁(yè)面,那么,就還需要增加一個(gè)操作,這個(gè)操作的本質(zhì)是希望用戶再次提供安全憑據(jù),在 ASP.NET Core 中,這個(gè)操作被稱為 Challenge。這 3 個(gè)操作結(jié)合在一起,就是驗(yàn)證最基本的要求,以接口形式表示,就是 IAuthenticationHandler 接口,如下所示:

          public interface IAuthenticationHandler
          {
          Task InitializeAsync(AuthenticationScheme scheme, HttpContext context);
          Task AuthenticateAsync();
          Task ChallengeAsync(AuthenticationProperties? properties);
          Task ForbidAsync(AuthenticationProperties? properties);
          }

          驗(yàn)證的結(jié)果是一個(gè) AuthenticateResult 對(duì)象。值得注意的是,它還提供了一個(gè)靜態(tài)方法 NoResult() 用來(lái)返回沒(méi)有得到結(jié)果,靜態(tài)方法 Fail() 生成一個(gè)表示驗(yàn)證異常的結(jié)果,而 Success() 成功則需要提供驗(yàn)證票據(jù)。

          通過(guò)驗(yàn)證之后,會(huì)返回一個(gè)包含了請(qǐng)求者票據(jù)的驗(yàn)證結(jié)果。

          namespace Microsoft.AspNetCore.Authentication
          {
          public class AuthenticateResult
          {
          // ......
          public static AuthenticateResult NoResult()
          {
          return new AuthenticateResult() { None = true };
          }
          public static AuthenticateResult Fail(Exception failure)
          {
          return new AuthenticateResult() { Failure = failure };
          }
          public static AuthenticateResult Success(AuthenticationTicket ticket)
          {
          if (ticket == null)
          {
          throw new ArgumentNullException(nameof(ticket));
          }
          return new AuthenticateResult() { Ticket = ticket, Properties = ticket.Properties };
          }
          public static AuthenticateResult Success(AuthenticationTicket ticket)
          {
          if (ticket == null)
          {
          throw new ArgumentNullException(nameof(ticket));
          }
          return new AuthenticateResult() { Ticket = ticket, Properties = ticket.Properties };
          }
          // ......
          }
          }

          在 GitHub 中查看 AuthenticateResult 源碼

          那么驗(yàn)證的信息來(lái)自哪里呢?除了前面介紹的 3 個(gè)操作之外,還要求一個(gè)初始化的操作 Initialize,通過(guò)這個(gè)方法來(lái)提供當(dāng)前請(qǐng)求的上下文信息。

          在 GitHub 中查看 IAuthenticationHandler 定義

          支持登錄和登出操作的驗(yàn)證接口

          有的時(shí)候,我們還希望提供登出操作,增加登出操作的接口被稱為 IAuthenticationSignOutHandler。

          public interface IAuthenticationSignOutHandler : IAuthenticationHandler
          {
          Task SignOutAsync(AuthenticationProperties? properties);
          }

          在 GitHub 中查看 IAuthenticationSignOutHandler 源碼

          在登出的基礎(chǔ)上,如果還希望提供登錄操作,那么就是 IAuthenticationSignInHandler 接口。

          public interface IAuthenticationSignInHandler : IAuthenticationSignOutHandler
          {
          Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties? properties);
          }

          在 GitHub 中查看 IAuthenticationSignInHandler 源碼

          實(shí)現(xiàn)驗(yàn)證支持的抽象基類 AuthenticationHandler

          直接實(shí)現(xiàn)接口還是比較麻煩的,在命名空間 Microsoft.AspNetCore.Authentication 下,微軟提供了抽象基類 AuthenticationHandler 以方便驗(yàn)證控制器的開發(fā),其它控制器可以從該控制器派生,以取得其提供的服務(wù)。

          namespace Microsoft.AspNetCore.Authentication
          {
          public abstract class AuthenticationHandler<TOptions> : IAuthenticationHandler where TOptions : AuthenticationSchemeOptions, new()
          {
          protected AuthenticationHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
          {
          Logger = logger.CreateLogger(this.GetType().FullName);
          UrlEncoder = encoder;
          Clock = clock;
          OptionsMonitor = options;
          }
          }
          // ......
          }

          通過(guò)類的定義可以看到,它使用了泛型。每個(gè)控制器應(yīng)該有一個(gè)對(duì)應(yīng)該控制器的配置選項(xiàng),通過(guò)泛型來(lái)指定驗(yàn)證處理器所使用的配置類型,在構(gòu)造函數(shù)中,可以看到它被用于獲取對(duì)應(yīng)的配置選項(xiàng)對(duì)象。

          在 GitHub 中查看 AuthenticationHandler 源碼

          通過(guò) InitializeAsync(),驗(yàn)證處理器可以獲得當(dāng)前請(qǐng)求的上下文對(duì)象 HttpContext。

          public async Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)

          最終,作為抽象類的 ,希望派生類來(lái)完成這個(gè)驗(yàn)證任務(wù),抽象方法 HandleAuthenticateAsync() 提供了擴(kuò)展點(diǎn)。

          /// 
          /// Allows derived types to handle authentication.
          ///

          /// The .
          protected abstract Task HandleAuthenticateAsync();

          驗(yàn)證的結(jié)果是一個(gè) AuthenticateResult。

          而拒絕服務(wù)則簡(jiǎn)單的多,直接在這個(gè)抽象基類中提供了默認(rèn)實(shí)現(xiàn)。直接返回 HTTP 403。

          protected virtual Task HandleForbiddenAsync(AuthenticationProperties properties)
          {
          Response.StatusCode = 403;
          return Task.CompletedTask;
          }

          剩下的一個(gè)也一樣,提供了默認(rèn)實(shí)現(xiàn)。直接返回 HTTP 401 響應(yīng)。

          protected virtual Task HandleChallengeAsync(AuthenticationProperties properties)
          {
          Response.StatusCode = 401;
          return Task.CompletedTask;
          }

          Jwt 驗(yàn)證處理器是如何實(shí)現(xiàn)的?

          對(duì)于 JWT 來(lái)說(shuō),并不涉及到登入和登出,所以它需要從實(shí)現(xiàn) IAuthenticationHandler 接口的抽象基類 AuthenticationHandler 派生出來(lái)即可。從 AuthenticationHandler 派生出來(lái)的 JwtBearerHandler 實(shí)現(xiàn)基于自己的配置選項(xiàng) JwtBearerOptions。所以該類定義就變得如下所示,而構(gòu)造函數(shù)顯然配合了抽象基類的要求。

          namespace Microsoft.AspNetCore.Authentication.JwtBearer
          {
          public class JwtBearerHandler : AuthenticationHandler<JwtBearerOptions>
          {
          public JwtBearerHandler(
          IOptionsMonitor options,
          ILoggerFactory logger,
          UrlEncoder encoder,
          ISystemClock clock)
          : base(options, logger, encoder, clock)
          { }
          // ......
          }
          }

          在 GitHub 中查看 JwtBearerHandler 源碼

          真正的驗(yàn)證則在 HandleAuthenticateAsync() 中實(shí)現(xiàn)。下面的代碼是不是就很熟悉了,從請(qǐng)求頭中獲取附帶的 JWT 訪問(wèn)令牌,然后驗(yàn)證該令牌的有效性,核心代碼如下所示。

          string authorization = Request.Headers[HeaderNames.Authorization];

          // If no authorization header found, nothing to process further
          if (string.IsNullOrEmpty(authorization))
          {
          return AuthenticateResult.NoResult();
          }

          if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
          {
          token = authorization.Substring("Bearer ".Length).Trim();
          }

          // If no token found, no further work possible
          if (string.IsNullOrEmpty(token))
          {
          return AuthenticateResult.NoResult();
          }

          // ......
          principal = validator.ValidateToken(token, validationParameters, out validatedToken);

          在 GitHub 中查看 JwtBearerHandler 源碼

          注冊(cè) Jwt 驗(yàn)證處理器

          在 ASP.NET Core 中,你可以使用各種驗(yàn)證處理器,并不僅僅只能使用一個(gè),驗(yàn)證控制器需要一個(gè)名稱,它被看作該驗(yàn)證模式 Schema 的名稱。Jwt 驗(yàn)證模式的默認(rèn)名稱就是 "Bearer",通過(guò)字符串常量 JwtBearerDefaults.AuthenticationScheme 定義。

          namespace Microsoft.AspNetCore.Authentication.JwtBearer
          {
          ///
          /// Default values used by bearer authentication.
          ///

          public static class JwtBearerDefaults
          {
          ///
          /// Default value for AuthenticationScheme property in the JwtBearerAuthenticationOptions
          ///

          public const string AuthenticationScheme = "Bearer";
          }
          }

          在 GitHub 中查看 JwtBearerDefaults 源碼

          最終通過(guò) AuthenticationBuilder 的擴(kuò)展方法 AddJwtBearer() 將 Jwt 驗(yàn)證控制器注冊(cè)到依賴注入的容器中。

          public static AuthenticationBuilder AddJwtBearer(this AuthenticationBuilder builder)
          => builder.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, _ => { });

          public static AuthenticationBuilder AddJwtBearer(
          this AuthenticationBuilder builder,
          string authenticationScheme,
          string displayName,
          Action configureOptions)
          {
          builder.Services.TryAddEnumerable(
          ServiceDescriptor.Singleton,
          JwtBearerPostConfigureOptions>());
          return builder.AddScheme(
          authenticationScheme, displayName, configureOptions);
          }

          在 GitHub 中查看 JwtBearerExtensions 擴(kuò)展方法源碼

          驗(yàn)證架構(gòu) Schema

          一種驗(yàn)證處理器,加上對(duì)應(yīng)的驗(yàn)證配置選項(xiàng),我們?cè)贋樗鹨粋€(gè)名字,組合起來(lái)就成為一種驗(yàn)證架構(gòu) Schema。在 ASP.NET Core 中,可以注冊(cè)多種驗(yàn)證架構(gòu)。例如,授權(quán)策略可以使用架構(gòu)的名稱來(lái)指定所使用的驗(yàn)證架構(gòu)來(lái)使用特定的驗(yàn)證方式。在配置驗(yàn)證的時(shí)候,通常設(shè)置默認(rèn)的驗(yàn)證架構(gòu)。當(dāng)沒(méi)有指定驗(yàn)證架構(gòu)的時(shí)候,就會(huì)使用默認(rèn)架構(gòu)進(jìn)行處理。

          還可以

          • 對(duì)于 authenticate, challenge, 以及 forbid 操作使用不同的驗(yàn)證架構(gòu)

          • 使用策略來(lái)組合多種驗(yàn)證架構(gòu)

          注冊(cè)的驗(yàn)證模式,最終變成 AuthenticationScheme,注冊(cè)到依賴注入服務(wù)中。

          public class AuthenticationScheme
          {
          public string Name { get; }
          public string? DisplayName { get; }

          [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
          public Type HandlerType { get; }
          }

          在 GitHub 中查看 AuthenticationScheme 源碼

          使用驗(yàn)證處理器

          IAuthenticationSchemeProvider

          各種驗(yàn)證架構(gòu)被保存到一個(gè) IAuthenticationSchemeProvider 中。

          public interface IAuthenticationSchemeProvider
          {
          Task> GetAllSchemesAsync();
          Task GetSchemeAsync(string name);
          void AddScheme(AuthenticationScheme scheme);
          void RemoveScheme(string name);
          }

          在 GitHub 中查看 IAuthenticationSchemeProvider 源碼

          IAuthenticationHandlerProvider

          最終的使用是通過(guò) IAuthenticationHandlerProvider 來(lái)實(shí)現(xiàn)的,通過(guò)一個(gè)驗(yàn)證模式的字符串名稱,可以取得所對(duì)應(yīng)的驗(yàn)證控制器。

          public interface IAuthenticationHandlerProvider
          {
          Task GetHandlerAsync(HttpContext context, string authenticationScheme);
          }

          在 GitHub 中查看 IAuthenticationHandlerProvider 源碼

          它的默認(rèn)實(shí)現(xiàn)是 AuthenticationHandlerProvider,源碼并不復(fù)雜。

          public class AuthenticationHandlerProvider : IAuthenticationHandlerProvider
          {
          public IAuthenticationSchemeProvider Schemes { get; }
          private readonly Dictionary<string, IAuthenticationHandler> _handlerMap
          = new Dictionary<string, IAuthenticationHandler>(StringComparer.Ordinal);

          public AuthenticationHandlerProvider(IAuthenticationSchemeProvider schemes)
          {
          Schemes = schemes;
          }

          public async Task GetHandlerAsync(HttpContext context, string authenticationScheme)
          {
          if (_handlerMap.TryGetValue(authenticationScheme, out var value))
          {
          return value;
          }

          var scheme = await Schemes.GetSchemeAsync(authenticationScheme);
          if (scheme == null)
          {
          return null;
          }
          var handler = (context.RequestServices.GetService(scheme.HandlerType) ??
          ActivatorUtilities.CreateInstance(context.RequestServices, scheme.HandlerType))
          as IAuthenticationHandler;
          if (handler != null)
          {
          await handler.InitializeAsync(scheme, context);
          _handlerMap[authenticationScheme] = handler;
          }
          return handler;
          }
          }

          在 GitHub 中查看 AuthenticationHandlerProvider 源碼

          Authentication 中間件 AuthenticationMiddleware

          驗(yàn)證中間件的處理就沒(méi)有那么復(fù)雜了。

          找到默認(rèn)的驗(yàn)證模式,使用默認(rèn)驗(yàn)證模式的名稱取得對(duì)應(yīng)的驗(yàn)證處理器,如果驗(yàn)證成功的話,把當(dāng)前請(qǐng)求用戶的主體放到當(dāng)前請(qǐng)求上下文的 User 上。

          里面還有一段特別的代碼,用來(lái)找出哪些驗(yàn)證處理器實(shí)現(xiàn)了 IAuthenticationHandlerProvider,并依次調(diào)用它們,看看是否需要提取終止請(qǐng)求處理過(guò)程。

          using System;
          using System.Threading.Tasks;
          using Microsoft.AspNetCore.Http;
          using Microsoft.Extensions.DependencyInjection;

          namespace Microsoft.AspNetCore.Authentication
          {
          public class AuthenticationMiddleware
          {
          private readonly RequestDelegate _next;

          public AuthenticationMiddleware(RequestDelegate next, IAuthenticationSchemeProvider schemes)
          {
          if (next == null)
          {
          throw new ArgumentNullException(nameof(next));
          }
          if (schemes == null)
          {
          throw new ArgumentNullException(nameof(schemes));
          }

          _next = next;
          Schemes = schemes;
          }

          public IAuthenticationSchemeProvider Schemes { get; set; }

          public async Task Invoke(HttpContext context)
          {
          context.Features.Set(new AuthenticationFeature
          {
          OriginalPath = context.Request.Path,
          OriginalPathBase = context.Request.PathBase
          });

          // Give any IAuthenticationRequestHandler schemes a chance to handle the request
          var handlers = context.RequestServices.GetRequiredService();
          foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())
          {
          var handler = await handlers.GetHandlerAsync(context, scheme.Name) as IAuthenticationRequestHandler;
          if (handler != null && await handler.HandleRequestAsync())
          {
          return;
          }
          }

          var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
          if (defaultAuthenticate != null)
          {
          var result = await context.AuthenticateAsync(defaultAuthenticate.Name);
          if (result?.Principal != null)
          {
          context.User = result.Principal;
          }
          }

          await _next(context);
          }
          }
          }

          在 GitHub 中查看 AuthenticationMiddle 源碼

          參考資料

          • https://docs.microsoft.com/en-us/aspnet/core/security/authentication/?view=aspnetcore-5.0


          往期精彩回顧




          【推薦】.NET Core開發(fā)實(shí)戰(zhàn)視頻課程?★★★

          .NET Core實(shí)戰(zhàn)項(xiàng)目之CMS 第一章 入門篇-開篇及總體規(guī)劃

          【.NET Core微服務(wù)實(shí)戰(zhàn)-統(tǒng)一身份認(rèn)證】開篇及目錄索引

          Redis基本使用及百億數(shù)據(jù)量中的使用技巧分享(附視頻地址及觀看指南)

          .NET Core中的一個(gè)接口多種實(shí)現(xiàn)的依賴注入與動(dòng)態(tài)選擇看這篇就夠了

          10個(gè)小技巧助您寫出高性能的ASP.NET Core代碼

          用abp vNext快速開發(fā)Quartz.NET定時(shí)任務(wù)管理界面

          在ASP.NET Core中創(chuàng)建基于Quartz.NET托管服務(wù)輕松實(shí)現(xiàn)作業(yè)調(diào)度

          現(xiàn)身說(shuō)法:實(shí)際業(yè)務(wù)出發(fā)分析百億數(shù)據(jù)量下的多表查詢優(yōu)化

          關(guān)于C#異步編程你應(yīng)該了解的幾點(diǎn)建議

          C#異步編程看這篇就夠了


          瀏覽 70
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  夜精品无码A片一区二区蜜桃 | 日韩淫淫网 | 一级片在线 | 大香蕉伊人综合在线 | 丁香狠狠色婷婷久久 |