哈囉大家好!
今天要繼續完成生成Session Token和AuthService的部分,那就馬上開始吧!(有點落落長QQ)
Session Token通常是透過產生一個JSON Web Token(JWT)來實現的,因為JWT可以在無狀態(stateless)API中使用,並且可以攜帶用戶資訊(Claims)。
接著要利用System.IdentityModel.Tokens.Jwt
library來建立Session Token。
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。
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被竊取。
using GoDutch.Models;
namespace GoDutchBackend.Services
{
public interface IAuthService
{
public string GenerateToken(User user);
}
}
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;
}
之後在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的請求!