using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; namespace DramaLing.Api.Tests.Integration.Fixtures; /// /// JWT 測試助手類別 /// 提供測試用的 JWT Token 生成功能 /// public static class JwtTestHelper { private const string TestSecretKey = "test-secret-minimum-32-characters-long-for-jwt-signing-in-test-mode-only"; private const string TestIssuer = "https://test.supabase.co"; private const string TestAudience = "authenticated"; /// /// 為指定使用者生成測試用 JWT Token /// public static string GenerateJwtToken(Guid userId, string? email = null, string? username = null) { var tokenHandler = new JwtSecurityTokenHandler(); var key = Encoding.UTF8.GetBytes(TestSecretKey); var claims = new List { new("sub", userId.ToString()), new("aud", TestAudience), new("iss", TestIssuer), new("iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64), new("exp", DateTimeOffset.UtcNow.AddHours(1).ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64) }; // 添加可選的 claims if (!string.IsNullOrEmpty(email)) claims.Add(new Claim("email", email)); if (!string.IsNullOrEmpty(username)) claims.Add(new Claim("preferred_username", username)); var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(claims), Expires = DateTime.UtcNow.AddHours(1), Issuer = TestIssuer, Audience = TestAudience, SigningCredentials = new SigningCredentials( new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) }; var token = tokenHandler.CreateToken(tokenDescriptor); return tokenHandler.WriteToken(token); } /// /// 為 TestUser1 生成 JWT Token /// public static string GenerateTestUser1Token() { return GenerateJwtToken( TestDataSeeder.TestUser1Id, "test1@example.com", "testuser1" ); } /// /// 為 TestUser2 生成 JWT Token /// public static string GenerateTestUser2Token() { return GenerateJwtToken( TestDataSeeder.TestUser2Id, "test2@example.com", "testuser2" ); } /// /// 生成已過期的 JWT Token (用於測試無效 token) /// public static string GenerateExpiredJwtToken(Guid userId) { var tokenHandler = new JwtSecurityTokenHandler(); var key = Encoding.UTF8.GetBytes(TestSecretKey); var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(new[] { new Claim("sub", userId.ToString()), new Claim("aud", TestAudience) }), Expires = DateTime.UtcNow.AddHours(-1), // 1 小時前過期 IssuedAt = DateTime.UtcNow.AddHours(-2), // 2 小時前簽發 // 不設置 NotBefore,讓它使用預設值 Issuer = TestIssuer, Audience = TestAudience, SigningCredentials = new SigningCredentials( new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) }; var token = tokenHandler.CreateToken(tokenDescriptor); return tokenHandler.WriteToken(token); } /// /// 生成無效簽章的 JWT Token (用於測試無效 token) /// public static string GenerateInvalidSignatureToken(Guid userId) { var tokenHandler = new JwtSecurityTokenHandler(); var wrongKey = Encoding.UTF8.GetBytes("wrong-secret-key-for-invalid-signature-test-purposes-only"); var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(new[] { new Claim("sub", userId.ToString()), new Claim("aud", TestAudience) }), Expires = DateTime.UtcNow.AddHours(1), Issuer = TestIssuer, Audience = TestAudience, SigningCredentials = new SigningCredentials( new SymmetricSecurityKey(wrongKey), // 使用錯誤的 key SecurityAlgorithms.HmacSha256Signature) }; var token = tokenHandler.CreateToken(tokenDescriptor); return tokenHandler.WriteToken(token); } /// /// 驗證 JWT Token 是否有效 (用於測試驗證) /// public static ClaimsPrincipal? ValidateToken(string token) { try { var tokenHandler = new JwtSecurityTokenHandler(); var key = Encoding.UTF8.GetBytes(TestSecretKey); var validationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(key), ValidateIssuer = true, ValidIssuer = TestIssuer, ValidateAudience = true, ValidAudience = TestAudience, ValidateLifetime = true, ClockSkew = TimeSpan.Zero }; var principal = tokenHandler.ValidateToken(token, validationParameters, out _); return principal; } catch { return null; } } }