[C#]關於Web Service的cors,還有使用ajax來跟web service取得sessoin,用session跟web service做驗證(此篇使用axios來做示例)

[C#]關於Web Service的cors,還有使用ajax來跟web service取得sessoin,用session跟web service做驗證(此篇使用axios來做示例)

前言

因為目前筆者所在的team算是公司裡面比較主要的項目,所以很早期就發展出來了,隨著年代發展下來,整個系統變的非常龐大,整個系統充斥著很多非常老舊的技術,但是團隊又希望能帶進一些比較現代化的技術,大部份的東西都不太可能翻新,所以需要把新技術跟十年前的老技術做搭配,說實在的筆者並不懼怕學習任何新東西,但是新東西要跟老東西混合在一起,可能上網都很難找到soulation,只能自己猜測自己try,不只是要學習一堆新技術,還要學習一堆老技術,然後再做個整合,真的是有一點的痛苦,總而言之這篇就來紀錄一下心得筆記。

導覽

  1. 將web service打開cors吧
  2. 使用axios來跟web service做溝通
  3. chrome裝上cors的工具,來幫助本地開發
  4. 結論

將web service打開cors吧

其實在web api的方面,都有package可以方便我們打開cors,但是web service畢竟是非常老舊的技術了,所以我在nuget也沒有找到相關的package,如果有的話再請留言告知我,所以這邊的話我是自己動手做的,先來看一下如果我完全沒有打開cors的話,理論上就會出現「XMLHttpRequest cannot load http://localhost:8899/WebServices/SsoWebService.asmx/Login. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8080' is therefore not allowed access.」

這段訊息就是告知了我們現在要去訪問的網站,並未接受8080的client端呼叫,所以如果我們想要client直接呼叫的話,我們就先得處理Access-Control-Allow-Origin的問題,我們首先在web.config加上下面這段語法,來通過第一個問題吧

<system.webServer>                                                               
  <httpProtocol>                                                                 
    <customHeaders>                                                              
      <add name="Access-Control-Allow-Origin" value="http://localhost:8080" />   
    </customHeaders>                                                             
  </httpProtocol>  
</system.webServer>                                                                   

執行之後又發生錯誤,而且這時候提示的更不明確了,不過其實我們可以從網路通訊看出來,有呼叫option就死了,所以要enable http methods,原本我想要在web.config直接開啟,但還是死掉,所以我就在global.asax加入下面這段程式碼

 void Application_BeginRequest(Object source, EventArgs e)                                
 {                                                                                        
     HttpApplication app = (HttpApplication)source;                                       
     HttpContext context = app.Context;                                                   
     if (context.Request.HttpMethod == "OPTIONS")                                         
     {                                                                                    
         context.Response.AddHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS");  
         HttpContext.Current.Response.End();                                              
     }                                                                                    
 }                                                                                        

接著再執行登入之後,又發生了錯誤「XMLHttpRequest cannot load http://localhost:8899/WebServices/SsoWebService.asmx/Login. Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.」

其實這個問題是我們在丟ajax的時候,會有一個Content-Type的header,這個也必須允許才能通過,這個部份我們一樣在global.asax裡面設定,至於為何有大小寫的設定,是因為筆者曾經在IE遇過坑,IE是認小寫的,所以我就大小寫全加了

void Application_BeginRequest(Object source, EventArgs e)                                       
{                                                                                               
    HttpApplication app = (HttpApplication)source;                                              
    HttpContext context = app.Context;                                                          
    if (context.Request.HttpMethod == "OPTIONS")                                                
    {                                                                                           
        context.Response.AddHeader("Access-Control-Allow-Headers",@"Authorization,Content-Type, 
        authorization,content-Type");                                                           
        context.Response.AddHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS");         
        HttpContext.Current.Response.End();                                                     
    }                                                                                           
}                                                                                               

然後呼叫web service就能成功執行了,完成了cors的issus了

使用axios來跟web service做溝通

首先看一下我登入部份的程式碼吧

  const loginData = {
    userAccount: 'anson',
    password: '1234'
  }
  axios.post(`${config.url}SsoWebService.asmx/Login`, loginData)

登入都正常也沒問題了,接著來跟伺服器溝通一下,嘗試看看session有沒有正常

axios.get(`${config.url}ApplicationTimeWebService.asmx/GetServerDateTime`)

又發生錯誤了,回傳302要求我們redirect的狀態,我們可以看一下request的部份,怎麼會沒有送session去伺服器呢

因為我們想要開啟session的話,必須要再加上某個option的設定,把之前get的程式碼改成如下

axios.get(`${config.url}ApplicationTimeWebService.asmx/GetServerDateTime`, { withCredentials: true })

還是錯誤,我明明就有把cookie丟回去了,為何還是回傳給我302呢?在看一下主控台的部份,發現了另一個錯誤「XMLHttpRequest cannot load http://localhost:8899/WebServices/ApplicationTimeWebService.asmx/GetServerDateTime. Redirect from 'http://localhost:8899/WebServices/ApplicationTimeWebService.asmx/GetServerDateTime' to 'http://localhost:8899/Login.aspx?ReturnUrl=%2fWebServices%2fApplicationTimeWebService.asmx%2fGetServerDateTime' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'. Origin 'http://localhost:8080' is therefore not allowed access. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.」

其實最主要的重點是我們如果在ajax想要可以使用session的功能的話,需要把Access-Control-Allow-Credentials也允許,所以在web.config的部份,再加上允許Credentials吧

<system.webServer>                                                            
  <httpProtocol>                                                              
    <customHeaders>                                                           
      <add name="Access-Control-Allow-Origin" value="http://localhost:8080" />
      <add name="Access-Control-Allow-Credentials" value="true" />            
    </customHeaders>                                                          
  </httpProtocol>        
</system.webServer>                                                        

執行之後發現還是回傳302,有給cookie也都沒有任何錯誤了,那下一步到底該怎麼走啊,其實是因為之前登入的時候,因為我們沒有設定withCredentials,所以回傳的並沒有保存下來,所以我們首先把登入的部份改成如下

axios.post(`${config.url}SsoWebService.asmx/Login`, loginData, { withCredentials: true })

web service登入成功之前,也要加上這段程式碼,告知伺服器確認我們認證成功了才會回傳給我們對應的cookie資訊哦

FormsAuthentication.SetAuthCookie(userName, true);

最後就可以看到我們終於成功了,喔耶.......

登入

驗證某個api並取回資料


chrome裝上cors的工具,來幫助本地開發

假設我們要在本地方便去呼叫api的話,可以使用伺服器語言(C# etc..)做包裝,但是如果我們到時候並不想去打開cors,而是要用一些取巧的方式在佈署的時候來通過cors的話,這時候我們就可以使用這種方式了,這樣子我們就不用在程式的部份去手動開啟cors了,先看一下安裝的工具就是下面圖示這一個

安裝完之後應該可以看到我們的chrome多了一個圖示可以開啟,不過記得開發階段才打開,以免發生資安的問題啊

結論

這篇寫得比較囉嗦一點,其實主因是這種東西處理了一次,之後就會忘記之前處理的所有細節的經過,而且又是一個老技術了,以後更有可能回來看了這篇文章,也不清楚當初自己為何要這樣做,所以特別紀錄的清楚點,最後總結就是有web api就別在用web service了吧,要轉移應該不是很大的難事,不過如果沒有時間預算來做這件事情,或者還要跟session繼續打交道的話,那將就點用吧,如果有任何錯誤的話,再請多多指正筆者。