167 lines
5.7 KiB
C#
167 lines
5.7 KiB
C#
using Microsoft.IdentityModel.Tokens;
|
||
using System.IdentityModel.Tokens.Jwt;
|
||
using System.Security.Claims;
|
||
using System.Text;
|
||
|
||
namespace DramaLing.Api.Tests.Integration.Fixtures;
|
||
|
||
/// <summary>
|
||
/// JWT 測試助手類別
|
||
/// 提供測試用的 JWT Token 生成功能
|
||
/// </summary>
|
||
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";
|
||
|
||
/// <summary>
|
||
/// 為指定使用者生成測試用 JWT Token
|
||
/// </summary>
|
||
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<Claim>
|
||
{
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 為 TestUser1 生成 JWT Token
|
||
/// </summary>
|
||
public static string GenerateTestUser1Token()
|
||
{
|
||
return GenerateJwtToken(
|
||
TestDataSeeder.TestUser1Id,
|
||
"test1@example.com",
|
||
"testuser1"
|
||
);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 為 TestUser2 生成 JWT Token
|
||
/// </summary>
|
||
public static string GenerateTestUser2Token()
|
||
{
|
||
return GenerateJwtToken(
|
||
TestDataSeeder.TestUser2Id,
|
||
"test2@example.com",
|
||
"testuser2"
|
||
);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成已過期的 JWT Token (用於測試無效 token)
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成無效簽章的 JWT Token (用於測試無效 token)
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 驗證 JWT Token 是否有效 (用於測試驗證)
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
} |