[C#]關於Web Service的cors,還有使用ajax來跟web service取得sessoin,用session跟web service做驗證(此篇使用axios來做示例)
前言
因為目前筆者所在的team算是公司裡面比較主要的項目,所以很早期就發展出來了,隨著年代發展下來,整個系統變的非常龐大,整個系統充斥著很多非常老舊的技術,但是團隊又希望能帶進一些比較現代化的技術,大部份的東西都不太可能翻新,所以需要把新技術跟十年前的老技術做搭配,說實在的筆者並不懼怕學習任何新東西,但是新東西要跟老東西混合在一起,可能上網都很難找到soulation,只能自己猜測自己try,不只是要學習一堆新技術,還要學習一堆老技術,然後再做個整合,真的是有一點的痛苦,總而言之這篇就來紀錄一下心得筆記。
導覽
其實在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了
首先看一下我登入部份的程式碼吧
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並取回資料
假設我們要在本地方便去呼叫api的話,可以使用伺服器語言(C# etc..)做包裝,但是如果我們到時候並不想去打開cors,而是要用一些取巧的方式在佈署的時候來通過cors的話,這時候我們就可以使用這種方式了,這樣子我們就不用在程式的部份去手動開啟cors了,先看一下安裝的工具就是下面圖示這一個
安裝完之後應該可以看到我們的chrome多了一個圖示可以開啟,不過記得開發階段才打開,以免發生資安的問題啊
這篇寫得比較囉嗦一點,其實主因是這種東西處理了一次,之後就會忘記之前處理的所有細節的經過,而且又是一個老技術了,以後更有可能回來看了這篇文章,也不清楚當初自己為何要這樣做,所以特別紀錄的清楚點,最後總結就是有web api就別在用web service了吧,要轉移應該不是很大的難事,不過如果沒有時間預算來做這件事情,或者還要跟session繼續打交道的話,那將就點用吧,如果有任何錯誤的話,再請多多指正筆者。