WCF SOA --- AJAX 跨域請求處理 CORS for WCF

1、問題

       跨域請求沒法處理的問題,因爲爲了阻止惡意的網站經過JS腳原本竊取正常網站受保護的資源。所由全部的瀏覽器的默認策略是阻止XmlHttpRequest的跨域的異步請求。 可是對於一個 複合型的應用集合來講,可能須要使用不一樣的域來部署咱們的應用。對於這種正常的需求,咱們的服務與應用就須要可以支持指定信認域的跨域的異步請法。web

      一般來講,咱們有三種替代方案跨域

      1, 使用JSONP,瀏覽器

            JSONP(JSON with Padding), 但因爲JSONP使用的是在HTML DOM中加入<script>標籤來包裹來對提供GET的服務請求數據,全部JSONP只對HTTP GET方式的請求取做用。咱們的SOA框架,大可能是使用POST的請求。cors

      2, 使用分離的「Proxy」代理服務,框架

            代理服務是指,在同一個請求的域下建立一個代理的服務, Ajax請求這個代理服務,由代理服務來路由這個請求到目標域的服務上。 若是咱們的全部的運用都必須使用HTML頁面來實現。 那這種方式,須要爲每個不一樣域的應用建立代理服務。 但請注意,若是咱們的應用是門戶網,請求可能來自外面的人, 對於性能需求高,那麼使用HTML是不可行的。咱們須要使用ASP.NET MVC結構, 那麼Controller層就如同咱們的代理服務層。dom

      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

2、解決步驟。

1, 應用, 對於應用層來講, 該怎麼樣寫,仍是怎麼樣寫,不須要變化。但對於底版本的瀏覽器的支持須要改代碼,不能使用XmlHttpRequest。須要使用XDomainRequest。spa

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;
}

結束。

相關文章
相關標籤/搜索