Files
emall-api/FutureMailAPI/Services/OAuthService.cs

204 lines
7.5 KiB
C#
Raw Normal View History

2025-10-16 09:56:36 +08:00
using Microsoft.EntityFrameworkCore;
2025-10-16 15:21:52 +08:00
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
2025-10-16 09:56:36 +08:00
using FutureMailAPI.Data;
using FutureMailAPI.DTOs;
2025-10-16 15:21:52 +08:00
using FutureMailAPI.Models;
2025-10-16 09:56:36 +08:00
namespace FutureMailAPI.Services
{
public class OAuthService : IOAuthService
{
private readonly FutureMailDbContext _context;
2025-10-16 15:21:52 +08:00
private readonly IConfiguration _configuration;
2025-10-16 09:56:36 +08:00
private readonly ILogger<OAuthService> _logger;
2025-10-16 15:21:52 +08:00
public OAuthService(FutureMailDbContext context, IConfiguration configuration, ILogger<OAuthService> logger)
2025-10-16 09:56:36 +08:00
{
_context = context;
2025-10-16 15:21:52 +08:00
_configuration = configuration;
2025-10-16 09:56:36 +08:00
_logger = logger;
}
2025-10-16 15:21:52 +08:00
public async Task<ApiResponse<OAuthTokenResponseDto>> LoginAsync(OAuthLoginRequestDto request)
2025-10-16 09:56:36 +08:00
{
2025-10-16 15:21:52 +08:00
try
2025-10-16 09:56:36 +08:00
{
2025-10-16 15:21:52 +08:00
// 验证OAuth客户端
var client = await _context.OAuthClients
.FirstOrDefaultAsync(c => c.ClientId == request.ClientId && c.ClientSecret == request.ClientSecret);
if (client == null)
2025-10-16 09:56:36 +08:00
{
2025-10-16 15:21:52 +08:00
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("无效的客户端凭据");
2025-10-16 09:56:36 +08:00
}
2025-10-16 15:21:52 +08:00
// 验证用户凭据
var user = await _context.Users
.FirstOrDefaultAsync(u => u.Email == request.Username);
if (user == null)
{
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("用户不存在");
}
// 验证密码
if (!BCrypt.Net.BCrypt.Verify(request.Password, user.PasswordHash))
{
return ApiResponse<OAuthTokenResponseDto>.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<OAuthTokenResponseDto>.SuccessResult(response);
2025-10-16 09:56:36 +08:00
}
2025-10-16 15:21:52 +08:00
catch (Exception ex)
2025-10-16 09:56:36 +08:00
{
2025-10-16 15:21:52 +08:00
_logger.LogError(ex, "OAuth登录时发生错误");
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("服务器内部错误");
}
2025-10-16 09:56:36 +08:00
}
2025-10-16 15:21:52 +08:00
public async Task<ApiResponse<OAuthTokenResponseDto>> RefreshTokenAsync(OAuthRefreshTokenRequestDto request)
2025-10-16 09:56:36 +08:00
{
2025-10-16 15:21:52 +08:00
try
2025-10-16 09:56:36 +08:00
{
2025-10-16 15:21:52 +08:00
// 查找刷新令牌
var token = await _context.OAuthTokens
.Include(t => t.User)
.Include(t => t.Client)
.FirstOrDefaultAsync(t => t.RefreshToken == request.RefreshToken);
if (token == null)
{
return ApiResponse<OAuthTokenResponseDto>.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<OAuthTokenResponseDto>.SuccessResult(response);
2025-10-16 09:56:36 +08:00
}
2025-10-16 15:21:52 +08:00
catch (Exception ex)
2025-10-16 09:56:36 +08:00
{
2025-10-16 15:21:52 +08:00
_logger.LogError(ex, "OAuth刷新令牌时发生错误");
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("服务器内部错误");
2025-10-16 09:56:36 +08:00
}
}
2025-10-16 15:21:52 +08:00
public async Task<bool> RevokeTokenAsync(string accessToken)
2025-10-16 09:56:36 +08:00
{
2025-10-16 15:21:52 +08:00
try
2025-10-16 09:56:36 +08:00
{
2025-10-16 15:21:52 +08:00
var token = await _context.OAuthTokens
.FirstOrDefaultAsync(t => t.AccessToken == accessToken);
if (token != null)
{
_context.OAuthTokens.Remove(token);
await _context.SaveChangesAsync();
return true;
}
return false;
2025-10-16 09:56:36 +08:00
}
2025-10-16 15:21:52 +08:00
catch (Exception ex)
2025-10-16 09:56:36 +08:00
{
2025-10-16 15:21:52 +08:00
_logger.LogError(ex, "OAuth撤销令牌时发生错误");
return false;
2025-10-16 09:56:36 +08:00
}
}
2025-10-16 15:21:52 +08:00
public async Task<OAuthToken?> GetTokenAsync(string accessToken)
2025-10-16 09:56:36 +08:00
{
2025-10-16 15:21:52 +08:00
try
2025-10-16 09:56:36 +08:00
{
2025-10-16 15:21:52 +08:00
return await _context.OAuthTokens
.Include(t => t.User)
.Include(t => t.Client)
.FirstOrDefaultAsync(t => t.AccessToken == accessToken && t.ExpiresAt > DateTime.UtcNow);
2025-10-16 09:56:36 +08:00
}
2025-10-16 15:21:52 +08:00
catch (Exception ex)
2025-10-16 09:56:36 +08:00
{
2025-10-16 15:21:52 +08:00
_logger.LogError(ex, "获取OAuth令牌时发生错误");
return null;
2025-10-16 09:56:36 +08:00
}
}
2025-10-16 15:21:52 +08:00
private string GenerateJwtToken(User user, OAuthClient client)
2025-10-16 09:56:36 +08:00
{
2025-10-16 15:21:52 +08:00
var jwtSettings = _configuration.GetSection("Jwt");
var key = Encoding.ASCII.GetBytes(jwtSettings["Key"] ?? throw new InvalidOperationException("JWT密钥未配置"));
var tokenDescriptor = new SecurityTokenDescriptor
2025-10-16 09:56:36 +08:00
{
2025-10-16 15:21:52 +08:00
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)
2025-10-16 09:56:36 +08:00
};
2025-10-16 15:21:52 +08:00
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
2025-10-16 09:56:36 +08:00
}
2025-10-16 15:21:52 +08:00
private string GenerateRefreshToken()
2025-10-16 09:56:36 +08:00
{
2025-10-16 15:21:52 +08:00
var randomNumber = new byte[32];
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
2025-10-16 09:56:36 +08:00
}
}
}