feat(auth): 添加统一 API 和 PC 认证端点

- 新增 API 端 JWT 登录、refresh token 轮换和退出登录流程
- 新增 refresh token 实体、DbSet 配置和 EF Core 迁移
- 新增 PC 端授权码登录、本地全局 token 刷新、登出和鉴权服务
- 扩展统一端点模型,支持宿主过滤、角色鉴权、OpenAPI 元数据和 DI 服务处理器
- API 启用 JwtBearer 认证、Swagger UI 和认证端点注册
- PC 端注册认证服务,并按宿主过滤桌面拦截端点
This commit is contained in:
2026-05-15 17:35:07 +08:00
parent c5f741e6a4
commit a9abd90874
26 changed files with 1295 additions and 31 deletions
@@ -1,4 +1,5 @@
using Avalonia_Services.Core;
using Microsoft.AspNetCore.Authorization;
using AspNetCoreFilterContext = Microsoft.AspNetCore.Http.EndpointFilterInvocationContext;
using AspNetCoreFilterDelegate = Microsoft.AspNetCore.Http.EndpointFilterDelegate;
// 解决与 ASP.NET Core 同名类型的冲突
@@ -22,7 +23,7 @@ namespace Avalonia_API.Extensions
{
var apiGroup = routeBuilder.MapGroup("/");
foreach (var endpoint in endpoints.Endpoints)
foreach (var endpoint in endpoints.ForHost(EndpointHostTarget.Api))
{
var routeHandlerBuilder = MapEndpoint(apiGroup, endpoint, serviceProvider);
@@ -43,7 +44,14 @@ namespace Avalonia_API.Extensions
// 鉴权(使用 ASP.NET Core 原生鉴权机制)
if (endpoint.RequireAuthorization)
{
if (!string.IsNullOrEmpty(endpoint.Policy))
if (endpoint.Roles.Count > 0)
{
routeHandlerBuilder.RequireAuthorization(new AuthorizeAttribute
{
Roles = string.Join(',', endpoint.Roles),
});
}
else if (!string.IsNullOrEmpty(endpoint.Policy))
{
routeHandlerBuilder.RequireAuthorization(endpoint.Policy);
}
@@ -57,6 +65,31 @@ namespace Avalonia_API.Extensions
{
routeHandlerBuilder.WithName(endpoint.Name);
}
if (!string.IsNullOrEmpty(endpoint.OpenApiTag))
{
routeHandlerBuilder.WithTags(endpoint.OpenApiTag);
}
if (!string.IsNullOrEmpty(endpoint.OpenApiDescription))
{
routeHandlerBuilder.WithDescription(endpoint.OpenApiDescription);
}
if (!string.IsNullOrWhiteSpace(endpoint.OpenApiSummary))
{
routeHandlerBuilder.WithSummary(endpoint.OpenApiSummary);
}
if (endpoint.OpenApiRequestType is not null)
{
routeHandlerBuilder.Accepts(endpoint.OpenApiRequestType, "application/json");
}
if (endpoint.OpenApiResponseType is not null)
{
routeHandlerBuilder.Produces(200, endpoint.OpenApiResponseType, "application/json");
}
}
return routeBuilder;
@@ -87,6 +120,7 @@ namespace Avalonia_API.Extensions
{
var ctx = await BuildContextFromHttpContext(httpContext);
ctx.Items["ServiceProvider"] = serviceProvider;
ctx.Items["User"] = httpContext.User;
var result = await unifiedHandler(ctx);
@@ -127,6 +161,7 @@ namespace Avalonia_API.Extensions
}
ctx.Items["HttpContext"] = httpContext;
ctx.Items["User"] = httpContext.User;
return ctx;
}