Contents

使用JSON Web Token(JWT)進行身分驗證與授權

使用JSON Web Token(JWT)進行身分驗證與授權

JWT(JSON Web Token)是一種廣泛使用的身份驗證和授權方法。它通常用於傳輸用戶訊息,可以使用HMAC 演算法或 RSA 公鑰/私鑰對進行簽署。

我們預計將天氣預報資訊,設為需要進行驗證,才能讀取。

驗證流程

  • 當使用者未進行驗證,進行訪問受保護資料時,顯示 401 錯誤
  • 使用者登入,Server 認證成功後,傳送一組 JWT 給使用者。
  • 使用者將取得 JWT,於 header 中的 Authorization 加上 Bearer token
  • 重新訪問受保護資料,顯示結果。
  • JWT 可以夾帶使用者資料,我們將訪問權限設為管理者,並嘗試不同角色的結果。

環境

  • Microsoft.AspNetCore.Authentication.JwtBearer

安裝 Microsoft.AspNetCore.Authentication.JwtBearer

/static/使用JSON Web Token(JWT)進行身分驗證與授權_376004120a0c47468dc9df102b59d56c/2023-10-18_13-47-43.png

設置 Issuer 和 Key

appsettings.json 中,設置 Issuer 和 Key

1
2
3
4
"JwtSettings": {
  "Issuer": "Solution1",
  "SecretKey": "ZG0JZtDY3^FnkbCYy@!vJfVE922k9MJG"
}

設定驗證與授權

Program.cs 設定驗證,並讀取 JwtSettings 設定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using Microsoft.AspNetCore.Authentication.JwtBearer;

// 讀取 JwtSettings
builder.Services.Configure<JwtSettings>(builder.Configuration.GetSection("JwtSettings"));

// 啟用 JwtBearer 驗證
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters()
    {
        // 驗證 Issuer
        ValidateIssuer = true,
        ValidIssuer = builder.Configuration.GetValue<string>("JwtSettings:Issuer"),

        // 驗證 Audience
        ValidateAudience = false,

        // 驗證 Token 有效期間
        ValidateLifetime = true,

        // 驗證 SecurityKey
        ValidateIssuerSigningKey = true,

        // Key
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration.GetValue<string>("JwtSettings:SecretKey")))
    };
});

// 順序不能錯,先驗證再授權
app.UseAuthentication();
app.UseAuthorization();

設定授權才能讀取

1
2
3
4
5
6
7
8
[Authorize]
[HttpGet]
public async Task<IActionResult> GetWeatherForecasts([FromQuery] GetWeatherForecastsQuery request)
{
    var query = await _mediator.Send(request);

    return Ok(query);
}

顯示未授權結果

/static/使用JSON Web Token(JWT)進行身分驗證與授權_376004120a0c47468dc9df102b59d56c/2023-10-18_14-09-29.png

登入並取得Token

接下來我們設置登入處理,當帳號密碼正確時,則回傳 Token,大意如下

1
2
3
4
5
6
7
[HttpPost]
public async Task<IActionResult> Login([FromBody] LoginCommand command)
{
    var result = await mediator.Send(command);

    return result is null ? NotFound() : Ok(result);
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class LoginCommandHandler : ICommandHandler<LoginCommand, string>
{
    private readonly IUserRepository _userRepository;
    private readonly IJwtService _jwtService;

    public LoginCommandHandler(IUserRepository userRepository, IJwtService jwtService)
    {
        _userRepository = userRepository ?? throw new ArgumentNullException(nameof(userRepository));
        _jwtService = jwtService ?? throw new ArgumentNullException(nameof(jwtService));
    }

    public async Task<string> Handle(LoginCommand request, CancellationToken cancellationToken)
    {
        var user = _userRepository.Login(request.Email, request.Password);

        if (user is null) return null;

        var token = _jwtService.GenerateToken(user);

        await Task.CompletedTask;
        return token;
    }
}

public class LoginCommand : ICommand<string>
{
    public string Email { get; set; }
    public string Password { get; set; }
}

產生 Token

其中 JwtService 就是我們要產生 Token 的服務,程式碼如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 如果 JwtService 放在 Infrastructure 層
// 可能要額外安裝 Microsoft.AspNetCore.Authentication.JwtBearer
public class JwtService : IJwtService
{
    private readonly JwtSettings _options;

    public JwtService(IOptions<JwtSettings> options)
    {
        _options = options.Value ?? throw new ArgumentNullException(nameof(options));
    }

    public string GenerateToken(User user)
    {
        // 設定要加入到 Token 中的 Claims
        var claims = new List<Claim>()
        {
            new Claim(JwtRegisteredClaimNames.Sub,user.Name),
            new Claim(JwtRegisteredClaimNames.Email,user.Email),
						new Claim(ClaimTypes.Role,user.Role)
        };

        // 建立一組對稱式加密的金鑰,主要用於 JWT 簽章之用
        var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_options.SecretKey));
        // HmacSha256 必須要大於 128 bits
        var signinCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

        var token = new JwtSecurityToken(
            issuer: _options.Issuer,
            audience: null,
            claims: claims,
            notBefore: null,
            expires: DateTime.UtcNow.AddHours(1),
            signinCredentials);

        return new JwtSecurityTokenHandler().WriteToken(token);
    }
}

測試登入後取得 Token

/static/使用JSON Web Token(JWT)進行身分驗證與授權_376004120a0c47468dc9df102b59d56c/2023-10-18_14-50-35.png

一開始有提到,JWT 是可以夾帶使用者資料的,我們可以透過 https://jwt.io/ 這個網站來看 Token 內容。

/static/使用JSON Web Token(JWT)進行身分驗證與授權_376004120a0c47468dc9df102b59d56c/2023-10-18_14-56-31.png

在 Header 中設置 Bearer <Token>

Swagger

/static/使用JSON Web Token(JWT)進行身分驗證與授權_376004120a0c47468dc9df102b59d56c/2023-10-18_15-01-19.png

通過授權查詢結果

/static/使用JSON Web Token(JWT)進行身分驗證與授權_376004120a0c47468dc9df102b59d56c/2023-10-18_15-07-31.png

Postman

/static/使用JSON Web Token(JWT)進行身分驗證與授權_376004120a0c47468dc9df102b59d56c/2023-10-18_15-09-24.png /static/使用JSON Web Token(JWT)進行身分驗證與授權_376004120a0c47468dc9df102b59d56c/2023-10-18_15-10-03.png

參考

如何在 ASP.NET Core 3 使用 Token-based 身分驗證與授權 (JWT)

https://blog.miniasp.com/post/2019/12/16/How-to-use-JWT-token-based-auth-in-aspnet-core-31