You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于MS Identity的MVC应用如何将Cookie认证代理至内部API?

优雅的下游认证会话代理方案

针对你的场景,有两种贴合.NET生态的优雅方案,无需手动解析Claims,且能完美兼容内部API的API Key安全机制:

方案一:基于JWT的Claims传递(标准轻量)

利用JWT将当前用户的Claims安全序列化后传递给内部API,API端验证JWT并还原用户身份,同时保留API Key作为基础安全层。

实现步骤:

  1. 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>();
      
  2. 内部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
          // 业务逻辑
      }
      

方案二:复用MS Identity加密Ticket(原生集成)

直接复用MVC中MS Identity生成的加密AuthenticationTicket,通过共享数据保护密钥在API端解密还原用户身份,完全贴合MS Identity的原生机制。

实现步骤:

  1. 共享数据保护密钥
    MVC和内部API必须使用相同的DataProtection配置,确保能互相解密Ticket:

    // MVC和API都添加此配置
    services.AddDataProtection()
            .PersistKeysToFileSystem(new DirectoryInfo(@"\\shared-server\dp-keys"))
            .SetApplicationName("YourAppFamily");
    
  2. 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);
        }
    }
    
  3. 内部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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 10:50:37