如何在 ASP.NET Core 3 使用 Token-based 身份驗證與授權
阿新 • • 發佈:2021-11-09
先建立所需要的web API
1、在依賴項中安裝所需要的:
Microsoft.AspNetCore.Authentication.JwtBearer
2、建立一個資料夾 Helpers 生成類Helpers.cs,然後可以把一下程式碼直接放在類中使用
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Text;
using System.IdentityModel.Tokens.Jwt;
namespace JwtAuthDemo.Helpers
{
public class JwtHelpers
{
private readonly IConfiguration Configuration;
public JwtHelpers(IConfiguration configuration)
{
this.Configuration = configuration;
}
public string GenerateToken(string userName, int expireMinutes = 30)
{
var issuer = Configuration.GetValue<string>("JwtSettings:Issuer");
var signKey = Configuration.GetValue<string>("JwtSettings:SignKey");
// 設定要加入到 JWT Token 中的宣告資訊(Claims)
var claims = new List<Claim>();
// 在 RFC 7519 規格中(Section#4),總共定義了 7 個預設的 Claims,我們應該只用的到兩種!
//claims.Add(new Claim(JwtRegisteredClaimNames.Iss, issuer));
claims.Add(new Claim(JwtRegisteredClaimNames.Sub, userName)); // User.Identity.Name
//claims.Add(new Claim(JwtRegisteredClaimNames.Aud, "The Audience"));
//claims.Add(new Claim(JwtRegisteredClaimNames.Exp, DateTimeOffset.UtcNow.AddMinutes(30).ToUnixTimeSeconds().ToString()));
//claims.Add(new Claim(JwtRegisteredClaimNames.Nbf, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString())); // 必須為數字
//claims.Add(new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString())); // 必須為數字
claims.Add(new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())); // JWT ID
// 網路上常看到的這個 NameId 設定是多餘的
//claims.Add(new Claim(JwtRegisteredClaimNames.NameId, userName));
// 這個 Claim 也以直接被 JwtRegisteredClaimNames.Sub 取代,所以也是多餘的
//claims.Add(new Claim(ClaimTypes.Name, userName));
// 你可以自行擴充 "roles" 加入登入者該有的角色
claims.Add(new Claim("roles", "Admin"));
claims.Add(new Claim("roles", "Users"));
var userClaimsIdentity = new ClaimsIdentity(claims);
// 建立一組對稱式加密的金鑰,主要用於 JWT 簽章之用
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(signKey));
// HmacSha256 有要求必須要大於 128 bits,所以 key 不能太短,至少要 16 字元以上
// https://stackoverflow.com/questions/47279947/idx10603-the-algorithm-hs256-requires-the-securitykey-keysize-to-be-greater
var signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature);
// 建立 SecurityTokenDescriptor
var tokenDescriptor = new SecurityTokenDescriptor
{
Issuer = issuer,
//Audience = issuer, // 由於你的 API 受眾通常沒有區分特別物件,因此通常不太需要設定,也不太需要驗證
//NotBefore = DateTime.Now, // 預設值就是 DateTime.Now
//IssuedAt = DateTime.Now, // 預設值就是 DateTime.Now
Subject = userClaimsIdentity,
Expires = DateTime.Now.AddMinutes(expireMinutes),
SigningCredentials = signingCredentials
};
// 產出所需要的 JWT securityToken 物件,並取得序列化後的 Token 結果(字串格式)
var tokenHandler = new JwtSecurityTokenHandler();
var securityToken = tokenHandler.CreateToken(tokenDescriptor);
var serializeToken = tokenHandler.WriteToken(securityToken);
return serializeToken;
}
}
}
3、在appsettings.json裡設定
{ "JwtSettings": { "Issuer": "JwtAuthDemo", "SignKey": "1Zl4h9703IzROikK3@uK&&OEb" }, ... }
4、在Startup.cs中設定,在public void ConfigureServices(IServiceCollection services)的方法裡
services.AddSingleton<JwtHelpers>();
5、建立控制,為了方便示範,原始碼如下:
usingSystem.Linq; using JwtAuthDemo.Helpers; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace JwtAuthDemo.Controllers { [Authorize] [ApiController] public class TokenController : ControllerBase { private readonly JwtHelpers jwt; publicTokenController(JwtHelpers jwt) { this.jwt = jwt; } [AllowAnonymous] [HttpPost("~/signin")] public ActionResult<string> SignIn(LoginViewModel login) { if (ValidateUser(login)) { return jwt.GenerateToken(login.Username); } else { return BadRequest(); } } private bool ValidateUser(LoginViewModel login) { return true; // TODO } [HttpGet("~/claims")] public IActionResult GetClaims() { return Ok(User.Claims.Select(p => new { p.Type, p.Value })); } [HttpGet("~/username")] public IActionResult GetUserName() { return Ok(User.Identity.Name); } [HttpGet("~/jwtid")] public IActionResult GetUniqueId() { var jti = User.Claims.FirstOrDefault(p => p.Type == "jti"); return Ok(jti.Value); } } public class LoginViewModel { public string Username { get; set; } public string Password { get; set; } } }
6、驗證合法的JWT Token
還是在Startup.cs裡插入到services.AddSwaggerGen裡
services .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { // 當驗證失敗時,回應標頭會包含 WWW-Authenticate 標頭,這裡會顯示失敗的詳細錯誤原因 options.IncludeErrorDetails = true; // 預設值為 true,有時會特別關閉 options.TokenValidationParameters = new TokenValidationParameters { // 透過這項宣告,就可以從 "sub" 取值並設定給 User.Identity.Name NameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", // 透過這項宣告,就可以從 "roles" 取值,並可讓 [Authorize] 判斷角色 RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role", // 一般我們都會驗證 Issuer ValidateIssuer = true, ValidIssuer = Configuration.GetValue<string>("JwtSettings:Issuer"), // 通常不太需要驗證 Audience ValidateAudience = false, //ValidAudience = "JwtAuthDemo", // 不驗證就不需要填寫 // 一般我們都會驗證 Token 的有效期間 ValidateLifetime = true, // 如果 Token 中包含 key 才需要驗證,一般都只有簽章而已 ValidateIssuerSigningKey = false, // "1234567890123456" 應該從 IConfiguration 取得 IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration.GetValue<string>("JwtSettings:SignKey"))) }; });
7、
app.UseAuthentication();
務必把程式碼定在 app.UseAuthorization();之前!(先驗證,再授權)
8、限制特定Api只能通過JWT驗證的HTTP要求存取
在ASP.NET Core[Authorize] 如下:
[Authorize] [HttpGet("~/claims")] public IActionResult GetClaims() { return Ok(User.Claims.Select(p => new { p.Type, p.Value })); } [Authorize] [HttpGet("~/username")] public IActionResult GetUserName() { return Ok(User.Identity.Name); }
如還有不懂 詳細:https://blog.miniasp.com/post/2019/12/16/How-to-use-JWT-token-based-auth-in-aspnet-core-31