using Microsoft.EntityFrameworkCore; using FutureMailAPI.Data; using FutureMailAPI.Models; using FutureMailAPI.DTOs; using FutureMailAPI.Helpers; using System.Security.Cryptography; using System.Text.Json; namespace FutureMailAPI.Services { public class OAuthService : IOAuthService { private readonly FutureMailDbContext _context; private readonly ILogger _logger; private readonly IPasswordHelper _passwordHelper; public OAuthService(FutureMailDbContext context, ILogger logger, IPasswordHelper passwordHelper) { _context = context; _logger = logger; _passwordHelper = passwordHelper; } public async Task> CreateClientAsync(int userId, OAuthClientCreateDto createDto) { var clientId = GenerateRandomString(32); var clientSecret = GenerateRandomString(64); var client = new OAuthClient { ClientId = clientId, ClientSecret = clientSecret, Name = createDto.Name, RedirectUris = JsonSerializer.Serialize(createDto.RedirectUris), Scopes = JsonSerializer.Serialize(createDto.Scopes), IsActive = true, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow }; _context.OAuthClients.Add(client); await _context.SaveChangesAsync(); var result = new OAuthClientSecretDto { ClientId = clientId, ClientSecret = clientSecret }; return ApiResponse.SuccessResult(result, "OAuth客户端创建成功"); } public async Task> GetClientAsync(string clientId) { var client = await _context.OAuthClients .FirstOrDefaultAsync(c => c.ClientId == clientId && c.IsActive); if (client == null) { return ApiResponse.ErrorResult("客户端不存在"); } var redirectUris = JsonSerializer.Deserialize(client.RedirectUris) ?? Array.Empty(); var scopes = JsonSerializer.Deserialize(client.Scopes) ?? Array.Empty(); var result = new OAuthClientDto { Id = client.Id, ClientId = client.ClientId, Name = client.Name, RedirectUris = redirectUris, Scopes = scopes, IsActive = client.IsActive, CreatedAt = client.CreatedAt, UpdatedAt = client.UpdatedAt }; return ApiResponse.SuccessResult(result); } public async Task> AuthorizeAsync(int userId, OAuthAuthorizationRequestDto request) { // 验证客户端 var client = await _context.OAuthClients .FirstOrDefaultAsync(c => c.ClientId == request.ClientId && c.IsActive); if (client == null) { return ApiResponse.ErrorResult("无效的客户端ID"); } // 验证重定向URI var redirectUris = JsonSerializer.Deserialize(client.RedirectUris) ?? Array.Empty(); if (!redirectUris.Contains(request.RedirectUri)) { return ApiResponse.ErrorResult("无效的重定向URI"); } // 验证范围 var clientScopes = JsonSerializer.Deserialize(client.Scopes) ?? Array.Empty(); var requestedScopes = request.Scope.Split(' ', StringSplitOptions.RemoveEmptyEntries); foreach (var scope in requestedScopes) { if (!clientScopes.Contains(scope)) { return ApiResponse.ErrorResult($"无效的范围: {scope}"); } } // 生成授权码 var code = GenerateRandomString(64); var authorizationCode = new OAuthAuthorizationCode { Code = code, ClientId = client.Id, UserId = userId, RedirectUri = request.RedirectUri, Scopes = request.Scope, IsUsed = false, ExpiresAt = DateTime.UtcNow.AddMinutes(10), // 授权码10分钟后过期 CreatedAt = DateTime.UtcNow }; _context.OAuthAuthorizationCodes.Add(authorizationCode); await _context.SaveChangesAsync(); var result = new OAuthAuthorizationResponseDto { Code = code, State = request.State }; return ApiResponse.SuccessResult(result); } public async Task> ExchangeCodeForTokenAsync(OAuthTokenRequestDto request) { // 验证客户端 var client = await GetClientByCredentialsAsync(request.ClientId, request.ClientSecret); if (client == null) { return ApiResponse.ErrorResult("无效的客户端凭据"); } // 验证授权码 var authCode = await _context.OAuthAuthorizationCodes .Include(c => c.Client) .Include(c => c.User) .FirstOrDefaultAsync(c => c.Code == request.Code && c.ClientId == client.Id); if (authCode == null || authCode.IsUsed || authCode.ExpiresAt < DateTime.UtcNow) { return ApiResponse.ErrorResult("无效的授权码"); } // 验证重定向URI if (authCode.RedirectUri != request.RedirectUri) { return ApiResponse.ErrorResult("重定向URI不匹配"); } // 标记授权码为已使用 authCode.IsUsed = true; await _context.SaveChangesAsync(); // 生成访问令牌和刷新令牌 var accessToken = GenerateRandomString(64); var refreshToken = GenerateRandomString(64); var scopes = !string.IsNullOrEmpty(request.Scope) ? request.Scope : authCode.Scopes; var oauthAccessToken = new OAuthAccessToken { Token = accessToken, ClientId = client.Id, UserId = authCode.UserId, Scopes = scopes, IsRevoked = false, ExpiresAt = DateTime.UtcNow.AddHours(1), // 访问令牌1小时后过期 CreatedAt = DateTime.UtcNow }; var oauthRefreshToken = new OAuthRefreshToken { Token = refreshToken, ClientId = client.Id, UserId = authCode.UserId, IsUsed = false, ExpiresAt = DateTime.UtcNow.AddDays(30), // 刷新令牌30天后过期 CreatedAt = DateTime.UtcNow }; _context.OAuthAccessTokens.Add(oauthAccessToken); _context.OAuthRefreshTokens.Add(oauthRefreshToken); await _context.SaveChangesAsync(); var result = new OAuthTokenResponseDto { AccessToken = accessToken, TokenType = "Bearer", ExpiresIn = 3600, // 1小时 RefreshToken = refreshToken, Scope = scopes }; return ApiResponse.SuccessResult(result); } public async Task> RefreshTokenAsync(OAuthTokenRequestDto request) { // 验证客户端 var client = await GetClientByCredentialsAsync(request.ClientId, request.ClientSecret); if (client == null) { return ApiResponse.ErrorResult("无效的客户端凭据"); } // 验证刷新令牌 var refreshToken = await _context.OAuthRefreshTokens .Include(t => t.Client) .Include(t => t.User) .FirstOrDefaultAsync(t => t.Token == request.RefreshToken && t.ClientId == client.Id); if (refreshToken == null || refreshToken.IsUsed || refreshToken.ExpiresAt < DateTime.UtcNow) { return ApiResponse.ErrorResult("无效的刷新令牌"); } // 标记旧刷新令牌为已使用 refreshToken.IsUsed = true; // 撤销旧访问令牌 var oldAccessTokens = await _context.OAuthAccessTokens .Where(t => t.UserId == refreshToken.UserId && t.ClientId == client.Id && !t.IsRevoked) .ToListAsync(); foreach (var token in oldAccessTokens) { token.IsRevoked = true; } // 生成新的访问令牌和刷新令牌 var newAccessToken = GenerateRandomString(64); var newRefreshToken = GenerateRandomString(64); var scopes = !string.IsNullOrEmpty(request.Scope) ? request.Scope : ""; var newOAuthAccessToken = new OAuthAccessToken { Token = newAccessToken, ClientId = client.Id, UserId = refreshToken.UserId, Scopes = scopes, IsRevoked = false, ExpiresAt = DateTime.UtcNow.AddHours(1), // 访问令牌1小时后过期 CreatedAt = DateTime.UtcNow }; var newOAuthRefreshToken = new OAuthRefreshToken { Token = newRefreshToken, ClientId = client.Id, UserId = refreshToken.UserId, IsUsed = false, ExpiresAt = DateTime.UtcNow.AddDays(30), // 刷新令牌30天后过期 CreatedAt = DateTime.UtcNow }; _context.OAuthAccessTokens.Add(newOAuthAccessToken); _context.OAuthRefreshTokens.Add(newOAuthRefreshToken); await _context.SaveChangesAsync(); var result = new OAuthTokenResponseDto { AccessToken = newAccessToken, TokenType = "Bearer", ExpiresIn = 3600, // 1小时 RefreshToken = newRefreshToken, Scope = scopes }; return ApiResponse.SuccessResult(result); } public async Task> RevokeTokenAsync(string token) { var accessToken = await _context.OAuthAccessTokens .FirstOrDefaultAsync(t => t.Token == token); if (accessToken == null) { return ApiResponse.ErrorResult("令牌不存在"); } accessToken.IsRevoked = true; await _context.SaveChangesAsync(); return ApiResponse.SuccessResult(true, "令牌已撤销"); } public async Task> ValidateTokenAsync(string token) { var accessToken = await _context.OAuthAccessTokens .FirstOrDefaultAsync(t => t.Token == token && !t.IsRevoked && t.ExpiresAt > DateTime.UtcNow); if (accessToken == null) { return ApiResponse.ErrorResult("无效的令牌"); } return ApiResponse.SuccessResult(true); } public async Task GetAccessTokenAsync(string token) { return await _context.OAuthAccessTokens .Include(t => t.Client) .Include(t => t.User) .FirstOrDefaultAsync(t => t.Token == token && !t.IsRevoked && t.ExpiresAt > DateTime.UtcNow); } public async Task GetClientByCredentialsAsync(string clientId, string clientSecret) { if (string.IsNullOrEmpty(clientSecret)) { // 公开客户端,只验证客户端ID return await _context.OAuthClients .FirstOrDefaultAsync(c => c.ClientId == clientId && c.IsActive); } // 机密客户端,验证客户端ID和密钥 return await _context.OAuthClients .FirstOrDefaultAsync(c => c.ClientId == clientId && c.ClientSecret == clientSecret && c.IsActive); } public async Task> LoginAsync(OAuthLoginDto loginDto) { // 验证客户端 var client = await GetClientByCredentialsAsync(loginDto.ClientId, loginDto.ClientSecret); if (client == null) { return ApiResponse.ErrorResult("无效的客户端凭据"); } // 验证用户凭据 var user = await _context.Users .FirstOrDefaultAsync(u => (u.Email == loginDto.UsernameOrEmail || u.Nickname == loginDto.UsernameOrEmail)); if (user == null) { return ApiResponse.ErrorResult("用户名或密码错误"); } // 验证密码 if (!_passwordHelper.VerifyPassword(loginDto.Password, user.PasswordHash)) { return ApiResponse.ErrorResult("用户名或密码错误"); } // 生成访问令牌和刷新令牌 var accessToken = GenerateRandomString(64); var refreshToken = GenerateRandomString(64); var oauthAccessToken = new OAuthAccessToken { Token = accessToken, ClientId = client.Id, UserId = user.Id, Scopes = loginDto.Scope, IsRevoked = false, ExpiresAt = DateTime.UtcNow.AddHours(1), // 访问令牌1小时后过期 CreatedAt = DateTime.UtcNow }; var oauthRefreshToken = new OAuthRefreshToken { Token = refreshToken, ClientId = client.Id, UserId = user.Id, IsUsed = false, ExpiresAt = DateTime.UtcNow.AddDays(30), // 刷新令牌30天后过期 CreatedAt = DateTime.UtcNow }; _context.OAuthAccessTokens.Add(oauthAccessToken); _context.OAuthRefreshTokens.Add(oauthRefreshToken); await _context.SaveChangesAsync(); var result = new OAuthTokenResponseDto { AccessToken = accessToken, TokenType = "Bearer", ExpiresIn = 3600, // 1小时 RefreshToken = refreshToken, Scope = loginDto.Scope }; return ApiResponse.SuccessResult(result); } private string GenerateRandomString(int length) { const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; var random = new Random(); var result = new char[length]; for (int i = 0; i < length; i++) { result[i] = chars[random.Next(chars.Length)]; } return new string(result); } } }