Files
emall-api/FutureMailAPI/Services/OAuthService.cs
2025-10-16 15:21:52 +08:00

204 lines
7.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<OAuthService> _logger;
public OAuthService(FutureMailDbContext context, IConfiguration configuration, ILogger<OAuthService> logger)
{
_context = context;
_configuration = configuration;
_logger = logger;
}
public async Task<ApiResponse<OAuthTokenResponseDto>> 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<OAuthTokenResponseDto>.ErrorResult("无效的客户端凭据");
}
// 验证用户凭据
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);
}
catch (Exception ex)
{
_logger.LogError(ex, "OAuth登录时发生错误");
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("服务器内部错误");
}
}
public async Task<ApiResponse<OAuthTokenResponseDto>> 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<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);
}
catch (Exception ex)
{
_logger.LogError(ex, "OAuth刷新令牌时发生错误");
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("服务器内部错误");
}
}
public async Task<bool> 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<OAuthToken?> 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);
}
}
}