using Avalonia_Common.Core; using Avalonia_EFCore.Database; using Avalonia_EFCore.Models; using Avalonia_Services.Core; using Avalonia_Services.Services.AuthService; using Microsoft.EntityFrameworkCore; namespace Avalonia_API.Authentication { /// /// API 鉴权端点服务,实现 , /// 处理登录、刷新 Token 和登出操作,使用 JWT 与 Refresh Token 机制。 /// public sealed class ApiAuthEndpointService( AppDataContext db, JwtTokenService jwtTokenService, RefreshTokenService refreshTokenService) : IApiAuthEndpointService { /// /// 处理用户登录请求。根据账号(邮箱或用户名)查找或创建用户, /// 生成 JWT Access Token 和 Refresh Token 并返回。 /// /// 服务端点上下文,包含请求体、请求头等信息。 /// 包含 AccessToken、RefreshToken 及过期时间的认证响应。 public async Task LoginAsync(ApiLoginRequest request, ServiceEndpointContext ctx) { if (string.IsNullOrWhiteSpace(request.Account)) { ctx.StatusCode = 400; return ResponseHelper.Failure(400, "账号不能为空"); } var user = await db.Users.FirstOrDefaultAsync( x => x.Email == request.Account || x.Name == request.Account); if (user is null) { user = new UserEntity { Name = request.Account, Email = request.Account.Contains('@') ? request.Account : null, }; db.Users.Add(user); await db.SaveChangesAsync(); } var roles = NormalizeRoles(request.Roles); var accessToken = jwtTokenService.CreateAccessToken(user, roles); var refreshToken = await refreshTokenService.CreateAsync( user.Id, ctx.GetHeader("User-Agent"), GetRemoteIpAddress(ctx)); return ResponseHelper.Ok(new AuthTokenResponse( accessToken.Token, refreshToken.Token, accessToken.ExpiresAt, refreshToken.Entity.ExpiresAt, roles), "登录成功"); } /// /// 使用 Refresh Token 轮换新的 Access Token 和 Refresh Token。 /// 旧的 Refresh Token 会被撤销并替换。 /// /// 服务端点上下文,包含请求体中的 RefreshToken。 /// 新的 Token 对;若 Refresh Token 无效则返回 401 错误。 public async Task RefreshAsync(ApiRefreshTokenRequest request, ServiceEndpointContext ctx) { var rotated = await refreshTokenService.RotateAsync( request.RefreshToken, ctx.GetHeader("User-Agent"), GetRemoteIpAddress(ctx)); if (rotated is null) { ctx.StatusCode = 401; return ResponseHelper.Failure(401, "刷新 token 无效或已过期"); } var user = await db.Users.FindAsync(rotated.Value.Entity.UserId); if (user is null) { ctx.StatusCode = 401; return ResponseHelper.Failure(401, "用户不存在"); } var roles = new[] { "Admin" }; var accessToken = jwtTokenService.CreateAccessToken(user, roles); return ResponseHelper.Ok(new AuthTokenResponse( accessToken.Token, rotated.Value.Token, accessToken.ExpiresAt, rotated.Value.Entity.ExpiresAt, roles), "刷新成功"); } /// /// 处理用户登出请求,撤销指定的 Refresh Token。 /// /// 服务端点上下文,包含请求体中的 RefreshToken。 /// 登出成功的响应。 public async Task LogoutAsync(ApiLogoutRequest request, ServiceEndpointContext ctx) { await refreshTokenService.RevokeAsync(request.RefreshToken); return ResponseHelper.Succeed("退出成功"); } /// /// 从上下文的 Items 中提取 ASP.NET Core HttpContext,并获取客户端远程 IP 地址。 /// /// 服务端点上下文。 /// 客户端 IP 地址字符串;若无法获取则返回 null。 private static string? GetRemoteIpAddress(ServiceEndpointContext ctx) { return ctx.Items.TryGetValue("HttpContext", out var value) && value is HttpContext httpContext ? httpContext.Connection.RemoteIpAddress?.ToString() : null; } /// /// 规范化角色数组:去空白、去重(忽略大小写),为空时默认返回 Admin 角色。 /// /// 原始角色数组,可为 null。 /// 规范化后的角色数组。 private static string[] NormalizeRoles(string[]? roles) { var normalized = roles? .Where(role => !string.IsNullOrWhiteSpace(role)) .Select(role => role.Trim()) .Distinct(StringComparer.OrdinalIgnoreCase) .ToArray(); return normalized is { Length: > 0 } ? normalized : ["Admin"]; } } }