<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 使用最簡潔的代碼實現(xiàn)登錄、認證和注銷

          共 9595字,需瀏覽 20分鐘

           ·

          2023-07-28 14:26

          前言


          認證是一個確定請求訪問者真實身份的過程,與認證相關(guān)的還有其他兩個基本操作——登錄和注銷。ASP.NET Core利用AuthenticationMiddleware中間件完成針對請求的認證,并提供了用于登錄、注銷以及"質(zhì)詢"的API,本篇文章利用它們使用最簡單的代碼實現(xiàn)這些功能。


          一、 認證票據(jù)


          要真正理解認證、登錄和注銷這三個核心操作的本質(zhì),就需要對ASP.NET采用的基于"票據(jù)"的認證機制有基本的了解。

          ASP.NET Core應(yīng)用的認證實現(xiàn)在AuthenticationMiddleware的中間件中,該中間件在處理分發(fā)給它的請求時會按照指定的認證方案(Authentication Scheme)從請求中提取能夠驗證用戶真實身份的信息,我們一般將此信息稱為安全令牌(Security Token)。

          ASP.NET Core應(yīng)用下的安全令牌被稱為認證票據(jù)(Authentication Ticket),它采用基于票據(jù)的認證方式。該中間件實現(xiàn)的整個認證流程涉及圖1所示的三種針對認證票據(jù)的操作,即認證票據(jù)的"頒發(fā)"、"檢驗"和"撤銷"。

          我們將這三個操作所涉及的三種角色稱為票據(jù)頒發(fā)者(Ticket Issuer)、驗證者(Authenticator)和撤銷者(Ticket Revoker),在大部分場景下這三種角色由同一個主體來扮演。

          圖1 基于票據(jù)的認證

          頒發(fā)認證票據(jù)的過程就是登錄(Sign In)操作。用戶試圖通過登錄來獲取認證票據(jù)時需要提供可用來證明自身身份的憑證(Credential),最常見的用戶憑證類型是"用戶名 + 密碼"。

          認證方在確定對方真實身份之后,會頒發(fā)一個認證票據(jù),該票據(jù)攜帶著與該用戶有關(guān)的身份、權(quán)限及其他相關(guān)的信息。

          一旦擁有了由認證方頒發(fā)的認證票據(jù),客戶端就可以按照雙方協(xié)商的方式(比如通過Cookie或者報頭)在請求中攜帶該認證票據(jù),并以此票據(jù)聲明的身份執(zhí)行目標(biāo)操作或者訪問目標(biāo)資源。

          認證票據(jù)一般都具有時效性,一旦過期將變得無效。如果希望在過期之前就讓認證票據(jù)無效,這就是注銷(Sign Out)操作。

          ASP.NET的認證系統(tǒng)旨在構(gòu)建一個標(biāo)準(zhǔn)的模型,用來完成針對請求的認證以及與之相關(guān)的登錄和注銷操作。按照慣例,在介紹認證模型的架構(gòu)設(shè)計之前,需要通過一個簡單的實例來演示如何在一個ASP.NET應(yīng)用中實現(xiàn)認證、登錄和注銷的功能。

          二、基于Cookie的認證

          我們會采用ASP.NET提供的基于Cookie的認證方案。該認證方案采用Cookie來攜帶認證票據(jù)。為了使讀者對基于認證的編程模式有深刻的理解,我們演示的這個應(yīng)用將從一個空白的ASP.NET應(yīng)用開始搭建。

          這個應(yīng)該會呈現(xiàn)兩個頁面,認證用戶訪問主頁會呈現(xiàn)一個"歡迎"頁面,匿名請求則會重定向到登錄頁面,我們將這兩個頁面的呈現(xiàn)實現(xiàn)在如下這個IPageRenderer服務(wù)中,PageRenderer類型為該接口的默認實現(xiàn)。

          public interface IPageRenderer
          {
              IResult RenderLoginPage(string? userName = nullstring? password = nullstring? errorMessage = null);
              IResult RenderHomePage(string userName);
          }

          public class PageRenderer : IPageRenderer
          {
              public IResult RenderHomePage(string userName)
              {
                  var html = @$"
          <html>
            <head><title>Index</title></head>
            <body>
              <h3>Welcome {userName}</h3>
              <a href='Account/Logout'>Sign Out</a>
            </body>
          </html>"
          ;
                  return Results.Content(html, "text/html");
              }

              public IResult RenderLoginPage(string? userName, string? password, string? errorMessage)
              {
                  var html = @$"
          <html>
            <head><title>Login</title></head>
            <body>
              <form method='post'>
               <input type='text' name='username' placeholder='User name' value = '{userName}' />
               <input type='password' name='password' placeholder='Password' value = '{password}' />
               <input type='submit' value='Sign In' />
              </form>
              <p style='color:red'>{errorMessage}</p>
            </body>
          </html>"
          ;
                  return Results.Content(html, "text/html");
              }
          }

          我們采用"用戶名+密碼"的認證方式,密鑰驗證實現(xiàn)的如下這個IAccountService接口的Validate方法中。

          在實現(xiàn)的AccountService類型中,我們預(yù)創(chuàng)建了三個密碼為"password"的賬號("foo"、"bar"和"baz")。

          public interface IAccountService
          {
              bool Validate(string userName, string password);
          }

          public class AccountServiceIAccountService
          {
              private readonly Dictionary<stringstring> _accounts  = new(StringComparer.OrdinalIgnoreCase)
                  {
                      { "Foo""password"},
                      { "Bar""password"},
                      { "Baz""password"}
                  };

              public bool Validate(string userName, string password) =>_accounts.TryGetValue(userName, out var pwd) && pwd == password;
          }

          我們即將創(chuàng)建的這個ASP.NET應(yīng)用主要處理四種類型的請求。主頁需要在登錄之后才能訪問,所以針對主頁的匿名請求會被重定向到登錄頁面。

          在登錄頁面輸入正確的用戶名和密碼之后,應(yīng)用會自動重定向到主頁,該頁面會顯示當(dāng)前認證用戶名并提供注銷的鏈接。我們按照如下所示的方式注冊了四個對應(yīng)的終結(jié)點,其中登錄和注銷采用的是約定的路徑"Account/Login"與"Account/Logout"。

          using App;
          using Microsoft.AspNetCore.Authentication;
          using Microsoft.AspNetCore.Authentication.Cookies;
          using System.Security.Claims;
          using System.Security.Principal;

          var builder = WebApplication.CreateBuilder();
          builder.Services
              .AddSingleton<IPageRenderer, PageRenderer>()
              .AddSingleton<IAccountService, AccountService>()
              .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();
          var app = builder.Build();
          app.UseAuthentication();

          app.Map("/", WelcomeAsync);
          app.MapGet("Account/Login", Login);
          app.MapPost("Account/Login", SignInAsync);
          app.Map("Account/Logout", SignOutAsync);
          app.Run();

          Task WelcomeAsync () => throw new NotImplementedException();
          IResult Login(IPageRenderer renderer) => throw new NotImplementedException();
          Task SignInAsync()=> throw new NotImplementedException();
          Task SignOutAsync() => throw new NotImplementedException();

          上面的演示程序調(diào)用UseAuthentication擴展方法注冊了AuthenticationMiddleware中間件,它所依賴服務(wù)是通過調(diào)用AddAuthentication擴展方法進行注冊。

          在調(diào)用該方法時,我們還設(shè)置了默認采用的認證方案,靜態(tài)類型CookieAuthenticationDefaults的AuthenticationScheme屬性返回的就是Cookie認證方案的默認方案名稱。

          我們在上面定義的兩個服務(wù)也在這里進行了注冊。圖2所示就是作為應(yīng)用的主頁在瀏覽器上呈現(xiàn)的效果。

          圖2 應(yīng)用主頁

          三、 強制認證

          演示實例的主頁是通過如下所示的WelcomeAsync方法來呈現(xiàn)的,該方法注入了當(dāng)前HttpContext上下文、代表當(dāng)前用戶的ClaimsPrincipal對象和IPageRenderer對象。我們利用ClaimsPrincipal對象確定用戶是否經(jīng)過人證,認證用戶請求將呈現(xiàn)正常的歡迎頁面,匿名請求直接調(diào)用HttpContext上下文的ChallengeAsync方法進行處理。

          基于Cookie的認證方案會自動將匿名請求重定向到登錄頁面,由于我們指定的登錄和注銷路徑是Cookie的認證方案約定的路徑,所以調(diào)用ChallengeAsync方法時根本不需要指定重定向路徑。

          Task WelcomeAsync(HttpContext context, ClaimsPrincipal user, IPageRenderer renderer)
          {
              if (user?.Identity?.IsAuthenticated ?? false)
              {
                  return renderer.RenderHomePage(user.Identity.Name!).ExecuteAsync(context);
              }

              return  context.ChallengeAsync();
          }

          四、登錄與注銷

          針對登錄頁面所在地址的請求由兩種類型,針對GET請求的Login方法會登錄頁面呈現(xiàn)出來,針對POST請求的SignInAsync方法檢驗輸入的用戶名和密碼,并在驗證成功后實施"登錄"。如下面的代碼片段所示,SignInAsync方法中注入了當(dāng)前HttpContext上下文、代表請求的HttpRequest對象和額外兩個服務(wù)。

          從請求表單將用戶和密碼提取出來后,我們利用IAccountService對象進行驗證。

          在驗證通過的情況下,我們會根據(jù)用戶名創(chuàng)建代表當(dāng)前用戶的ClaimsPrincipal對象,并將它作為參數(shù)調(diào)用HttpContext上下文的SignInAsync擴展方法實施登錄, 該方法最終會自動重定向到初始方法的路徑,也就是我們的主頁。

          IResult Login(IPageRenderer renderer) => renderer.RenderLoginPage();

          Task SignInAsync(HttpContext context, HttpRequest request, IPageRenderer renderer,IAccountService accountService)
          {
              var username = request.Form["username"];
              if (string.IsNullOrEmpty(username))
              {
                  return renderer.RenderLoginPage(nullnull"Please enter user name.").ExecuteAsync(context);
              }

              var password = request.Form["password"];
              if (string.IsNullOrEmpty(password))
              {
                  return renderer.RenderLoginPage(username, null"Please enter user password.").ExecuteAsync(context);
              }

              if (!accountService.Validate(username, password))
              {
                  return renderer.RenderLoginPage(username, null"Invalid user name or password.").ExecuteAsync(context);
              }

              var identity = new GenericIdentity(name: username, type: "PASSWORD");
              var user = new ClaimsPrincipal(identity);
              return context.SignInAsync(user);
          }

          如果用戶名或者密碼沒有提供或者不匹配,登錄頁面會以圖3所示的形式再次呈現(xiàn)出來,并保留輸入的用戶名和錯誤消息。ChallengeAsync方法會將當(dāng)前路徑(主頁路徑“/”,經(jīng)過編碼后為“%2F”)存儲在一個名為ReturnUrl的查詢字符串中,SignInAsync方法正是利用它實現(xiàn)對初始路徑的重定向的。

          既然登錄可以通過調(diào)用當(dāng)前HttpContext上下文的SignInAsync擴展方法來完成,那么注銷操作對應(yīng)的自然就是SignOutAsync擴展方法。

          如下面的代碼片段所示,SignOutAsync擴展方法正是調(diào)用這個方法來注銷當(dāng)前登錄狀態(tài)的。我們在完成注銷之后將應(yīng)用重定向到主頁。

          async Task SignOutAsync(HttpContext context)
          {
              await context.SignOutAsync();
              context.Response.Redirect("/");
          }

          轉(zhuǎn)自:Artech

          鏈接:cnblogs.com/artech/p/inside-asp-net-core-6-39.html







          回復(fù) 【關(guān)閉】學(xué)永久關(guān)閉App開屏廣告
          回復(fù) 【刪除】學(xué)自動檢測那個微信好友刪除、拉黑
          回復(fù) 【手冊】獲取3萬字.NET、C#工程師面試手冊
          回復(fù) 【幫助】獲取100+個常用的C#幫助類庫
          回復(fù) 【加群】加入DotNet學(xué)習(xí)交流群

          瀏覽 750
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  208操逼视频 | 久久久久国产一区二区三区四区 | 成人影视自拍 | 日本欧美国产一级黄色大片 | 久久精品毛片 |