摘要:這種情況通常發(fā)生在反向代理的時候,前端發(fā)起請求代理服務(wù)器,代理服務(wù)器發(fā)起請求到,這時候就容易導(dǎo)致域名不一致,請一定要注意這點。
寫在最前 前后端分離其實有兩類:
開發(fā)階段使用dev-server,生產(chǎn)階段是打包成靜態(tài)文件整個放入后端項目中。
開發(fā)階段使用dev-server,生產(chǎn)階段是打包成靜態(tài)文件放入多帶帶的靜態(tài)資源服務(wù)器中,如nginx。
這兩種方案最大的區(qū)別就是生產(chǎn)階段。由于第一種方案前端和后端本質(zhì)在同一個服務(wù)中的,所以壓根就沒有跨域,配置cas的坑比較少。而第二種方案我們一般使用nginx反向代理完成跨域,配置cas的坑會很多。為了后面分析方便,我們分別稱上述兩種方案為『前后端分離A』和『前后端分離B』
請求也分為兩類:1.HTTP請求:像瀏覽器地址欄發(fā)起的請求、瀏覽器自發(fā)的訪問某個網(wǎng)址、Postman測試接口,這些行為其實都是發(fā)起的HTTP請求,不會有跨域問題。
2.AJAX(XMLHttpRequest)請求:這是瀏覽器內(nèi)部的XMLHttpRequest對象發(fā)起的請求,瀏覽器會禁止其發(fā)起跨域的請求,主要是為了防止跨站腳本偽造的攻擊(CSRF)。
難點分析前后端分離、跨域、CAS這三項技術(shù)多帶帶使用起來,甚至拿其中兩個出來一起使用,難度都不大,下面來列舉一下:
前后端分離(AB)+跨域
前后端分離A+CAS(因為A方案根本就沒有跨域這一說)
前后端分離B+跨域+CAS
前后端分離(AB)+跨域這個最簡單,只有跨域,沒有CAS,常見的CORS、反向代理、JSONP都可以解決
前后端分離A+CAS 坑:CAS認(rèn)證過期,莫名出現(xiàn)跨域錯誤的問題可能有人會問,剛才不是說方案A壓根就沒有跨域問題嗎?其實,這個跨域錯誤不怪我們的后端,而是怪CAS那邊的后端,待我詳細(xì)說來。
正常情況下,CAS認(rèn)證成功后,瀏覽器會設(shè)置好一個來自CAS的Cookie以維持與CAS的Session。之后每次請求,無論是ajax請求還是http請求,都會帶上這個cookie。而我們自己的后端服務(wù)器也會有一個CAS Authorization的過濾器,把沒有CAS認(rèn)證過的請求重定向到CAS的login頁面。因為本次請求我們帶了cas的cookie,所以請求順利通過filter來到controller層,進(jìn)而返回數(shù)據(jù)。
但是考慮這樣一個情況,今天你打開你的瀏覽器,訪問一個你們新做的cms系統(tǒng)的網(wǎng)址,然后跳到cas login頁面,正常登陸,正常使用。然后來到第二天早上,因為昨天的頁面你沒關(guān),你直接點了一個查詢按鈕,結(jié)果報錯了。你打開瀏覽器控制臺,竟然發(fā)現(xiàn)報了一個跨域的錯誤。這里有兩處困惑:
為什么他喵的會跨域呢?我們前后端命名部署在一臺服務(wù)器上,是同域的啊。
為什么cas認(rèn)證失效后,沒有自動跳到cas登錄頁呢?可是我之前直接在瀏覽器輸入cms系統(tǒng)的地址時,因為沒認(rèn)證過,瀏覽器是能直接跳到cas登錄頁的,為什么這次不行呢?
ajax到底怎么處理302的
為什么會跨域:
想想一下這樣的一個流程:第二天早上你來,點擊一個查詢按鈕,發(fā)起了ajax請求,請求中帶上了一個已經(jīng)失效的cookie,然后請求被后端cas filter攔截,發(fā)現(xiàn)已失效,讓后302跳轉(zhuǎn)到cas login界面。在這個過程中,你之前發(fā)起的ajax請求其實被redirect到了cas的login.html頁面(這只是表象,本質(zhì)后面會提到)。你相當(dāng)于發(fā)起ajax請求去請求一個html文件下來,然而cas的服務(wù)器并沒有配置跨域,為了安全考慮也不能配置跨域,所以你的ajax請求還沒來得及請求下來數(shù)據(jù),你就被瀏覽器認(rèn)為是跨域了,因為你的確在請求cas服務(wù)器的一個靜態(tài)資源。
退一萬步說,就算cas服務(wù)器配置了跨域,雖然你點擊查詢按鈕的行為不會報跨域錯誤了,但你依然不能自動跳轉(zhuǎn)到cas login頁面,因為這個login.html直接當(dāng)做你ajax的success中的回調(diào)參數(shù)回來了,瀏覽器是不會幫你跳轉(zhuǎn)的。
為什么不能跳轉(zhuǎn):
首先,你打開瀏覽器輸入cms系統(tǒng)的地址去訪問的時候,發(fā)起的是HTTP請求,是不存在跨域問題的。因此你的HTTP請求被后端的filter給redirect到了cas的login.html,這個流程是沒問題的。而你點擊查詢按鈕,發(fā)起的是ajax請求,是沒法跳轉(zhuǎn)的(具體原因見下方文字)
ajax在302中的行為本質(zhì)
當(dāng)你點擊查詢按鈕,發(fā)起的是ajax請求,請求被后端filter攔截,并告知你302跳轉(zhuǎn)到login頁,此時瀏覽器首先會感知到這次ajax請求的302狀態(tài),并替ajax去訪問要跳轉(zhuǎn)到的地址,然后將訪問的結(jié)果(其實就是整個login.html頁面)返回到你的ajax的success回調(diào)函數(shù)中,因此這個回調(diào)函數(shù)的參數(shù)其實就是整個login.html的頁面。并且,直到瀏覽器把html放到ajax的success回調(diào)函數(shù)后,ajax才會真正的回調(diào),之前的302狀態(tài)ajax是感知不到的,當(dāng)然也獲取不到,所以想通過ajax判斷status是否是302,進(jìn)而手動location.href到login頁的方案是不行的。
其實,這么看起來就像是你的ajax直接請求到了login.html頁面。
另外,在實際cas跳轉(zhuǎn)的過程中,在ajax的success回調(diào)之前,你的ajax操作就被瀏覽器認(rèn)為是跨域了,所以你壓根就沒機(jī)會回調(diào)success,也因此獲取不到status狀態(tài)或者那個沒卵用的login.html。
我們要實現(xiàn)的就是:在cookie失效時,點擊查詢按鈕后,能自動跳轉(zhuǎn)到cas登錄頁。
方案很多,但都靠一下兩點:
用HTTP請求替代Ajax請求去跳轉(zhuǎn)到登錄頁
用200代替302告知ajax當(dāng)前請求的狀態(tài)
舉幾個例子:
1、錯誤方案:設(shè)法攔截ajax的response,然后判斷response的status是否是302,如果是302就手動location.href跳到cas登錄頁,但是這樣是不行的,因為我們根本獲取不到這個302狀態(tài)。
2、必須要后端配合,后端需要額外加1個filter和1個controller, 起個名字吧,就叫ValidateFilter和ValidateController吧。
ValidateFilter只過濾那些需要被cas攔截的請求,在doFilter里面判斷HttpServletRequest的狀態(tài),看看這個request里能不能獲取到當(dāng)前用戶名,如果能獲取到,代表認(rèn)證沒問題,讓這個請求繼續(xù)往下走chain.doFilter,如果不能獲取到,代表認(rèn)證失效了(因為filter不能直接返回,所以我們需要一個ValidateController),我們request.dispatch這個請求到ValidateController的redirect方法中(自己寫的),讓這個redirect方法返回一個result,result中設(shè)置一個標(biāo)志,比如給code:xxx。
然后前端設(shè)法在ajax的response之前獲取response的result,看看result的code是否為xxx,如果是,那就location.href跳轉(zhuǎn)到cas登錄頁即可,其中service參數(shù)寫cas登陸之后要回調(diào)的后端接口,然后讓后端去跳轉(zhuǎn)到前端頁面。
為什么不能直接service寫前端?
因為我們不僅要跟cas服務(wù)器維持session,還要跟我們自己的后端維持session,如果不回調(diào)后端,后端就不會感知到我們的登錄狀態(tài)了。
比如:
//前端: if(result.code === xxx) { location. //currentPath是為了login之后再調(diào)回當(dāng)前頁面 } //后端 filter 偽代碼: void doFilter(request, response, chain) { if(request中有用戶名) { chain.doFilter() } else if(request.uri == "/redirect/to/caslogin") { chain.doFilter() } else { request.dispatch("/redirect/to/caslogin") } } //后端 controller 偽代碼 // 用來接受filter過來的那些認(rèn)證失效的請求 @path("/redirect/to/caslogin") String redirectToCasLogin(request, response) { return { "code": xxx } } // 用來在login之后回調(diào)用 @path("/redirect/to/frontend") String redirectToFrontend(request, response) { String path = request中的currentPath參數(shù) request.sendRedirect(path) } // 另外,這個controller一定不要被validateFilter過濾,因為如果這個controller也要被過濾,那就陷入cas驗證的死循環(huán)了。
3.和2類似,但是location.href中直接寫
location.href = "http://后端服務(wù)器地址/redirect/to/caslogin?currentPath=當(dāng)前頁面路徑"
此時我們直接請求后端接口/redirect/to/caslogin,他首先被validateFilter攔截,但是因為有一個if判斷,他被直接doFilter,然后請求來到了cas的Filter,因為沒登錄,該filter會自動拼接我們配置的cas serverName+當(dāng)前請求的uri,同樣會形成
"http://cas.server.com/login?service=http://后端服務(wù)器地址/redirec...徑"這樣的url。
寫不動了,總之要注意:要保持cookie的域一致
對于nginx,如果從 www.a.com/ 代理到 www.b.com/api,那么形成的cookie的域是會是/api,而瀏覽器發(fā)起請求時只能攜帶/域的cookie,所以導(dǎo)致cookie丟失,session失效。可以通過nginx配置,把/api域下的cookie都放到/即可解決。
為了避免額外的麻煩,最好保持代理前后url一致吧,即都有一個/api前綴,或者都沒有。
對于瀏覽器,發(fā)起的ajax所帶的cookie是發(fā)起請求的host域名有嚴(yán)格關(guān)系的,不同的域名帶不同的cookie,所以如果出現(xiàn),你明明已經(jīng)登陸了,但是在此發(fā)起ajax請求,后端還是識別不出來你的登錄狀態(tài),那就可能是你發(fā)起的請求的域名不一致了。也就是說,你去請求后端接口的時候用www.a.com,結(jié)果cas登陸成功后的要回調(diào)的接口成了www.b.com,這樣你的cas登錄狀態(tài)的cookie就附著在www.b.com的域名上了,然后當(dāng)你再發(fā)起www.a.com的請求的時候,發(fā)現(xiàn)你根本帶不上cas下來的cookie,因為域不同。
這種情況通常發(fā)生在反向代理的時候,前端發(fā)起ajax請求代理服務(wù)器www.a.com,代理服務(wù)器發(fā)起請求到www.b.com,這時候就容易導(dǎo)致域名不一致,請一定要注意這點。
另外,對于當(dāng)前前后端分開部署的情況,location.href中,service的回調(diào)接口不能直接寫后端地址(相當(dāng)于www.b.com),而應(yīng)該寫www.a.com,讓代理服務(wù)器去訪問www.b.com,這樣才能保持cookie的域的一致性!!!!
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/69713.html
摘要:這種情況通常發(fā)生在反向代理的時候,前端發(fā)起請求代理服務(wù)器,代理服務(wù)器發(fā)起請求到,這時候就容易導(dǎo)致域名不一致,請一定要注意這點。 寫在最前 前后端分離其實有兩類: 開發(fā)階段使用dev-server,生產(chǎn)階段是打包成靜態(tài)文件整個放入后端項目中。 開發(fā)階段使用dev-server,生產(chǎn)階段是打包成靜態(tài)文件放入單獨的靜態(tài)資源服務(wù)器中,如nginx。 這兩種方案最大的區(qū)別就是生產(chǎn)階段。由于第...
摘要:這種情況通常發(fā)生在反向代理的時候,前端發(fā)起請求代理服務(wù)器,代理服務(wù)器發(fā)起請求到,這時候就容易導(dǎo)致域名不一致,請一定要注意這點。 寫在最前 前后端分離其實有兩類: 開發(fā)階段使用dev-server,生產(chǎn)階段是打包成靜態(tài)文件整個放入后端項目中。 開發(fā)階段使用dev-server,生產(chǎn)階段是打包成靜態(tài)文件放入單獨的靜態(tài)資源服務(wù)器中,如nginx。 這兩種方案最大的區(qū)別就是生產(chǎn)階段。由于第...
摘要:最重要的兩點請求跨域的時候,默認(rèn)不會攜帶。通常是這樣的前端發(fā)起,后端接受請求并執(zhí)行,前端接受相應(yīng)并發(fā)起,請求重定向后的頁面,其中不存在跨域問題。 最重要的兩點: ajax請求跨域的時候,默認(rèn)不會攜帶cookie。 請求分為普通請求(HttpRequest)和Ajax請求(XMLHttpRequest) 先屢一下跨域CAS認(rèn)證的流程: 前端發(fā)起ajax請求,請求首先被跨域Filter...
摘要:最重要的兩點請求跨域的時候,默認(rèn)不會攜帶。通常是這樣的前端發(fā)起,后端接受請求并執(zhí)行,前端接受相應(yīng)并發(fā)起,請求重定向后的頁面,其中不存在跨域問題。 最重要的兩點: ajax請求跨域的時候,默認(rèn)不會攜帶cookie。 請求分為普通請求(HttpRequest)和Ajax請求(XMLHttpRequest) 先屢一下跨域CAS認(rèn)證的流程: 前端發(fā)起ajax請求,請求首先被跨域Filter...
摘要:目前正在寫一個微信公眾號的小項目,記錄一下遇到的問題和解決方法主要是前端。前端提交時使用,在后端再取出對應(yīng)的微信支付看了下文檔,以前是需要用喚起支付,而現(xiàn)在則是把微信內(nèi)置到了微信的瀏覽器中。 目前正在寫一個微信公眾號的小項目,記錄一下遇到的問題和解決方法(主要是前端)。內(nèi)容持續(xù)更新中~ 主要實現(xiàn) 前后端分離前端為 SPA 單頁面使用微信的JSSDK微信支付 技術(shù)方案 后端使用 php ...
閱讀 1017·2023-04-25 22:27
閱讀 872·2021-11-22 14:56
閱讀 984·2021-11-11 16:54
閱讀 1678·2019-08-30 15:54
閱讀 3500·2019-08-30 13:20
閱讀 1213·2019-08-30 10:55
閱讀 2080·2019-08-26 13:34
閱讀 3281·2019-08-26 11:53