<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 基于角色的 JWT 令牌

          共 9641字,需瀏覽 20分鐘

           ·

          2021-03-25 17:13


          原文:https://bit.ly/3vYljq3
          作者:Rick Strahl
          翻譯:精致碼農(nóng)-王亮
          聲明:我翻譯技術(shù)文章不是逐句翻譯的,而是根據(jù)我自己的理解來(lái)表述的。其中可能會(huì)去除一些本人實(shí)在不知道如何組織但又不影響理解的句子。

          ASP.NET Core 中的認(rèn)證和授權(quán)仍然是配置中最麻煩的組件。似乎幾乎在每一個(gè)應(yīng)用程序上,我都會(huì)遇到一些與 Auth 有關(guān)的問(wèn)題。四個(gè)版本帶來(lái)了三種不同的身份驗(yàn)證實(shí)現(xiàn),功能的更新也留下了一大波過(guò)時(shí)的信息。今天,我看著 Web API 基于角色 JWT 授權(quán)認(rèn)證的過(guò)時(shí)信息,陷入了一個(gè)土撥鼠日(譯注:形容不斷重復(fù)的日子)的循環(huán)中。

          目前在 ASP.NET Core 中的 JWT 令牌(Token)配置實(shí)際上非常好用,只要你把正確的配置咒語(yǔ)串起來(lái)。Auth 配置的部分問(wèn)題是,大多數(shù)配置只需按固定的“儀式”進(jìn)行操作。例如,設(shè)置IssuerAudience我們似乎完全不需要關(guān)心它們是什么,但它們是 JWT 令牌要求的一部分,確實(shí)需要配置。幸運(yùn)的是,這些設(shè)置中只有少數(shù)幾個(gè)是真正需要的,大部分都是模板。

          在這篇文章中,我具體講一下:

          • ASP.NET Core Web API 的認(rèn)證

          • JWT 令牌的使用

          • 基于角色授權(quán)

          • 只使用底層功能--不使用 ASP.NET Core Identity

          配置


          認(rèn)證(Authentication)和授權(quán)(Authorization)在 ASP.NET Core 中作為中間件提供,你必須在ConfigureServices()中配置它們,并在Configure()中連接中間件。

          配置 JWT 認(rèn)證和授權(quán)

          第一步是在Startup文件中的ConfigureServices()中配置認(rèn)證(Authentication)。在這里添加 JWT 令牌配置,并將所需組件添加到 ASP.NET Core 的處理管道中:

          // in ConfigureServices()

          // config shown for reference values
          config.JwtToken.Issuer = "https://mysite.com";
          config.JwtToken.Audience = "https://mysite.com";
          config.JwtToken.SigningKey = "12345@4321"; // some long id

          // Configure Authentication
          services.AddAuthentication( auth=>
          {
          auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
          auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
          })
          .AddJwtBearer(options =>
          {
          options.SaveToken = true;
          options.TokenValidationParameters = new TokenValidationParameters
          {
          ValidateIssuer = true,
          ValidIssuer = config.JwtToken.Issuer,
          ValidateAudience = true,
          ValidAudience = config.JwtToken.Audience,
          ValidateIssuerSigningKey = true,
          IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.JwtToken.SigningKey))
          };
          }

          JWT 認(rèn)證有一堆的設(shè)置,其中大部分是足夠神秘的,所以我?guī)缀踔皇菍⑺鼈儚?fù)制和粘貼。我只想說(shuō),這些設(shè)置大多是關(guān)于設(shè)置協(xié)議和令牌包裝器(Wrapper)的。通常情況下,我將這些值存儲(chǔ)在我的應(yīng)用程序的配置中,這樣它就會(huì)通過(guò) .NET 配置 Provider 提取進(jìn)來(lái),而上面的config就是那個(gè)特定的配置實(shí)例。

          在這個(gè)全局配置中沒(méi)有什么是針對(duì)角色的。所有基于角色的相關(guān)配置都發(fā)生在后面的認(rèn)證(Authenticate)端點(diǎn)中創(chuàng)建令牌的時(shí)候。

          令牌和哈希如何工作

          在進(jìn)入這里之前,我們先來(lái)回顧一下基于令牌的身份驗(yàn)證是如何工作的,以及這些設(shè)置值是如何融入這個(gè)方案的。

          上面的設(shè)置值配置了令牌的常用值和用于簽署令牌的密鑰。它們提供身份識(shí)別標(biāo)記,以確保生成的令牌是唯一的。我認(rèn)為這些值是一個(gè)基本的令牌包裝,通常在你驗(yàn)證用戶(hù)后,當(dāng)你創(chuàng)建令牌并將令牌作為 Web 請(qǐng)求的一部分提供給用戶(hù)之時(shí),你將向令牌添加你的自定義、應(yīng)用特定的 Claim,。

          IssuerSigningKey是這個(gè)配置中最重要的部分,它用于將最終的令牌與包裝器以及任何添加的聲明進(jìn)行哈希(Hash)。該哈希值用于驗(yàn)證令牌的真實(shí)性。請(qǐng)注意,雖然生成的令牌被編碼為 Base64,但它本身并不安全,即使在客戶(hù)端,內(nèi)容也可以被解碼。也就是說(shuō),你可以將任何 JWT 令牌粘貼到 JWT.io 這個(gè)網(wǎng)站中,對(duì)令牌的內(nèi)容進(jìn)行解碼。

          哈希確保了令牌不能被改變。當(dāng)令牌與請(qǐng)求一起發(fā)送時(shí),它將由 ASP.NET Core 的 JWToken 中間件進(jìn)行驗(yàn)證,它首先根據(jù)令牌數(shù)據(jù)驗(yàn)證哈希值,然后根據(jù)包含的授權(quán)信息進(jìn)行認(rèn)證/授權(quán)。如果客戶(hù)端或其他實(shí)體以任何方式更改了令牌,則哈希值將無(wú)法驗(yàn)證通過(guò),會(huì)被直接拒絕。之后在中間件管道的授權(quán)部分進(jìn)行用戶(hù)名和角色等的匹配。

          添加 Auth 中間件

          接下來(lái)我們需要在Startup文件的Configure中使用app.UseAuthentication()app.UseAuthorization()添加實(shí)際的中間件:

          // in Startup.Configure()
          app.UseHttpsRedirection();
          app.UseRouting();

          // *** These are the important ones - note order matters ***
          app.UseAuthentication();
          app.UseAuthorization();

          app.UseStatusCodePages();
          //app.UseDefaultFiles(); // so index.html is not required
          //app.UseStaticFiles();

          app.UseEndpoints(endpoints =>
          {
          endpoints.MapControllers();
          });

          請(qǐng)注意,順序?qū)τ谡J(rèn)證(Authentication)和授權(quán)(Authorization)很重要。這兩個(gè)需要在 Routing 之后但在任何 HTTP 輸出中間件之前添加,最重要的是在app.UseEndpoints()之前。

          使用 Web API 端點(diǎn)認(rèn)證用戶(hù)


          接下來(lái),我們需要在應(yīng)用程序中通過(guò)詢(xún)問(wèn)憑證來(lái)驗(yàn)證用戶(hù),然后生成一個(gè)令牌并將其返回給 API 客戶(hù)端。

          這很可能發(fā)生在 Controller 的 Action 方法或中間件端點(diǎn)處理程序中。下面是使用 Controller 的 Action 方法示例:

          [AllowAnonymous]
          [HttpPost]
          [Route("authenticate")]
          public object Authenticate(AuthenticateRequestModel loginUser)
          {
          // My application logic to validate the user
          // returns a user entity with Roles collection
          var bus = new AccountBusiness();
          var user = bus.AuthenticateUser(loginUser.Username, loginUser.Password);
          if (user == null)
          throw new ApiException("Invalid Login Credentials: " + bus.ErrorMessage, 401);

          var claims = new List<Claim>();
          claims.Add(new Claim("Username",loginUser.Username));
          claims.Add(new Claim("DisplayName",loginUser.Name));

          // Add roles as multiple claims
          foreach(var role in user.Roles)
          {
          claims.Add(new Claim(ClaimTypes.Role, role.Name));
          }
          // Optionally add other app specific claims as needed
          claims.Add(new Claim("UserState", UserState.ToString()));

          // create a new token with token helper and add our claim
          // from `Westwind.AspNetCore` NuGet Package
          var token = JwtHelper.GetJwtToken(
          loginUser.Username,
          Configuration.JwtToken.SigningKey,
          Configuration.JwtToken.Issuer,
          Configuration.JwtToken.Audience,
          TimeSpan.FromMinutes(Configuration.JwtToken.TokenTimeoutMinutes),
          claims.ToArray());

          return new
          {
          token = JwtHelper.GetJwtTokenString(token),
          expires = token.ValidTo
          };
          }

          我正在使用一個(gè)JwtHelper類(lèi)來(lái)實(shí)際生成一個(gè)令牌,這樣我就不必在每個(gè)應(yīng)用中記住JwtHelper類(lèi)實(shí)現(xiàn)的這個(gè)重復(fù)的“儀式”。這段代碼創(chuàng)建了令牌,并從中提取了一個(gè)字符串,準(zhǔn)備作為承載令牌值返回。下面是這個(gè)類(lèi)的完整代碼:

          public class JwtHelper
          {

          /// <summary>
          /// Returns a Jwt Token from basic input parameters
          /// </summary>
          public static JwtSecurityToken GetJwtToken(
          string username,
          string uniqueKey,
          string issuer,
          string audience,
          TimeSpan expiration,
          Claim[] additionalClaims = null)
          {
          var claims = new[]
          {
          new Claim(JwtRegisteredClaimNames.Sub,username),
          new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
          };

          if (additionalClaims is object)
          {
          var claimList = new List<Claim>(claims);
          claimList.AddRange(additionalClaims);
          claims = claimList.ToArray();
          }

          var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(uniqueKey));
          var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

          return new JwtSecurityToken(
          issuer: issuer,
          audience: audience,
          expires: DateTime.UtcNow.Add(expiration),
          claims: claims,
          signingCredentials: creds
          );
          }

          /// <summary>
          /// Returns a token string from base claims
          /// </summary>
          public static string GetJwtTokenString(
          string username,
          string uniqueKey,
          string issuer,
          string audience,
          TimeSpan expiration,
          Claim[] additionalClaims = null)
          {
          var token = GetJwtToken(username, uniqueKey, issuer, audience, expiration, additionalClaims);
          return new JwtSecurityTokenHandler().WriteToken(token);
          }

          /// <summary>
          /// Converts an existing Jwt Token to a string
          /// </summary>
          public static string GetJwtTokenString(JwtSecurityToken token)
          {
          return new JwtSecurityTokenHandler().WriteToken(token);
          }

          /// <summary>
          /// Returns an issuer key
          /// </summary>
          public static SymmetricSecurityKey GetSymetricSecurityKey(string issuerKey)
          {
          return new SymmetricSecurityKey(Encoding.UTF8.GetBytes(issuerKey));
          }
          }

          Controller 的Authenticate()代碼首先使用一個(gè)應(yīng)用程序特定的業(yè)務(wù)對(duì)象來(lái)驗(yàn)證用戶(hù),用戶(hù)登錄信息作為 API 調(diào)用的一部分傳入該方法(比如 HTML 網(wǎng)頁(yè)的登錄表單)。如果用戶(hù)是有效的,我就創(chuàng)建新的 Claim,這些 Claim 被打包到令牌中。

          令牌包括用戶(hù)名和角色,這是 ASP.NET Core 授權(quán)工作所需的內(nèi)容。然后,如果有必要,我可以添加一些額外的應(yīng)用程序特定的 Claim,比如上面例子中的DisplayName和自定義UserState對(duì)象。這些聲明會(huì)隨令牌一起,以便在后續(xù)請(qǐng)求提取,而不必再訪問(wèn)后端數(shù)據(jù)庫(kù)檢索它們。

          最后,使用JwtHelperGetJwtToken()生成令牌,并使用GetJwtTokenString()將令牌轉(zhuǎn)換為字符串,這個(gè)字符串將被客戶(hù)端放在請(qǐng)求頭中攜帶到后臺(tái)服務(wù)端。

          請(qǐng)注意,要確??梢阅涿L問(wèn) Authentication 方法。如果 Controller 標(biāo)注了 [Authorize] 特性,則需要在Authenticate()方法上標(biāo)注[AllowAnonymous]特性。

          Claim 和角色

          ASP.NET Core 使用 Claim 進(jìn)行認(rèn)證。Claim 是你可以存儲(chǔ)在令牌中的數(shù)據(jù)片段,這些數(shù)據(jù)與令牌一起攜帶,并可以從令牌中讀取。對(duì)于授權(quán)來(lái)說(shuō),角色可以作為 Claim。

          在 .NET Core 3.1 和 5.x 中,為授權(quán)添加 ASP.NET Core 角色識(shí)別的正確語(yǔ)法是,為每個(gè)角色添加多個(gè) Claim:

          // Add roles as multiple claims
          foreach(var role in user.Roles)
          {
          claims.Add(new Claim(ClaimTypes.Role, role.Name));

          // these also work - and reduce token size
          // claims.Add(new Claim("roles", role.Name));
          // claims.Add(new Claim("role", role.Name));
          }

          訪問(wèn)生成 JWT 令牌的 API

          到這,我已經(jīng)有了一個(gè)用于認(rèn)證的 API 端點(diǎn),我可以從這個(gè)端點(diǎn)上獲取一個(gè)令牌。下面是這個(gè)請(qǐng)求的樣子:

          傳入用戶(hù)名和密碼,則會(huì)返回令牌和到期時(shí)間。你可以在 jwt.io 查看這個(gè)令牌和它生成的內(nèi)容:

          請(qǐng)注意,該令牌很容易被外部工具解碼,與我的應(yīng)用程序完全無(wú)關(guān)。這意味著所包含的令牌數(shù)據(jù)是不安全的。然而,除非數(shù)據(jù)由原始的簽名密鑰簽名,否則無(wú)法更改該令牌中的值并提供給服務(wù)器應(yīng)用程序。這可以防止令牌被篡改。

          一旦生成了令牌并發(fā)送給客戶(hù)端,客戶(hù)端就可以在后續(xù)的請(qǐng)求中使用它來(lái)添加相應(yīng)的授權(quán)請(qǐng)求頭:

          Authorization: Bearer 123456******

          確保 API 的安全


          現(xiàn)在剩下的就是通過(guò)在 Controller 或端點(diǎn)方法上添加[Authorize]特性來(lái)選擇性或限制對(duì) API 的訪問(wèn)。

          我可以使用以下特性之一,或者完全不使用特性(對(duì)于開(kāi)放訪問(wèn)):

          • 普通的[Authorize]讓任何經(jīng)過(guò)認(rèn)證的用戶(hù)進(jìn)入

          • 基于角色的[Authorize(Roles = "Administrator,ReportUser")]訪問(wèn)

          • 允許匿名[AllowAnonymous]訪問(wèn)

          請(qǐng)注意,這些特性可以在 Controller 類(lèi)或 Action 方法上標(biāo)注,而且它們是自上而下分層工作的,所以一個(gè)類(lèi)屬性適用于所有的 Action 方法。這就是 [AllowAnonymous] 的用武之地,它可以覆蓋一兩個(gè)可能需要開(kāi)放訪問(wèn)的請(qǐng)求(如Authenticate()Logout())。

          要為任何登錄用戶(hù)設(shè)置授權(quán),只需使用[Authorize]即可:

          [Authorize]   // just require ANY authentication
          [Route("/api/v1/lookups")]
          public class IdLookupController : BaseApiController

          在這種情況下,你可能需要對(duì)用戶(hù)進(jìn)行一些額外的驗(yàn)證,以確保你有正確的用戶(hù)進(jìn)行特定的操作。

          要設(shè)置特定角色的限制,你可以使用Roles參數(shù):

          [Authorize(Roles = "Administrator")]
          [HttpPost]
          [Route("customers")]
          public async Task<SaveResponseModel> SaveCustomer(IdvCustomer model)

          現(xiàn)在只有那些屬于 Administrator 組的人有訪問(wèn)權(quán)。角色可以是使用逗號(hào)分隔的列表,如使用“Administrator, ReportUser”來(lái)允許多個(gè)角色訪問(wèn)。

          使用令牌訪問(wèn)安全端點(diǎn)

          現(xiàn)在 API 已經(jīng)安全了,我們必須在每個(gè)請(qǐng)求中傳遞 Bearer 令牌來(lái)進(jìn)行驗(yàn)證。它看起來(lái)像這樣:

          瞧,我現(xiàn)在可以訪問(wèn)管理員組保護(hù)的 POST 操作了。

          這就完成了一個(gè)閉環(huán)...

          總結(jié)


          在最近的版本中,ASP.NET Core 中的身份驗(yàn)證和授權(quán)已經(jīng)變得簡(jiǎn)單了很多,但是要找到正確的文檔來(lái)設(shè)置 JWT 令牌身份驗(yàn)證的所有相關(guān)信息仍然不易。關(guān)于身份驗(yàn)證的信息很多,很容易在文檔中迷失方向,并最終可能選擇過(guò)時(shí)的信息,因?yàn)樵谡麄€(gè) ASP.NET Core 版本中,身份驗(yàn)證的行為已經(jīng)發(fā)生了重大變化。(基于本文)如果你要查找額外的信息,請(qǐng)確保它是 3.1 及以后的版本。

          在這篇文章中,我已經(jīng)解決了 3.1 和 5.0 版本的問(wèn)題。值得慶幸的是,5.0 沒(méi)有看到對(duì)認(rèn)證/授權(quán) API 的進(jìn)一步破壞性改變。

          通常情況下,我寫(xiě)下這篇文章是為了讓我自己安心,這樣我就能在一個(gè)地方得到所有的信息。希望你們中的一些人也會(huì)覺(jué)得這很有用。


          往期精彩回顧




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

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

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

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

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

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

          用abp vNext快速開(kāi)發(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ù)量下的多表查詢(xún)優(yōu)化

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

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


          瀏覽 44
          點(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毛一级a看免费网站 |