背景
登入是一個網站最基礎的功能。有人說它很簡單,其實不然,登入邏輯很簡單,但涉及知識點比較多,如:
密碼加密、cookie、session、token、JWT等。
我們看一下傳統的做法,前後端統一在一個服務中:

如圖所示,邏輯處理和頁面放在一個服務中,使用者輸入使用者名、密碼後,背景服務在session中設定登入狀态,和使用者的一些基本資訊,
然後将響應(Response)傳回到浏覽器(Browser),并設定Cookie。下次使用者在這個浏覽器(Browser)中,再次
通路服務時,請求中會帶上這個Cookie,服務端根據這個Cookie就能找到對應的session,從session中取得使用者的資訊,進而
維持了使用者的登入狀态。這種機制被稱作Cookie-Session機制。
近幾年,随着前後端分離的流行,我們的項目結構也發生了變化,如下圖:
我們通路一個網站時,先去請求靜态服務,拿到頁面後,再異步去背景請求資料,最後渲染成我們看到的帶有資料的網站。在這種結構下,
我們的登入狀态怎麼維持呢?上面的Cookie-Session機制還适不适用?
這裡又分兩種情況,服務A和服務B在同一域下,服務A和服務B在不同域下。在詳細介紹之前,我們先普及一下浏覽器的同源政策。
同源政策
同源政策是浏覽器保證安全的基礎,它的含義是指,A網頁設定的 Cookie,B網頁不能打開,除非這兩個網頁同源。
所謂同源是指:
- 協定相同
- 域名相同
- 端口相同
例如:
http://www.a.com/login,協定是
http
,域名是
www.a.com
,端口是
80
。隻要這3個相同,我們就可以在請求(Request)時帶上Cookie,
在響應(Response)時設定Cookie。
同域下的前後端分離
我們了解了浏覽器的同源政策,接下來就看一看同域下的前後端分離,首先看服務端能不能設定Cookie,具體代碼如下:
後端代碼:
@RequestMapping("setCookie")
public String setCookie(HttpServletResponse response){
Cookie cookie = new Cookie("test","same");
cookie.setPath("/");
response.addCookie(cookie);
return "success";
}
我們設定Cookie的path為根目錄"/",以便在該域的所有路徑下都能看到這個Cookie。
前端代碼:
<!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>
我們在浏覽器通路
http://www.a.com:8888/index.html,通路前先設定hosts,将www.a.com指向我們本機。通路結果如圖所示:
我們可以看到伺服器成功設定了Cookie。然後我們再看看同域下,異步請求能不能帶上Cookie,代碼如下:
@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";
}
前端代碼如下:
<!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>
通路結果如圖所示:
再看看背景列印的日志:
name:test-----value:same
同域下,異步請求時,Cookie也能帶到服務端。
是以,我們在做前後端分離時,前端和後端部署在同一域下,滿足浏覽器的同源政策,登入不需要做特殊的處理。
不同域下的前後端分離
不同域下,我們的響應(Response)能不能設定Cookie呢?請求時能不能帶上Cookie呢?我們實驗結果如下,這裡就不給大家貼代碼了。
由于我們在a.com域下的頁面跨域通路b.com的服務,b.com的服務不能設定Cookie。
如果b.com域下有Cookie,我們在a.com域下的頁面跨域通路b.com的服務,能不能把b.com的Cookie帶上嗎?答案是也帶不上。那麼我們怎麼解決
跨域問題呢?
JSONP解決跨域
JSONP的原理我們可以在
維基百科上檢視,上面寫的很清楚,我們不做過多的介紹。我們改造接口,
在每個接口上增加callback參數:
@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參數不為空,将傳回js函數。前端改造如下:
設定Cookie頁面改造如下:
<script>
$(function () {
$.ajax({
url : "http://www.b.com:8888/test/setCookie?callback=?",
method: "get",
dataType : 'jsonp',
success : function (json) {
console.log(json);
}
});
})
</script>
請求Cookie時改造如下:
<script>
$(function () {
$.ajax({
url : "http://www.b.com:8888/test/getCookie?callback=?",
method: "get",
dataType : 'jsonp',
success : function (json) {
console.log(json);
}
});
})
</script>
所有的請求都加了callback參數,請求的結果如下:
很神奇吧!我們設定了b.com域下的Cookie。 如果想知道為什麼?還是看一看JSONP的原理吧。我們再通路第二個頁面,看看Cookie能不能
傳到服務。背景列印日志為:
name:test-----value:same
好了,不同域下的前後端分離,可以通過JSONP跨域,進而保持登入狀态。 但是,jsonp本身沒有跨域安全規範,一般都是後端進行安全限制,
處理不當很容易造成安全問題。
CORS解決跨域
CORS是一個W3C标準,全稱是"跨域資源共享"(Cross-origin resource sharing)。CORS需要浏覽器和伺服器同時支援。目前,所有浏覽器都支援該功能,IE浏覽器不能低于IE10。
整個CORS通信過程,都是浏覽器自動完成,不需要使用者參與。對于開發者來說,CORS通信與同源的AJAX通信沒有差别,代碼完全一樣。
浏覽器一旦發現AJAX請求跨源,就會自動添加一些附加的頭資訊,有時還會多出一次附加的請求,但使用者不會有感覺。
如果想要詳細了解原理,請參考
CORS請求預設不發送Cookie和HTTP認證資訊。若要發送Cookie,浏覽器和服務端都要做設定,咱們要解決的是跨域後的登入問題,是以要允許跨域發送
Cookie。
後端要設定允許跨域請求的域和允許設定和接受Cookie。
@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
設定了允許跨域請求的域,
allowCredentials
允許設定和接受Cookie。
前端要設定允許發送和接受Cookie。
<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>
我們通路頁面看一下效果。
沒有Cookie嗎?别急,我們再從浏覽器的設定裡看一下。
有Cookie了,我們再看看通路能不能帶上Cookie,背景列印結果如下:
name:test-----value:same
我們使用CORS,也解決了跨域。
總結
前後端分離,基于Cookie-Session機制的登入總結如下
- 前後端同域——與普通登入沒有差別
- 前後端不同域
- JSONP方式實作
- CORS方式實作