204 lines
7.5 KiB
C#
204 lines
7.5 KiB
C#
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);
|
||
}
|
||
}
|
||
} |