using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Security.Cryptography; using System.Text; using FutureMailAPI.Data; using FutureMailAPI.DTOs; using FutureMailAPI.Models; namespace FutureMailAPI.Services { public class OAuthService : IOAuthService { private readonly FutureMailDbContext _context; private readonly IConfiguration _configuration; private readonly ILogger _logger; public OAuthService(FutureMailDbContext context, IConfiguration configuration, ILogger logger) { _context = context; _configuration = configuration; _logger = logger; } public async Task> LoginAsync(OAuthLoginRequestDto request) { try { // 验证OAuth客户端 var client = await _context.OAuthClients .FirstOrDefaultAsync(c => c.ClientId == request.ClientId && c.ClientSecret == request.ClientSecret); if (client == null) { return ApiResponse.ErrorResult("无效的客户端凭据"); } // 验证用户凭据 var user = await _context.Users .FirstOrDefaultAsync(u => u.Email == request.Username); if (user == null) { return ApiResponse.ErrorResult("用户不存在"); } // 验证密码 if (!BCrypt.Net.BCrypt.Verify(request.Password, user.PasswordHash)) { return ApiResponse.ErrorResult("密码错误"); } // 生成访问令牌 var accessToken = GenerateJwtToken(user, client); var refreshToken = GenerateRefreshToken(); // 保存令牌到数据库 var oauthToken = new OAuthToken { AccessToken = accessToken, RefreshToken = refreshToken, UserId = user.Id, ClientId = client.Id, ExpiresAt = DateTime.UtcNow.AddHours(1), // 访问令牌1小时过期 CreatedAt = DateTime.UtcNow }; _context.OAuthTokens.Add(oauthToken); await _context.SaveChangesAsync(); var response = new OAuthTokenResponseDto { AccessToken = accessToken, RefreshToken = refreshToken, TokenType = "Bearer", ExpiresIn = 3600 // 1小时,单位秒 }; return ApiResponse.SuccessResult(response); } catch (Exception ex) { _logger.LogError(ex, "OAuth登录时发生错误"); return ApiResponse.ErrorResult("服务器内部错误"); } } public async Task> RefreshTokenAsync(OAuthRefreshTokenRequestDto request) { try { // 查找刷新令牌 var token = await _context.OAuthTokens .Include(t => t.User) .Include(t => t.Client) .FirstOrDefaultAsync(t => t.RefreshToken == request.RefreshToken); if (token == null) { return ApiResponse.ErrorResult("无效的刷新令牌"); } // 生成新的访问令牌 var accessToken = GenerateJwtToken(token.User, token.Client); var refreshToken = GenerateRefreshToken(); // 更新令牌 token.AccessToken = accessToken; token.RefreshToken = refreshToken; token.ExpiresAt = DateTime.UtcNow.AddHours(1); token.UpdatedAt = DateTime.UtcNow; await _context.SaveChangesAsync(); var response = new OAuthTokenResponseDto { AccessToken = accessToken, RefreshToken = refreshToken, TokenType = "Bearer", ExpiresIn = 3600 // 1小时,单位秒 }; return ApiResponse.SuccessResult(response); } catch (Exception ex) { _logger.LogError(ex, "OAuth刷新令牌时发生错误"); return ApiResponse.ErrorResult("服务器内部错误"); } } public async Task RevokeTokenAsync(string accessToken) { try { var token = await _context.OAuthTokens .FirstOrDefaultAsync(t => t.AccessToken == accessToken); if (token != null) { _context.OAuthTokens.Remove(token); await _context.SaveChangesAsync(); return true; } return false; } catch (Exception ex) { _logger.LogError(ex, "OAuth撤销令牌时发生错误"); return false; } } public async Task GetTokenAsync(string accessToken) { try { return await _context.OAuthTokens .Include(t => t.User) .Include(t => t.Client) .FirstOrDefaultAsync(t => t.AccessToken == accessToken && t.ExpiresAt > DateTime.UtcNow); } catch (Exception ex) { _logger.LogError(ex, "获取OAuth令牌时发生错误"); return null; } } private string GenerateJwtToken(User user, OAuthClient client) { var jwtSettings = _configuration.GetSection("Jwt"); var key = Encoding.ASCII.GetBytes(jwtSettings["Key"] ?? throw new InvalidOperationException("JWT密钥未配置")); var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), new Claim(ClaimTypes.Name, user.Username), new Claim(ClaimTypes.Email, user.Email), new Claim("client_id", client.ClientId) }), Expires = DateTime.UtcNow.AddHours(1), Issuer = jwtSettings["Issuer"], Audience = jwtSettings["Audience"], SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) }; var tokenHandler = new JwtSecurityTokenHandler(); var token = tokenHandler.CreateToken(tokenDescriptor); return tokenHandler.WriteToken(token); } private string GenerateRefreshToken() { var randomNumber = new byte[32]; using var rng = RandomNumberGenerator.Create(); rng.GetBytes(randomNumber); return Convert.ToBase64String(randomNumber); } } }