ASP.NET Core中间件Windows认证问题:访问/ecp路径持续重定向至登录页
ASP.NET Core代理中间件无法传递Windows凭据导致/ecp路径持续重定向至登录页
问题背景
- 部署环境:IIS托管的ASP.NET Core应用,启用Windows认证,禁用Forms认证
- 核心现象:自定义代理中间件转发/ecp路径请求到下游服务器时,持续重定向至登录页
- 已尝试操作:设置
HttpClientHandler.UseDefaultCredentials = true以传递Windows凭据,检查请求头未发现异常
中间件代码
using System.Net.Http.Headers; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; namespace ECP.Web.Configurations.Middleware { public static class Routing { public static void Apply(WebApplication app) { app.UseRouting(); app.UseMiddleware<SimpleProxyMiddleware>(); // Routing dla CommonController app.MapControllerRoute( name: "ecpRoute", pattern: "ecp/{action=Index}", defaults: new { controller = "Common", action = "Index" }); app.MapControllerRoute( name: "default", pattern: "{controller=Common}/{action=Index}"); app.MapRazorPages(); } } public class SimpleProxyMiddleware { private readonly RequestDelegate _next; private readonly HttpClient _httpClient; private readonly ILogger<SimpleProxyMiddleware> _logger; public SimpleProxyMiddleware(RequestDelegate next, ILogger<SimpleProxyMiddleware> logger) { _next = next; var handler = new HttpClientHandler { UseDefaultCredentials = true, // This setting passes Windows Authentication credentials AllowAutoRedirect = false }; _httpClient = new HttpClient(handler); _logger = logger; } public async Task InvokeAsync(HttpContext context) { var upstreamPath = context.Request.Path.Value; // Check if the request has already been processed by this middleware if (context.Request.Headers.ContainsKey("X-Proxy-Processed")) { _logger.LogInformation("Request already processed by proxy, passing to next middleware."); await _next(context); return; } if (upstreamPath.StartsWith("/ecp")) { // Modify the URL to point to the downstream service var downstreamUrl = $"https://ecp.dev.ad.ulcp:44392{upstreamPath}"; _logger.LogInformation("Requesting URL: {DownstreamUrl}", downstreamUrl); var requestMessage = new HttpRequestMessage { Method = new HttpMethod(context.Request.Method), RequestUri = new Uri(downstreamUrl) }; // Mark the request as processed by this proxy requestMessage.Headers.Add("X-Proxy-Processed", "true"); // Copy request headers, including authentication information _logger.LogInformation("Request Headers:"); foreach (var header in context.Request.Headers) { _logger.LogInformation("{HeaderKey}: {HeaderValue}", header.Key, string.Join(", ", header.Value)); requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()); } try { // Send the request to the downstream service var responseMessage = await _httpClient.SendAsync(requestMessage); context.Response.StatusCode = (int)responseMessage.StatusCode; _logger.LogInformation("Response Status Code: {StatusCode}", responseMessage.StatusCode); // Copy response headers _logger.LogInformation("Response Headers:"); foreach (var header in responseMessage.Headers) { _logger.LogInformation("{HeaderKey}: {HeaderValue}", header.Key, string.Join(", ", header.Value)); context.Response.Headers[header.Key] = header.Value.ToArray(); } foreach (var header in responseMessage.Content.Headers) { _logger.LogInformation("{HeaderKey}: {HeaderValue}", header.Key, string.Join(", ", header.Value)); context.Response.Headers[header.Key] = header.Value.ToArray(); } context.Response.Headers.Remove("transfer-encoding"); // Forward the response content await responseMessage.Content.CopyToAsync(context.Response.Body); } catch (Exception ex) { _logger.LogError(ex, "An error occurred while processing the request."); throw; } } else { await _next(context); } } } }
IIS配置
- 已启用Windows认证
- 已禁用Forms认证
Ocelot配置(备用代理方案)
{ "DownstreamPathTemplate": "/ecp/{url}", "DownstreamScheme": "https", "DownstreamHostAndPorts": [ { "Host": "ecp.dev.ad.uclp", "Port": 443 } ], "UpstreamPathTemplate": "/ecp/{url}", "UpstreamHttpMethod": [ "Get" ] }
问题分析与解决方案
核心原因
- 中间件顺序错误:代理中间件在
UseRouting之后执行,而路由规则中已定义ecpRoute,导致/ecp请求先被路由到CommonController,未进入代理中间件处理。 - 凭据传递逻辑错误:
UseDefaultCredentials = true传递的是应用池身份的凭据,而非当前登录用户的Windows凭据,下游服务器因身份不匹配返回登录重定向。 - 重定向响应未处理:下游返回的302重定向直接被转发,重定向目标为下游登录页,且未保留原用户认证上下文,导致循环重定向。
解决方案
1. 调整中间件顺序
将代理中间件移至UseRouting之前,确保/ecp请求先被代理捕获:
public static void Apply(WebApplication app) { // 优先执行代理中间件 app.UseMiddleware<SimpleProxyMiddleware>(); app.UseRouting(); // 后续路由配置保持不变 app.MapControllerRoute( name: "ecpRoute", pattern: "ecp/{action=Index}", defaults: new { controller = "Common", action = "Index" }); app.MapControllerRoute( name: "default", pattern: "{controller=Common}/{action=Index}"); app.MapRazorPages(); }
2. 正确传递用户Windows凭据(Kerberos委派)
若需转发当前用户的Windows凭据,需配置Kerberos约束委派:
- 在AD中为应用池的服务账户配置对下游服务器服务的约束委派权限
- 在代理中间件中临时 impersonate 当前用户身份发送请求:
// 在InvokeAsync方法内获取当前用户Windows身份 var windowsIdentity = context.User.Identity as WindowsIdentity; if (windowsIdentity != null) { using (windowsIdentity.Impersonate()) { var handler = new HttpClientHandler { UseDefaultCredentials = true, AllowAutoRedirect = false }; // 使用临时HttpClient在 impersonation 上下文发送请求 using (var client = new HttpClient(handler)) { var responseMessage = await client.SendAsync(requestMessage); // 后续响应处理逻辑... } } }
注意:不要在中间件构造函数中初始化HttpClient,需在InvokeAsync内根据当前用户身份创建,避免凭据复用冲突。
3. 处理重定向响应
当下游返回302重定向时,修改重定向目标为本应用路径:
if (responseMessage.StatusCode == HttpStatusCode.Redirect || responseMessage.StatusCode == HttpStatusCode.Found) { var redirectUrl = responseMessage.Headers.Location.ToString(); // 替换下游域名为本应用域名 var newRedirectUrl = redirectUrl.Replace("https://ecp.dev.ad.ulcp:44392", $"{context.Request.Scheme}://{context.Request.Host}"); context.Response.Redirect(newRedirectUrl); return; }
4. 清理冲突请求头
转发请求时移除可能导致认证问题的头信息:
// 复制请求头前跳过冲突字段 foreach (var header in context.Request.Headers) { if (header.Key.Equals("Connection", StringComparison.OrdinalIgnoreCase) || header.Key.Equals("Host", StringComparison.OrdinalIgnoreCase)) { continue; } requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()); }
内容的提问来源于stack exchange,提问作者Michael1834
相关产品推荐
相关产品推荐

