WCF SOA --- AJAX 跨域请求处理 CORS for WCF

一、问题

       跨域请求无法处理的问题,由于为了阻止恶意的网站通过JS脚本来窃取正常网站受保护的资源。所由所有的浏览器的默认策略是阻止XmlHttpRequest的跨域的异步请求。 但是对于一个 复合型的应用集合来说,可能需要使用不同的域来部署我们的应用。对于这种正常的需求,我们的服务与应用就需要能够支持指定信认域的跨域的异步请法。

      通常来说,我们有三种替代方案

      1, 使用JSONP,

            JSONP(JSON with Padding), 但由于JSONP使用的是在HTML DOM中加入<script>标签来包裹来对提供GET的服务请求数据,所有JSONP只对HTTP GET方式的请求取作用。我们的SOA框架,大多是使用POST的请求。

      2, 使用分离的“Proxy”代理服务,

            代理服务是指,在同一个请求的域下创建一个代理的服务, Ajax请求这个代理服务,由代理服务来路由这个请求到目标域的服务上。 如果我们的所有的运用都必须使用HTML页面来实现。 那这种方式,需要为每一个不同域的应用创建代理服务。 但请注意,如果我们的应用是门户网,请求可能来自外面的人, 对于性能需求高,那么使用HTML是不可行的。我们需要使用ASP.NET MVC结构, 那么Controller层就如同我们的代理服务层。

      3, 使用CORS

            CORS,Cross-Origin Resource Sharing, 这是一种新的对跨域支持的方法, 它是通过定义一系列的HTTP Headers 在Service 与Client, 服务端可以去掉对于跨域的约束,不仅可以使用GET,还可以使用POST, PUT,或是DELETE,  XmlHttpRequest对象已经实现了CORS, 将它作为正常的AJAX调用。但是目前只有最新版本的浏览器才支持, 对于Firefox 3.5以下,Safari 4以下,Chrome 3以下, IE 10 以下,可以使用XDomainRequest对象来代替XmlHttpRequest对象来实现。

           浏览器对于使用CORS的请求,会分成次请求,第一次,预先授权请求,会将请求的方式变成OPTIONS, 请求的数据为空,这个过程实际上是在看当前的Origin是否被服务允许,如果允许只服务返回200OK, 否则返回405 Method NOT Allowed, 第二次,正常请求,浏览器将请求的数据POST发送服务,服务端会正常响应。 注意下面是Chrome的,IE的话,只会显示一条。

image2015-1-28 16-56-35

二、解决步骤。

1, 应用, 对于应用层来说, 该怎么样写,还是怎么样写,不需要变化。但对于底版本的浏览器的支持需要改代码,不能使用XmlHttpRequest。需要使用XDomainRequest。

2, 服务, 这里我们使用的是WCF, 对于使用ASP.NET Web API的话,使用参考:https://msdn.microsoft.com/en-us/magazine/dn532203.aspx, 或是Google, CORS in ASP.NET Web Api.

对于WCF,我参考了 CarlosFigueira写的 http://blogs.msdn.com/b/carlosfigueira/archive/2012/05/15/implementing-cors-support-in-wcf.aspx ,文章写的非常不错, 但是我发现,其它它是一个半成品,没有实现玩,它的目的是创建一个新的Operation,只是支持OPTIONS方式的请求,Action的名称是当前操作的名称 +特定的后缀。 但这样问题来了,客户端浏览器的请求,每一次不会自动加上这个后缀,所有Invoke一直都不会被调用。 大家的兴趣下载了使用的话,如果想结合到自己的项目中, 可要记得把Web.config中的<modules runAllManagedModulesForAllRequests="true">去掉。 当然,前提是,你使用的Service都是自己通过ServiceHostFactory创建出来的, 如果使用默认的,是不会支持的。 其它 CarlosFigueira想法。 我做了一些改变。 实现步骤如下:

1,在IIS中,添加Http  Response Header 3个,如下

image2015-1-28 17-15-25

建议这个设置在WebSite上, 这样你的服务在这个Website下作为一个Web Application的话,将自动继承这些配置。 写在web.config,可以方便控制允许访问的域。当前我使用 *, 这样所有的域都能访问,如果想指定哪些域的话,可以改成如 Http://domain1.com,Http://domain2......。那么只有来自这些域的请求才能处理。当然这个设置,或以在WCF,添加自己的IDispatchMessageInspector实现,  在BeforeSendReply事件中,添加响应头。

2,在使用自定ServiceHostFactory,创建服务时, 我们可以指定IServiceBehavior, 在它的ApplyDispatchBehavior事件中。 找到所有的Operation,对它注入WebInvokeAttribute, Method设置成*,  目的是动态将Operation对 Method=OPTIONS的请求处理, 如下代码

foreach (ServiceEndpoint endpoint in desc.Endpoints)
{
foreach (var operation in endpoint.Contract.Operations)
{
//Add WebInvoke Attribute to all operation except for WebGet only, so that our opeations are able to handle OPTIONS Http Request Method for CORS issue.
if ( !operation.Behaviors.Any(d => d is WebGetAttribute)
&& !operation.Behaviors.Any(d => d is WebInvokeAttribute))
{
WebInvokeAttribute wia = new WebInvokeAttribute();
wia.UriTemplate = operation.Name;
wia.Method = "*";
operation.Behaviors.Add(wia);
}
}
}

3,当OPTIONS方式的请求来到时,在自定的 IOperationInvoker的invoke事件中, 判断当前的Http Method是否为OPTIONS, 如果是的话,不处理正常逻辑, 返回一个200 OK的响应。代码如下

public object Invoke(object instance, object[] inputs, out object[] outputs)
{

string operationName = "";
if (OperationContext.Current.IncomingMessageHeaders.Action != null)
{
operationName = OperationContext.Current.IncomingMessageHeaders.Action.ToString();
}
if (OperationContext.Current.IncomingMessageProperties.Keys.Contains("HttpOperationName"))
{
operationName = OperationContext.Current.IncomingMessageProperties["HttpOperationName"].ToString();
}

HttpRequestMessageProperty request = System.ServiceModel.OperationContext.Current.IncomingMessageProperties["httpRequest"] as HttpRequestMessageProperty;
//If enable CORS, then we need to handle OPTIONS Method to reply OK, so that the browser will seed the right POST request.
if (request != null
&& request.Method == "OPTIONS" )
{
System.ServiceModel.Channels.Message input = (System.ServiceModel.Channels.Message)inputs[0];
outputs = null;
return HandlePreflight(input, operationName);
}

else

{

正常处理。。。。

}

}

System.ServiceModel.Channels.Message HandlePreflight(System.ServiceModel.Channels.Message input, string operationName)
{
System.ServiceModel.Channels.Message reply = System.ServiceModel.Channels.Message.CreateMessage(MessageVersion.None, operationName);
HttpResponseMessageProperty httpResponse = new HttpResponseMessageProperty();
reply.Properties.Add(HttpResponseMessageProperty.Name, httpResponse);

httpResponse.SuppressEntityBody = true;
httpResponse.StatusCode = System.Net.HttpStatusCode.OK;
httpResponse.Headers.Add(CorsConstants.AccessControlAllowOrigin, "*");
httpResponse.Headers.Add(CorsConstants.AccessControlAllowMethods, string.Join(",", new List<string>() { "POST", "GET", "OPTIONS" }));

return reply;
}

结束。

posted on 2015-01-28 17:31  Stephen Ding  阅读(1958)  评论(0编辑  收藏  举报