101 lines
3.3 KiB
C#
101 lines
3.3 KiB
C#
using Microsoft.IdentityModel.Tokens;
|
|
using System.IdentityModel.Tokens.Jwt;
|
|
using System.Security.Claims;
|
|
using System.Text;
|
|
|
|
namespace DramaLing.Api.Services;
|
|
|
|
public interface IAuthService
|
|
{
|
|
Task<Guid?> GetUserIdFromTokenAsync(string? authorizationHeader);
|
|
Task<ClaimsPrincipal?> ValidateTokenAsync(string token);
|
|
}
|
|
|
|
public class AuthService : IAuthService
|
|
{
|
|
private readonly IConfiguration _configuration;
|
|
private readonly ILogger<AuthService> _logger;
|
|
|
|
public AuthService(IConfiguration configuration, ILogger<AuthService> logger)
|
|
{
|
|
_configuration = configuration;
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task<Guid?> GetUserIdFromTokenAsync(string? authorizationHeader)
|
|
{
|
|
try
|
|
{
|
|
if (string.IsNullOrEmpty(authorizationHeader))
|
|
return null;
|
|
|
|
if (!authorizationHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
|
|
return null;
|
|
|
|
var token = authorizationHeader["Bearer ".Length..].Trim();
|
|
var claimsPrincipal = await ValidateTokenAsync(token);
|
|
|
|
if (claimsPrincipal == null)
|
|
return null;
|
|
|
|
var userIdString = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier)?.Value ??
|
|
claimsPrincipal.FindFirst("sub")?.Value;
|
|
|
|
if (Guid.TryParse(userIdString, out var userId))
|
|
return userId;
|
|
|
|
return null;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error extracting user ID from token");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public async Task<ClaimsPrincipal?> ValidateTokenAsync(string token)
|
|
{
|
|
try
|
|
{
|
|
// 優先從環境變數讀取,再從配置讀取
|
|
var jwtSecret = Environment.GetEnvironmentVariable("DRAMALING_SUPABASE_JWT_SECRET")
|
|
?? _configuration["Supabase:JwtSecret"];
|
|
|
|
if (string.IsNullOrEmpty(jwtSecret))
|
|
{
|
|
_logger.LogError("Supabase JWT Secret not configured in environment variables or config");
|
|
return null;
|
|
}
|
|
|
|
var tokenHandler = new JwtSecurityTokenHandler();
|
|
var key = Encoding.UTF8.GetBytes(jwtSecret);
|
|
|
|
var validationParameters = new TokenValidationParameters
|
|
{
|
|
ValidateIssuer = true,
|
|
ValidateAudience = true,
|
|
ValidateLifetime = true,
|
|
ValidateIssuerSigningKey = true,
|
|
ValidIssuer = Environment.GetEnvironmentVariable("DRAMALING_SUPABASE_URL")
|
|
?? _configuration["Supabase:Url"]
|
|
?? "https://localhost",
|
|
ValidAudience = "authenticated",
|
|
IssuerSigningKey = new SymmetricSecurityKey(key),
|
|
ClockSkew = TimeSpan.FromMinutes(5) // 允許 5 分鐘時間偏差
|
|
};
|
|
|
|
var principal = tokenHandler.ValidateToken(token, validationParameters, out _);
|
|
return principal;
|
|
}
|
|
catch (SecurityTokenException ex)
|
|
{
|
|
_logger.LogWarning("Invalid JWT token: {Message}", ex.Message);
|
|
return null;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error validating JWT token");
|
|
return null;
|
|
}
|
|
}
|
|
} |