feat: add JWT authentication to gateway

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
movingsam 2026-02-28 18:37:31 +08:00
parent 0cbb5a2c0e
commit 2a4a06ddb8
3 changed files with 61 additions and 7 deletions

View File

@ -1,3 +1,4 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using YarpGateway.Data;
@ -9,6 +10,7 @@ namespace YarpGateway.Controllers;
[ApiController]
[Route("api/gateway")]
[Authorize] // 要求所有管理 API 都需要认证
public class GatewayConfigController : ControllerBase
{
private readonly IDbContextFactory<GatewayDbContext> _dbContextFactory;

View File

@ -1,3 +1,4 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using YarpGateway.Data;
@ -7,6 +8,7 @@ namespace YarpGateway.Controllers;
[ApiController]
[Route("api/gateway/pending-services")]
[Authorize] // 要求所有管理 API 都需要认证
public class PendingServicesController : ControllerBase
{
private readonly IDbContextFactory<GatewayDbContext> _dbContextFactory;

View File

@ -1,3 +1,4 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Serilog;
@ -23,10 +24,48 @@ builder.Host.UseSerilog(
.Enrich.FromLogContext()
);
// 配置 JWT
builder.Services.Configure<JwtConfig>(builder.Configuration.GetSection("Jwt"));
builder.Services.Configure<RedisConfig>(builder.Configuration.GetSection("Redis"));
builder.Services.AddSingleton(sp => sp.GetRequiredService<IOptions<RedisConfig>>().Value);
// 添加 JWT 认证
var jwtConfig = builder.Configuration.GetSection("Jwt").Get<JwtConfig>();
if (jwtConfig != null && !string.IsNullOrEmpty(jwtConfig.Authority))
{
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = jwtConfig.Authority;
options.Audience = jwtConfig.Audience;
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = jwtConfig.ValidateIssuer,
ValidateAudience = jwtConfig.ValidateAudience,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ClockSkew = TimeSpan.Zero
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
Log.Warning("JWT authentication failed: {Error}", context.Exception?.Message);
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
Log.Information("JWT token validated for user: {User}",
context.Principal?.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value ?? "unknown");
return Task.CompletedTask;
}
};
});
builder.Services.AddAuthorization();
}
builder.Services.AddDbContextFactory<GatewayDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"))
);
@ -78,6 +117,7 @@ builder.Services.AddServiceDiscovery();
builder.Services.AddHostedService<KubernetesPendingSyncService>();
// CORS 配置 - 修复 AllowAnyOrigin 与 AllowCredentials 不兼容问题
var corsSettings = builder.Configuration.GetSection("Cors");
builder.Services.AddCors(options =>
{
@ -88,16 +128,18 @@ builder.Services.AddCors(options =>
{
if (allowAnyOrigin)
{
policy.AllowAnyOrigin();
// AllowAnyOrigin 与 AllowCredentials 不兼容
policy.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
}
else
{
policy.WithOrigins(allowedOrigins);
}
policy.AllowAnyHeader()
policy.WithOrigins(allowedOrigins)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
}
});
});
@ -109,6 +151,14 @@ builder.Services.AddReverseProxy();
var app = builder.Build();
app.UseCors("AllowFrontend");
// 添加认证和授权中间件(必须在自定义中间件之前)
if (jwtConfig != null && !string.IsNullOrEmpty(jwtConfig.Authority))
{
app.UseAuthentication();
app.UseAuthorization();
}
app.UseMiddleware<JwtTransformMiddleware>();
app.UseMiddleware<TenantRoutingMiddleware>();