基于MS Identity的MVC应用如何将Cookie认证代理至内部API?
优雅的下游认证会话代理方案
针对你的场景,有两种贴合.NET生态的优雅方案,无需手动解析Claims,且能完美兼容内部API的API Key安全机制:
方案一:基于JWT的Claims传递(标准轻量)
利用JWT将当前用户的Claims安全序列化后传递给内部API,API端验证JWT并还原用户身份,同时保留API Key作为基础安全层。
实现步骤:
MVC应用配置
- 注册对称加密的JWT生成服务(内部服务用对称加密足够):
services.AddSingleton<IJwtGenerator>(sp => { var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your-shared-secret-key")); return new JwtGenerator(key); }); - 创建自定义
DelegatingHandler,自动为内部API请求添加JWT头:public class UserClaimsDelegatingHandler : DelegatingHandler { private readonly IHttpContextAccessor _httpContextAccessor; private readonly IJwtGenerator _jwtGenerator; public UserClaimsDelegatingHandler(IHttpContextAccessor httpContextAccessor, IJwtGenerator jwtGenerator) { _httpContextAccessor = httpContextAccessor; _jwtGenerator = jwtGenerator; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var user = _httpContextAccessor.HttpContext?.User; if (user?.Identity?.IsAuthenticated == true) { var token = _jwtGenerator.GenerateToken(user); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); } // 同时添加API Key request.Headers.Add("X-Api-Key", "your-internal-api-key"); return await base.SendAsync(request, cancellationToken); } } - 注册HttpClient时绑定该Handler:
services.AddHttpClient("InternalApiClient") .AddHttpMessageHandler<UserClaimsDelegatingHandler>();
- 注册对称加密的JWT生成服务(内部服务用对称加密足够):
内部API配置
- 配置双认证方案(API Key + JWT),并设置JWT认证为可选:
services.AddAuthentication() .AddApiKeyAuth(options => { }) // 你的现有API Key认证方案 .AddJwtBearer("UserJwt", options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your-shared-secret-key")), RequireSignedTokens = true }; // 忽略JWT认证失败,继续用API Key处理请求 options.Events = new JwtBearerEvents { OnAuthenticationFailed = context => { context.SkipToNextMiddleware(); return Task.CompletedTask; } }; }); - 在需要用户身份的接口上指定授权方案:
[Authorize(AuthenticationSchemes = "ApiKey,UserJwt")] public IActionResult SomeAction() { var user = User; // 已还原ClaimsPrincipal // 业务逻辑 }
- 配置双认证方案(API Key + JWT),并设置JWT认证为可选:
方案二:复用MS Identity加密Ticket(原生集成)
直接复用MVC中MS Identity生成的加密AuthenticationTicket,通过共享数据保护密钥在API端解密还原用户身份,完全贴合MS Identity的原生机制。
实现步骤:
共享数据保护密钥
MVC和内部API必须使用相同的DataProtection配置,确保能互相解密Ticket:// MVC和API都添加此配置 services.AddDataProtection() .PersistKeysToFileSystem(new DirectoryInfo(@"\\shared-server\dp-keys")) .SetApplicationName("YourAppFamily");MVC应用的DelegatingHandler
获取当前用户的AuthenticationTicket,序列化加密后传递:public class TicketDelegatingHandler : DelegatingHandler { private readonly IHttpContextAccessor _httpContextAccessor; private readonly IDataProtector _dataProtector; public TicketDelegatingHandler(IHttpContextAccessor httpContextAccessor, IDataProtectionProvider dataProtectionProvider) { _httpContextAccessor = httpContextAccessor; _dataProtector = dataProtectionProvider.CreateProtector("UserTicketTransfer"); } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var authResult = await _httpContextAccessor.HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme); if (authResult.Succeeded) { var ticketSerializer = new TicketSerializer(); var ticketBytes = ticketSerializer.Serialize(authResult.Ticket); var protectedTicket = _dataProtector.Protect(ticketBytes); request.Headers.Add("X-User-Ticket", Convert.ToBase64String(protectedTicket)); } request.Headers.Add("X-Api-Key", "your-internal-api-key"); return await base.SendAsync(request, cancellationToken); } }内部API的自定义认证Handler
读取X-User-Ticket头,解密还原Ticket:public class TicketAuthenticationHandler : AuthenticationHandler<TicketAuthenticationOptions> { private readonly IDataProtector _dataProtector; public TicketAuthenticationHandler(IOptionsMonitor<TicketAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IDataProtectionProvider dataProtectionProvider) : base(options, logger, encoder, clock) { _dataProtector = dataProtectionProvider.CreateProtector("UserTicketTransfer"); } protected override async Task<AuthenticateResult> HandleAuthenticateAsync() { if (!Request.Headers.TryGetValue("X-User-Ticket", out var ticketHeader)) { return AuthenticateResult.NoResult(); } try { var ticketBytes = Convert.FromBase64String(ticketHeader); var unprotectedBytes = _dataProtector.Unprotect(ticketBytes); var ticketSerializer = new TicketSerializer(); var ticket = ticketSerializer.Deserialize(unprotectedBytes); var principal = ticket.Principal; var ticketAuthResult = new AuthenticationTicket(principal, Scheme.Name); return AuthenticateResult.Success(ticketAuthResult); } catch { return AuthenticateResult.NoResult(); } } } // 注册自定义认证方案 services.AddAuthentication() .AddApiKeyAuth(options => { }) .AddScheme<TicketAuthenticationOptions, TicketAuthenticationHandler>("UserTicket", options => { });
方案选择建议
- 若内部API未来可能支持跨语言调用,优先选方案一(JWT),标准性更好;
- 若希望完全基于MS Identity原生机制,减少额外配置,选方案二(Ticket复用),集成度更高。
两种方案都无需手动解析Claims,且能保证API在无用户登录时仍可通过API Key正常处理业务。
内容的提问来源于stack exchange,提问作者Rok
相关产品推荐
相关产品推荐

