WCF路由转发Windows凭据至Negotiate认证服务的配置问题
问题背景
现有一个采用Transport安全+Negotiate认证(Windows凭据)的WCF服务,客户端通过模拟远程服务认可的凭据访问。需在客户端与服务间加入WCF路由,遇到以下凭据传递问题:
- 若路由绑定配置为
AuthenticationSchemes.Negotiate,路由无法识别客户端凭据导致认证失败; - 改为
AuthenticationSchemes.Anonymous后,客户端可连接路由,但路由无可用凭据模拟客户端访问服务,触发错误:
The contract operation 'ProcessRequest' requires Windows identity for automatic impersonation. A Windows identity that represents the caller is not provided by binding ('CustomBinding','http://tempuri.org/') for contract ('IRequestReplyRouter','http://schemas.microsoft.com/netfx/2009/05/routing'
安全限制下无法硬编码凭据,理想流程:客户端(携带Windows凭据)→匿名连接路由→路由转发凭据并模拟客户端访问服务。
当前配置代码
路由服务配置
ServiceHost serviceHost = new ServiceHost(typeof(RoutingService), new Uri[] { new Uri($"https://{routerHost}/routingservice/router") }); CustomBinding routerBinding = new CustomBinding(); routerBinding.Elements.Add(new ReliableSessionBindingElement { InactivityTimeout = TimeSpan.FromMinutes(22) }); routerBinding.Elements.Add(new HttpsTransportBindingElement { AuthenticationScheme = AuthenticationSchemes.Anonymous, MaxReceivedMessageSize = 100000000, MaxBufferPoolSize = 40000000, }); ServiceEndpoint endpoint = serviceHost.AddServiceEndpoint(typeof(IRequestReplyRouter), routerBinding, string.Empty); endpoint.Behaviors.Add(new MessageLogger("Router"));
路由转发的客户端端点配置
CustomBinding remoteServiceBinding = new CustomBinding(); remoteServiceBinding.Elements.Add( new ReliableSessionBindingElement { MaxRetryCount = 15, Ordered = true, InactivityTimeout = TimeSpan.FromMinutes(22) }); remoteServiceBinding.Elements.Add( new HttpsTransportBindingElement { AuthenticationScheme = AuthenticationSchemes.Negotiate, MaxReceivedMessageSize = 100000000, MaxBufferPoolSize = 40000000, }); EndpointAddress clientAddress = new EndpointAddress("https://<remoteservice url>"); ContractDescription contract = ContractDescription.GetContract(typeof(IRequestReplyRouter)); ServiceEndpoint client = new ServiceEndpoint(contract, remoteServiceBinding, clientAddress); List<ServiceEndpoint> endpointList = new List<ServiceEndpoint>() { client }; RoutingConfiguration rc = new RoutingConfiguration(){ RouteOnHeadersOnly = true }; rc.FilterTable.Add(new MatchAllMessageFilter(), endpointList); //attach the behavior to the service host serviceHost.Description.Behaviors.Add(new RoutingBehavior(rc)); serviceHost.Description.Behaviors.Add(new ServiceMetadataBehavior() { HttpsGetEnabled = true }); var serviceAuthorizationBehavior = serviceHost.Description.Behaviors.Find<ServiceAuthorizationBehavior>(); if (serviceAuthorizationBehavior != null) { serviceAuthorizationBehavior.ImpersonateCallerForAllOperations = true; }
客户端配置片段
NetworkCredential remoteCred = <remote machine domain, username, password> this.ClientCredentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation; this.ClientCredentials.Windows.ClientCredential = remoteCred;
已尝试的无效方法
- 在
BeforeSendRequest阶段使用消息检查器注入安全上下文/头 - 为路由配置其他绑定以在消息中传递客户端凭据
- 自定义ServiceAuthorization管理器
- 通过
AddBindingParameters传递凭据(自定义端点行为) - 操作
OperationContextScope和ServiceSecurityContext.Current
解决方案
要实现凭据的转发,核心是让路由在匿名接收客户端请求后,获取客户端的Windows凭据并在转发时使用该凭据模拟访问后端服务。以下是关键调整步骤:
1. 修改路由服务的绑定,启用消息级凭据传递
路由服务当前使用匿名Transport绑定,无法获取客户端Windows身份。需要在绑定中添加Windows消息安全元素,让客户端在消息层传递凭据,同时保持Transport层匿名:
CustomBinding routerBinding = new CustomBinding(); // 添加可靠会话 routerBinding.Elements.Add(new ReliableSessionBindingElement { InactivityTimeout = TimeSpan.FromMinutes(22) }); // 添加Windows消息安全,用于接收客户端的Windows凭据 routerBinding.Elements.Add(new WindowsMessageSecurityBindingElement { // 设置为消息层客户端认证,Transport层匿名 ClientCredentialType = MessageCredentialType.Windows, NegotiateServiceCredential = true }); // 保持Transport层匿名 routerBinding.Elements.Add(new HttpsTransportBindingElement { AuthenticationScheme = AuthenticationSchemes.Anonymous, MaxReceivedMessageSize = 100000000, MaxBufferPoolSize = 40000000, });
2. 配置路由的服务授权,允许获取客户端身份并模拟
确保路由服务的ServiceAuthorizationBehavior不仅开启模拟,还要设置PrincipalPermissionMode为UseWindowsGroups以正确识别客户端Windows身份:
var serviceAuthorizationBehavior = serviceHost.Description.Behaviors.Find<ServiceAuthorizationBehavior>(); if (serviceAuthorizationBehavior == null) { serviceAuthorizationBehavior = new ServiceAuthorizationBehavior(); serviceHost.Description.Behaviors.Add(serviceAuthorizationBehavior); } serviceAuthorizationBehavior.ImpersonateCallerForAllOperations = true; serviceAuthorizationBehavior.PrincipalPermissionMode = PrincipalPermissionMode.UseWindowsGroups;
3. 为路由转发的客户端端点添加凭据获取行为
创建一个自定义端点行为,在转发请求时从当前操作上下文获取客户端的Windows身份,并将其设置为转发请求的凭据:
public class ForwardClientCredentialBehavior : IEndpointBehavior { public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { // 注册客户端凭据提供程序 bindingParameters.Add(new ClientCredentials()); } public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { // 添加消息检查器,在发送前设置凭据 clientRuntime.MessageInspectors.Add(new ForwardCredentialMessageInspector()); } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { } public void Validate(ServiceEndpoint endpoint) { } } public class ForwardCredentialMessageInspector : IClientMessageInspector { public void AfterReceiveReply(ref Message reply, object correlationState) { } public object BeforeSendRequest(ref Message request, IClientChannel channel) { // 获取当前路由操作上下文的客户端Windows身份 var callerIdentity = ServiceSecurityContext.Current.WindowsIdentity; if (callerIdentity != null && callerIdentity.IsAuthenticated) { // 获取客户端通道的凭据,设置为调用方的身份 var clientCredentials = channel.GetProperty<ClientCredentials>(); if (clientCredentials != null) { // 使用调用方的模拟令牌创建凭据 clientCredentials.Windows.ClientCredential = new NetworkCredential( callerIdentity.Name.Split('\\')[1], string.Empty, callerIdentity.Name.Split('\\')[0]); // 启用模拟级别 clientCredentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation; } } return null; } }
然后将该行为添加到路由转发的客户端端点:
ServiceEndpoint client = new ServiceEndpoint(contract, remoteServiceBinding, clientAddress); // 添加自定义凭据转发行为 client.Behaviors.Add(new ForwardClientCredentialBehavior());
4. 调整客户端配置,确保消息层传递凭据
客户端需要配置为使用消息层Windows认证,同时保持Transport层的HTTPS安全:
// 修改客户端绑定,添加消息安全元素 CustomBinding clientBinding = new CustomBinding(); clientBinding.Elements.Add(new ReliableSessionBindingElement { InactivityTimeout = TimeSpan.FromMinutes(22) }); clientBinding.Elements.Add(new WindowsMessageSecurityBindingElement { ClientCredentialType = MessageCredentialType.Windows, NegotiateServiceCredential = true }); clientBinding.Elements.Add(new HttpsTransportBindingElement { AuthenticationScheme = AuthenticationSchemes.Anonymous, MaxReceivedMessageSize = 100000000, MaxBufferPoolSize = 40000000, }); // 设置客户端凭据 this.Endpoint.Binding = clientBinding; NetworkCredential remoteCred = <remote machine domain, username, password>; this.ClientCredentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation; this.ClientCredentials.Windows.ClientCredential = remoteCred;
关键原理说明
- 通过消息层Windows安全,客户端可以在匿名Transport连接的同时,将Windows凭据嵌入消息中传递给路由;
- 路由服务通过
ServiceSecurityContext获取客户端的Windows身份,再通过自定义行为将该身份注入到转发请求的客户端凭据中; - 开启路由的模拟设置,确保转发请求时使用客户端的身份访问后端服务。
内容的提问来源于stack exchange,提问作者Ingmar H.

