本篇會示範:
建立 JWT 設定檔物件:
在 appsettings.json 設定檔中加入以下設定值:
註:記得替換為自己的金鑰,和網域,避免安全問題
建立 JwtService ,用來產生 JWT Token:
上面的範例幾乎是最簡單的產生 JWT Token 的程式碼了,產生出來的內容只有以下四個:
內容好像很少,不過目前夠用了(可能需要的就是加入 jti ,用以識別 JWT ,透過 jti 來拒絕 JWT 存取),假設需要角色和權限的驗證,筆者也比較傾向於動態確認,不要把角色和權限寫入 Token 中,讓取得 token 的人都可以看光光,具體的做法可以看這篇: ASP.NET Core 6 身分驗證 JWT 登入時自動賦予角色和權限
記得在 Program.cs 中註冊服務
建立最簡單認證用的 Controller ,這個範例沒有實現帳號密碼的檢查,需要各位使用當前裝案的方式替換,然後會將使用者帳號送去給剛剛建立的 JwtService 產生 Jwt Token
登入 API 的呼叫方式如下:
範例回應:
註冊服務:
建立從 JWT 中取得身份識別的範例 API:
登入 API 的呼叫方式如下:
回應範例:
這樣就完成了登入取得 JWT 、呼叫 API 需要 JWT ,並且可以藉由 JWT 確認呼叫者的身份!
- 一個登入 API ,會產生 JWT Token 並回傳
- 有標記 AllowAnonymousAttribute 的 API 可以不需要 Token 即可使用
- 其他 API 都需要加上 JWT Token ,不然會回傳 401 Unauthorized
- 有傳入 JWT Token 的請求都可以藉由 JWT Token 自動取得使用者資訊
產生 JWT Token
安裝套件
先使用 NuGet 安裝 Microsoft.AspNetCore.Authentication.JwtBearer 套件,或是使用 .NET CLI 執行以下指令安裝
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
建立 JWT 設定檔物件:
using System.ComponentModel.DataAnnotations;
public class JwtOptions
{
public const string SectionName = "Jwt";
[Required] public string SignKey { get; set; } = null!;
[Required] public string Issuer { get; set; } = null!;
public int ExpireMinutes { get; set; } = 60 * 24; // 過期時間(分鐘),這裡範例的是一天(24 小時)
}
在 appsettings.json 設定檔中加入以下設定值:
{
"AllowedHosts": "*",
"Jwt": {
"SignKey": "12345678901234567890123456789012",
"Issuer": "http://ruyut.com/",
"ExpireMinutes": 60
}
}
註:記得替換為自己的金鑰,和網域,避免安全問題
建立 JwtService ,用來產生 JWT Token:
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
public class JwtService(IOptions<JwtOptions> options)
{
private readonly JwtOptions _jwtOptions = options.Value;
public string GenerateToken(string username)
{
var claims = new List<Claim>();
claims.Add(new Claim(JwtRegisteredClaimNames.Sub, username));
var userClaimsIdentity = new ClaimsIdentity(claims);
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtOptions.SignKey));
var signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature);
var tokenDescriptor = new SecurityTokenDescriptor
{
Issuer = _jwtOptions.Issuer,
IssuedAt = DateTime.Now,
Subject = userClaimsIdentity,
Expires = DateTime.Now.AddMinutes(_jwtOptions.ExpireMinutes),
SigningCredentials = signingCredentials
};
var tokenHandler = new JwtSecurityTokenHandler
{
SetDefaultTimesOnTokenCreation = false
};
var securityToken = tokenHandler.CreateToken(tokenDescriptor);
var serializeToken = tokenHandler.WriteToken(securityToken);
return serializeToken;
}
}
上面的範例幾乎是最簡單的產生 JWT Token 的程式碼了,產生出來的內容只有以下四個:
- sub: 使用者
- exp: 過期時間
- iat: 產生時間
- iss: 發行者
{
"sub" : "test",
"exp" : 1704125202,
"iat" : 1704121602,
"iss" : "http://ruyut.com/"
}
內容好像很少,不過目前夠用了(可能需要的就是加入 jti ,用以識別 JWT ,透過 jti 來拒絕 JWT 存取),假設需要角色和權限的驗證,筆者也比較傾向於動態確認,不要把角色和權限寫入 Token 中,讓取得 token 的人都可以看光光,具體的做法可以看這篇: ASP.NET Core 6 身分驗證 JWT 登入時自動賦予角色和權限
記得在 Program.cs 中註冊服務
var builder = WebApplication.CreateBuilder(args);
// 讀取設定值
builder.Services.AddOptions<JwtOptions>()
.BindConfiguration(JwtOptions.SectionName)
.ValidateDataAnnotations()
.ValidateOnStart();
builder.Services.AddSingleton<JwtService>();
var app = builder.Build();
建立最簡單認證用的 Controller ,這個範例沒有實現帳號密碼的檢查,需要各位使用當前裝案的方式替換,然後會將使用者帳號送去給剛剛建立的 JwtService 產生 Jwt Token
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("[controller]")]
public class AuthController : ControllerBase
{
private readonly JwtService _jwtService;
public AuthController(JwtService jwtService)
{
_jwtService = jwtService;
}
public record class LoginDto(string UserName, string Password);
public record class TokenDto(string Token);
[AllowAnonymous] // 允許匿名登入
[HttpPost("login")]
public ActionResult<string> Login([FromBody] LoginDto dto)
{
// todo: 驗證帳號密碼
var token = _jwtService.GenerateToken(dto.UserName); // 依照帳號產生 jwt
return Ok(new TokenDto(token));
}
}
登入 API 的呼叫方式如下:
curl -X 'POST' \
'http://localhost:5221/Auth/login' \
-H 'accept: text/plain' \
-H 'Content-Type: application/json' \
-d '{
"userName": "ruyut",
"password": "ruyut"
}'
範例回應:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJydXl1dCIsImV4cCI6MTcwNDEyNTYwNywiaWF0IjoxNzA0MTIyMDA3LCJpc3MiOiJodHRwOi8vcnV5dXQuY29tLyJ9.uiHHFUF12ALpJqZQWZOIWmnvf11srEi6DnkF7fDQ9u4"
}
增加請求需要 Jwt Token 的限制
為了避免 Program.cs 過度臃腫,會將 JWT 相關的拆分,不直接寫在 Program.cs 中,這裡有許多可以探討,不過本篇先建立一個最簡單的 JwtStartup.cs 而不進行過多的拆分:
using System.Security.Claims;
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.IdentityModel.Tokens;
public static class JwtStartup
{
public static IServiceCollection AddJwtAuthentication(this IServiceCollection services, IConfiguration config)
{
var jwtOptions = new JwtOptions();
config.GetSection(JwtOptions.SectionName).Bind(jwtOptions); // 將設定檔中的設定值綁定到 JwtOptions 物件上
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = ClaimTypes.NameIdentifier,
ClockSkew = TimeSpan.Zero, // 有效時間接受的誤差
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtOptions.SignKey)), // 發行者金鑰
ValidateAudience = false, // 驗證受眾
ValidateIssuer = true, // 驗證發行者
ValidateIssuerSigningKey = true, // 驗證發行者金鑰
ValidateLifetime = true, // 驗證有效時間
ValidIssuer = jwtOptions.Issuer, // 發行者
};
});
// .AddJwtBearer();
services.AddAuthorization();
// 未通過身份驗證的請求自動回傳 401 Unauthorized (除非有使用 AllowAnonymousAttribute)
services.AddControllers(options => options.Filters.Add(new AuthorizeFilter()));
return services;
}
}
註冊服務:
var builder = WebApplication.CreateBuilder(args);
// 讀取設定值
builder.Services.AddOptions<JwtOptions>()
.BindConfiguration(JwtOptions.SectionName)
.ValidateDataAnnotations()
.ValidateOnStart();
builder.Services.AddSingleton<JwtService>();
builder.Services.AddJwtAuthentication(builder.Configuration);
var app = builder.Build();
app.UseAuthentication(); // 身份驗證
app.UseAuthorization(); // 驗證授權 (原本應該就有這行,注意不要重複加入)
建立從 JWT 中取得身份識別的範例 API:
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("[controller]")]
public class UserController : ControllerBase
{
[HttpGet("profile")]
public ActionResult<string> Profile()
{
// 從 jwt 取得使用者名稱
var userName = User.Identity?.Name;
return Ok(userName);
}
}
登入 API 的呼叫方式如下:
curl --location 'http://localhost:5221/User/profile' \
--header 'accept: text/plain' \
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJydXl1dCIsImV4cCI6MTcwNDEyNzcxNywiaWF0IjoxNzA0MTI0MTE3LCJpc3MiOiJodHRwOi8vcnV5dXQuY29tLyJ9.--0JDyIOgrk4u5lSsqUm-cqHPeR-z1bKJj_GaRwSx_c'
回應範例:
ruyut
這樣就完成了登入取得 JWT 、呼叫 API 需要 JWT ,並且可以藉由 JWT 確認呼叫者的身份!
感謝教學~
回覆刪除非常感謝您的支持!
刪除