登錄是一個(gè)網(wǎng)站最基礎(chǔ)的功能。有人說它很簡單,其實(shí)不然,登錄邏輯很簡單,但涉及知識(shí)點(diǎn)比較多,如: 密碼加密、cookie、session、token、JWT等。
我們看一下傳統(tǒng)的做法,前后端統(tǒng)一在一個(gè)服務(wù)中:
如圖所示,邏輯處理和頁面放在一個(gè)服務(wù)中,用戶輸入用戶名、密碼后,后臺(tái)服務(wù)在session中設(shè)置登錄狀態(tài),和用戶的一些基本信息, 然后將響應(yīng)(Response)返回到瀏覽器(Browser),并設(shè)置Cookie。下次用戶在這個(gè)瀏覽器(Browser)中,再次 訪問服務(wù)時(shí),請(qǐng)求中會(huì)帶上這個(gè)Cookie,服務(wù)端根據(jù)這個(gè)Cookie就能找到對(duì)應(yīng)的session,從session中取得用戶的信息,從而 維持了用戶的登錄狀態(tài)。這種機(jī)制被稱作Cookie-Session機(jī)制。
近幾年,隨著前后端分離的流行,我們的項(xiàng)目結(jié)構(gòu)也發(fā)生了變化,如下圖:
我們?cè)L問一個(gè)網(wǎng)站時(shí),先去請(qǐng)求靜態(tài)服務(wù),拿到頁面后,再異步去后臺(tái)請(qǐng)求數(shù)據(jù),最后渲染成我們看到的帶有數(shù)據(jù)的網(wǎng)站。在這種結(jié)構(gòu)下, 我們的登錄狀態(tài)怎么維持呢?上面的Cookie-Session機(jī)制還適不適用?
這里又分兩種情況,服務(wù)A和服務(wù)B在同一域下,服務(wù)A和服務(wù)B在不同域下。在詳細(xì)介紹之前,我們先普及一下瀏覽器的同源策略。
同源策略是瀏覽器保證安全的基礎(chǔ),它的含義是指,A網(wǎng)頁設(shè)置的 Cookie,B網(wǎng)頁不能打開,除非這兩個(gè)網(wǎng)頁同源。 所謂同源是指:
例如:http://www.a.com/login,協(xié)議是http
,域名是www.a.com
,端口是80
。只要這3個(gè)相同,我們就可以在請(qǐng)求(Request)時(shí)帶上Cookie, 在響應(yīng)(Response)時(shí)設(shè)置Cookie。
我們了解了瀏覽器的同源策略,接下來就看一看同域下的前后端分離,首先看服務(wù)端能不能設(shè)置Cookie,具體代碼如下:
后端代碼:
1 2 3 4 5 6 7 | @RequestMapping ( 'setCookie' ) public String setCookie(HttpServletResponse response){ Cookie cookie = new Cookie( 'test' , 'same' ); cookie.setPath( '/' ); response.addCookie(cookie); return 'success' ; } |
我們?cè)O(shè)置Cookie的path為根目錄”/”,以便在該域的所有路徑下都能看到這個(gè)Cookie。
前端代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <! DOCTYPE html> < html lang='en'> < head > < meta charset='UTF-8'> < title >test</ title > < script src='http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.0.js'></ script > < script > $(function () { $.ajax({ url : '/test/setCookie', method: 'get', success : function (json) { console.log(json); } }); }) </ script > </ head > < body > aaa </ body > </ html > |
我們?cè)跒g覽器訪問http://www.a.com:8888/index.html,訪問前先設(shè)置hosts,將www.a.com指向我們本機(jī)。訪問結(jié)果如圖所示:
我們可以看到服務(wù)器成功設(shè)置了Cookie。然后我們?cè)倏纯赐蛳?,異步?qǐng)求能不能帶上Cookie,代碼如下:
后端代碼:
1 2 3 4 5 6 7 8 9 10 | @RequestMapping ( 'getCookie' ) public String getCookie(HttpServletRequest request,HttpServletResponse response){ Cookie[] cookies = request.getCookies(); if (cookies != null && cookies.length > 0 ) { for (Cookie cookie : cookies) { System.out.println( 'name:' cookie.getName() '-----value:' cookie.getValue()); } } return 'success' ; } |
前端代碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <! DOCTYPE html> < html lang='en'> < head > < meta charset='UTF-8'> < title >user</ title > < script src='http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.0.js'></ script > < script > $(function () { $.ajax({ url : 'http://www.b.com:8888/test/getCookie', method: 'get', success : function (json) { console.log(json); } }); }) </ script > </ head > < body > </ body > </ html > |
訪問結(jié)果如圖所示:
再看看后臺(tái)打印的日志:
1 | name:test-----value:same |
同域下,異步請(qǐng)求時(shí),Cookie也能帶到服務(wù)端。
所以,我們?cè)谧銮昂蠖朔蛛x時(shí),前端和后端部署在同一域下,滿足瀏覽器的同源策略,登錄不需要做特殊的處理。
不同域下,我們的響應(yīng)(Response)能不能設(shè)置Cookie呢?請(qǐng)求時(shí)能不能帶上Cookie呢?我們實(shí)驗(yàn)結(jié)果如下,這里就不給大家貼代碼了。
由于我們?cè)赼.com域下的頁面跨域訪問b.com的服務(wù),b.com的服務(wù)不能設(shè)置Cookie。
如果b.com域下有Cookie,我們?cè)赼.com域下的頁面跨域訪問b.com的服務(wù),能不能把b.com的Cookie帶上嗎?答案是也帶不上。那么我們?cè)趺唇鉀Q 跨域問題呢?
JSONP的原理我們可以在維基百科上查看,上面寫的很清楚,我們不做過多的介紹。我們改造接口, 在每個(gè)接口上增加callback參數(shù):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | @RequestMapping ( 'setCookie' ) public String setCookie(HttpServletResponse response,String callback){ Cookie cookie = new Cookie( 'test' , 'same' ); cookie.setPath( '/' ); response.addCookie(cookie); if (StringUtils.isNotBlank(callback)){ return callback '('success')' ; } return 'success' ; } @RequestMapping ( 'getCookie' ) public String getCookie(HttpServletRequest request,HttpServletResponse response,String callback){ Cookie[] cookies = request.getCookies(); if (cookies != null && cookies.length > 0 ) { for (Cookie cookie : cookies) { System.out.println( 'name:' cookie.getName() '-----value:' cookie.getValue()); } } if (StringUtils.isNotBlank(callback)){ return callback '('success')' ; } return 'success' ; } |
如果callback參數(shù)不為空,將返回js函數(shù)。前端改造如下:
設(shè)置Cookie頁面改造如下:
1 2 3 4 5 6 7 8 9 10 11 12 | < script > $(function () { $.ajax({ url : 'http://www.b.com:8888/test/setCookie?callback=?', method: 'get', dataType : 'jsonp', success : function (json) { console.log(json); } }); }) </ script > |
請(qǐng)求Cookie時(shí)改造如下:
1 2 3 4 5 6 7 8 9 10 11 12 | < script > $(function () { $.ajax({ url : 'http://www.b.com:8888/test/getCookie?callback=?', method: 'get', dataType : 'jsonp', success : function (json) { console.log(json); } }); }) </ script > |
所有的請(qǐng)求都加了callback參數(shù),請(qǐng)求的結(jié)果如下:
很神奇吧!我們?cè)O(shè)置了b.com域下的Cookie。 如果想知道為什么?還是看一看JSONP的原理吧。我們?cè)僭L問第二個(gè)頁面,看看Cookie能不能 傳到服務(wù)。后臺(tái)打印日志為:
1 | name:test-----value:same |
好了,不同域下的前后端分離,可以通過JSONP跨域,從而保持登錄狀態(tài)。 但是,jsonp本身沒有跨域安全規(guī)范,一般都是后端進(jìn)行安全限制, 處理不當(dāng)很容易造成安全問題。
CORS是一個(gè)W3C標(biāo)準(zhǔn),全稱是”跨域資源共享”(Cross-origin resource sharing)。CORS需要瀏覽器和服務(wù)器同時(shí)支持。目前,所有瀏覽器都支持該功能,IE瀏覽器不能低于IE10。 整個(gè)CORS通信過程,都是瀏覽器自動(dòng)完成,不需要用戶參與。對(duì)于開發(fā)者來說,CORS通信與同源的AJAX通信沒有差別,代碼完全一樣。 瀏覽器一旦發(fā)現(xiàn)AJAX請(qǐng)求跨源,就會(huì)自動(dòng)添加一些附加的頭信息,有時(shí)還會(huì)多出一次附加的請(qǐng)求,但用戶不會(huì)有感覺。 如果想要詳細(xì)理解原理,請(qǐng)參考維基百科
CORS請(qǐng)求默認(rèn)不發(fā)送Cookie和HTTP認(rèn)證信息。若要發(fā)送Cookie,瀏覽器和服務(wù)端都要做設(shè)置,咱們要解決的是跨域后的登錄問題,所以要允許跨域發(fā)送 Cookie。
后端要設(shè)置允許跨域請(qǐng)求的域和允許設(shè)置和接受Cookie。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | @RequestMapping ( 'setCookie' ) @CrossOrigin (origins= 'http://www.a.com:8888' ,allowCredentials = 'true' ) public String setCookie(HttpServletResponse response){ Cookie cookie = new Cookie( 'test' , 'same' ); cookie.setPath( '/' ); response.addCookie(cookie); return 'success' ; } @RequestMapping ( 'getCookie' ) @CrossOrigin (origins= 'http://www.a.com:8888' ,allowCredentials = 'true' ) public String getCookie(HttpServletRequest request,HttpServletResponse response){ Cookie[] cookies = request.getCookies(); if (cookies != null && cookies.length > 0 ) { for (Cookie cookie : cookies) { System.out.println( 'name:' cookie.getName() '-----value:' cookie.getValue()); } } return 'success' ; } |
我們通過@CrossOrigin
注解允許跨域,origins
設(shè)置了允許跨域請(qǐng)求的域,allowCredentials
允許設(shè)置和接受Cookie。
前端要設(shè)置允許發(fā)送和接受Cookie。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | < script > $(function () { $.ajax({ url : 'http://www.b.com:8888/test/setCookie', method: 'get', success : function (json) { console.log(json); }, xhrFields: { withCredentials: true } }); }) </ script > < script > $(function () { $.ajax({ url : 'http://www.b.com:8888/test/getCookie', method: 'get', success : function (json) { console.log(json); }, xhrFields: { withCredentials: true } }); }) </ script > |
我們?cè)L問頁面看一下效果。
沒有Cookie嗎?別急,我們?cè)購臑g覽器的設(shè)置里看一下。
有Cookie了,我們?cè)倏纯丛L問能不能帶上Cookie,后臺(tái)打印結(jié)果如下:
1 | name:test-----value:same |
我們使用CORS,也解決了跨域。
前后端分離,基于Cookie-Session機(jī)制的登錄總結(jié)如下
聯(lián)系客服