Files
gitea-mcp/Auth/JwtBearerSetup.cs
T
zhengchen.tao 0f07300cec
Build Docker Image / build (push) Has been cancelled
Build Docker Image / deploy (push) Has been cancelled
refactor: unify JwtOptions schema with obsidian-mcp + simplify deploy
- Config/JwtOptions: flatten SigningKeyCurrent/Previous into nested
  SigningKey { Current, Previous } class to match obsidian-mcp shape.
  Both services now bind the same env var pattern (Jwt__SigningKey__Current),
  removing the schema fork that caused gitea-mcp to start with empty keys
  when compose used the obsidian-mcp convention.
- Auth/JwtBearerSetup, appsettings.json, README: follow rename.
- .gitea/workflows/build-image.yml: deploy job no longer clones nas-infra
  to a temp dir (which lacks the gitignored .env.shared). Now cd directly
  into /volume1/docker/compose/gitea-mcp, exposed by gitea-runner mount.
2026-05-16 17:24:09 +08:00

69 lines
2.8 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 GiteaMcp.Config;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
namespace GiteaMcp.Auth;
/// <summary>
/// JWT Bearer 验签配置,与 obsidian-mcp 同款 HS256 对称密钥方案。
/// ValidIssuer = https://auth.zhengchentao.winValidAudience = gitea。
/// 支持 Current + Previous 双密钥(轮换窗口)。
/// </summary>
public static class JwtBearerSetup
{
public static IServiceCollection AddGiteaJwtBearer(
this IServiceCollection services,
IConfiguration configuration)
{
var jwtOpts = configuration.GetSection(JwtOptions.SectionName).Get<JwtOptions>()
?? new JwtOptions();
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
// 关闭默认的入站 claim type 映射,否则 "sub"/"scope" 会被改写成
// ClaimTypes.NameIdentifier 之类的长 URI,下游 FindFirst("scope") 取不到。
options.MapInboundClaims = false;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = jwtOpts.Issuer,
ValidateAudience = true,
ValidAudience = jwtOpts.Audience,
ValidateIssuerSigningKey = true,
// ToList 物化一次,避免每次验签重建 SymmetricSecurityKey。
IssuerSigningKeys = BuildSigningKeys(jwtOpts).ToList(),
ValidateLifetime = true,
// nas-auth 设计里允许 2 分钟时钟偏差
ClockSkew = TimeSpan.FromMinutes(2),
};
});
return services;
}
/// <summary>
/// 构建 Current + Previous 双密钥列表,供 SDK 依次尝试验签。
/// 轮换期间两把钥匙同时有效,过期的旧 Token 会被 ValidateLifetime 自然拦截。
/// Current 未配置直接抛错,避免容器静默以"任何 token 都不通过"的状态运行。
/// </summary>
private static IEnumerable<SecurityKey> BuildSigningKeys(JwtOptions opts)
{
if (string.IsNullOrWhiteSpace(opts.SigningKey.Current))
throw new InvalidOperationException(
"Jwt:SigningKey:Current 未配置,gitea-mcp 无法启动。" +
"请在 .env.shared 设置 JWT_SIGNING_KEY_CURRENT 与 nas-auth 共用。");
yield return new SymmetricSecurityKey(Encoding.UTF8.GetBytes(opts.SigningKey.Current));
if (!string.IsNullOrWhiteSpace(opts.SigningKey.Previous))
yield return new SymmetricSecurityKey(Encoding.UTF8.GetBytes(opts.SigningKey.Previous));
}
}