在强制执行Windows身份验证时,是否可以处理WCF服务的“跨源资源共享”请求?
我的场景:
-
我已经设置了一个通过webHttpBinding公开的自托管WCF服务。
-
该服务应该使用jQuery从浏览器直接调用。实际上,这将限制我使用basicHttpBinding或webHttpBinding。在这种情况下,我使用webHttpBinding来调用服务操作。
-
HTML页面(将调用WCF服务)由web服务器提供,该服务器位于同一台计算机上,但与WCF服务位于不同的端口上。这意味着我需要CORS支持才能在Firefox、Chrome、。。。
-
用户在调用WCF服务时必须使用Windows身份验证进行身份验证。为此,我已将我的webHttpBinding配置为使用传输安全模式“TransportCredentialsOnly”。
W3C规定在这种情况下应该使用CORS。
简单地说,这意味着浏览器将检测到我正在进行跨域请求。在实际向我的WCF服务发送请求之前,它会向我的WCF服务URL发送一个所谓的“preflight”请求。这个飞行前请求使用HTTP方法“OPTIONS”,并询问是否允许始发URL(=为我的HTML提供服务的Web服务器)将请求发送到我的服务URL。然后,在将实际请求发送到我的WCF服务之前,浏览器需要HTTP 200响应(=“OK”)。来自我的服务的任何其他回复都将阻止实际请求的发送。
CORS目前还没有内置到WCF中,所以我使用WCF扩展点来添加CORS兼容性。
我的自托管服务的App.Config的服务部分:
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="MyApp.DefaultServiceBehavior">
<serviceMetadata httpGetEnabled="True"/>
<serviceDebug includeExceptionDetailInFaults="True"/>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="MyApp.DefaultEndpointBehavior">
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<webHttpBinding>
<binding name="MyApp.DefaultWebHttpBinding">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows"/>
</security>
</binding>
</webHttpBinding>
</bindings>
<services>
<service
name="MyApp.FacadeLayer.LookupFacade"
behaviorConfiguration="MyApp.DefaultServiceBehavior"
>
<endpoint
contract="MyApp.Services.ILookupService"
binding="webHttpBinding"
bindingConfiguration="MyApp.DefaultWebHttpBinding"
address=""
behaviorConfiguration="MyApp.DefaultEndpointBehavior"
>
</endpoint>
<host>
<baseAddresses>
<add baseAddress="http://localhost/Temporary_Listen_Addresses/myapp/LookupService"/>
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
我已经实现了一个IDispatchMessageInspector,它可以回复飞行前的消息:
public class CORSSupport : IDispatchMessageInspector
{
private Dictionary<string, string> requiredHeaders;
public CORSSupport(Dictionary<string, string> requiredHeaders)
{
this.requiredHeaders = requiredHeaders ?? new Dictionary<string, string>();
}
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
HttpRequestMessageProperty httpRequest = request.Properties["httpRequest"] as HttpRequestMessageProperty;
if (httpRequest.Method.ToUpper() == "OPTIONS")
instanceContext.Abort();
return httpRequest;
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
HttpRequestMessageProperty httpRequest = correlationState as HttpRequestMessageProperty;
HttpResponseMessageProperty httpResponse = reply.Properties["httpResponse"] as HttpResponseMessageProperty;
foreach (KeyValuePair<string, string> item in this.requiredHeaders)
httpResponse.Headers.Add(item.Key, item.Value);
string origin = httpRequest.Headers["origin"];
if (origin != null)
httpResponse.Headers.Add("Access-Control-Allow-Origin", origin);
if (httpRequest.Method.ToUpper() == "OPTIONS")
httpResponse.StatusCode = HttpStatusCode.NoContent;
}
}
此IDispatchMessageInspector是通过自定义IServiceBehavior属性注册的。
我通过jQuery调用我的服务,如下所示:
$.ajax(
{
url: 'http://localhost/Temporary_Listen_Addresses/myapp/LookupService/SomeLookup',
type: 'GET',
xhrFields:
{
withCredentials: true
}
}
)
.done(function () { alert('Yay!'); })
.error(function () { alert('Nay!'); });
这在IE10和Chrome中有效(我收到一个消息框,上面写着“耶!”),但在Firefox中无效。在Firefox中,我得到一个“不!”和一个HTTP 401(未经授权)错误。
这个401是由于我在服务配置中设置了“Windows身份验证”。身份验证的工作方式是浏览器首先发送一个没有任何身份验证信息的请求。然后,服务器以HTTP 401(未经授权)进行回复,该HTTP 401指示要使用的认证方法。然后,浏览器通常会重新提交包括用户凭据在内的请求(之后,请求将正常进行)。
不幸的是,W3C似乎已经表示证书不应该传递到CORS飞行前消息中。因此,WCF以HTTP 401进行回复。Chrome似乎确实以某种方式发送了飞行前请求标头中的凭据(根据W3C规范,这实际上是不正确的),而Firefox则没有。
此外,W3C只识别对飞行前请求的HTTP 200响应:任何其他响应(如我收到的HTTP 401)都意味着CORS请求失败,实际请求可能无法提交。。。
我不知道如何让这个(简单的)场景发挥作用。有人能帮忙吗?