iT邦幫忙

2025 iThome 鐵人賽

DAY 22
0
佛心分享-SideProject30

30天的旅程!從學習C#到開發小專案系列 第 22

DAY 22 - 生成Session Token並註冊驗證服務

  • 分享至 

  • xImage
  •  

哈囉大家好!
今天要繼續完成生成Session Token和AuthService的部分,那就馬上開始吧!(有點落落長QQ)

在AuthService中產生專案專屬的Session Token

Session Token通常是透過產生一個JSON Web Token(JWT)來實現的,因為JWT可以在無狀態(stateless)API中使用,並且可以攜帶用戶資訊(Claims)。

接著要利用System.IdentityModel.Tokens.Jwtlibrary來建立Session Token。

  1. 安裝JWT library:
    執行指令來進行安裝:
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
dotnet add package System.IdentityModel.Tokens.Jwt

註:Microsoft.AspNetCore.Authentication.JwtBearer是用來生成和驗證JWT的套件,
System.IdentityModel.Tokens.Jwt則是核心的JWT處理library。

  1. 配置JWT Secret Key(密鑰)和選項
    首先要在appsettings.json中新增JWT的配置,定義用於簽署JWT的密鑰(Secret Key),以及Token的有效期限...等。
"JwtSettings": {
    "Secret": "自己設定的至少32字元且複雜的secret key",
    "Issuer": "GoDutchApi",
    "Audience": "GoDutchClients",
    "TokenExpirationInMinutes": 60
  },

註:這裡"JwtSettings"中的"Secret"是用來簽署專案專屬JWT的secret key。這個secret key是自己定義的,並沒有特別的格式規定。
這個secret key會用在TokenService.cs來產生Token上的數位簽名。當前端把Session Token發給API時,API會用這個secret key來驗證session token是否有被竄改。
設定secret key時,必須至少32個字元並且複雜,並且不能洩漏給前端。並且在部署時,透過環境變數或Google Secret Manager來進行管理。
"Issuer"(發行者)則是定義「誰建立並簽發了這個JWT」,用來確認token確實是由該應用程式所屬的授權伺服器產生。
"Audience"(受眾)則是定義「這個JWT是要給誰做使用」,防止token被竊取。

  1. 建立JWT Token服務(專門處理Token生成任務)和更新AuthService
  • Interface ITokenService.cs:
using GoDutch.Models;
namespace GoDutchBackend.Services
{
    public interface IAuthService
    {
        public string GenerateToken(User user);
    }
}
  • Service TokenService.cs

namespace GoDutchBackend.Services
{
    public class TokenService: ITokenService
    {
        private readonly IConfiguration _configuration;
        
        public TokenService(IConfiguration configuration)
        {
            _configuration = configuration;
        }
        
        public string GenerateToken(User user)
        {
            var jwtSettings = _configuration.GetSection("JwtSettings");
            var key = Encoding.ASCII.GetBytes(jwtSettings["Secret"]!);
            
            // 定義Token攜帶的用戶資訊
            var claims = new List<Claim>
            {
                // 應用程式內部唯一的ID作為主體(Sub)
                new Claim(JwtRegisteredClaimNames.Sub, user.Id.toString()),
                // 包含其他有用的資訊,例如Email和Name
                new Claim(JwtRegisteredClaimNames.Email, user.Email),
                new Claim("name", user.Name)
            };
            
            // 設置安全secret key
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(claims),
                Expires = DateTime.Now.AddMinutes(jwtSettings.GetValue<double>("TokenExpirationInMinutes")),
                Issuer = jwtSettings["Issuer"],
                Audience = jwtSettings["Audience"],
                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
            };
            
            // 建立並寫入token
            var tokenHandler = new JwtSecurityTokenHandler();
            var token = tokenHandler.CreateToken(tokenDescriptor);

            return tokenHandler.WriteToken(token);
        }
    }
}

完成後就可以在AuthService中注入ITokenService:

public async Task<string?> AuthenticateGoogleUserAsync(string googleIdToken)
{
    ...
    var appSessionToken = _tokenService.GenerateToken(user);
    return appSessionToken; 
}

整合Controller和DI

之後在appsettings.json檔中,將Google Client ID加入配置:

"GoogleAuth": {
    "ClientId": "我的ClientID"
  }

最後在AuthController中使用注入的服務來處理驗證邏輯:

using GoDutchBackend.Models;
using GoDutchBackend.Services;
using Microsoft.AspNetCore.Mvc;

namespace GoDutchBackend.Controllers;

[ApiController]
[Route("[controller]")]
public class AuthController : ControllerBase
{
    private readonly ILogger<AuthController> _logger;
    private readonly IAuthService _authService;

    // 透過constructor注入auth服務
    public AuthController(IAuthService authService, ILogger<AuthController> logger)
    {
        _authService = authService;
        _logger = logger;
    }

    [HttpPost("google-login")]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
    public async Task<IActionResult> GoogleLogin([FromBody] TokenRequest request)
    {
        _logger.LogInformation("Attempting to authenticate user with ID Token.");

        var sessionToken = await _authService.AuthenticateGoogleUserAsync(request.GoogleSubId);

        if (string.IsNullOrEmpty(sessionToken))
        {
            // 驗證失敗
            _logger.LogWarning("Authentication failed for ID Token: {Token}", request.GoogleSubId);
            return Unauthorized(new { message = "Invalid Google ID token." });
        }

        // 驗證成功,回傳應用程式專屬的Session Token給前端
        _logger.LogInformation("Authenticate successful. Sending application session token.");

        return Ok(new
        {
            message = "Login successful",
            appSessionToken = sessionToken
        });
    }
}

在Program.cs檔案中把Token和Auth服務註冊到DI容器裡:

...
builder.Services.AddScoped<ITokenService, TokenService>();
builder.Services.AddScoped<IAuthService, AuthService>();

這樣就完成了回傳給前端Session Token的部分~
當Google ID Token驗證成功後,API會根據使用在儲存在Neon資料庫中的ID來產生一個安全且帶有簽名的專屬JWT Session Token,再把token回傳給前端,前端就可以拿這個token來進行其他API的請求!


上一篇
DAY 21 - 取得ID Token後,User資料發送到後端驗證儲存
下一篇
DAY 23 - 配置JWT Bearer 認證中介軟體(Middleware)
系列文
30天的旅程!從學習C#到開發小專案24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言