背景

登錄是一個網站最基礎的功能。有人說它很簡單,其實不然,登錄邏輯很簡單,但涉及知識點比較多,如:

密碼加密、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>

我們在瀏覽器訪問a.com:8888/index.html,訪問前先設置hosts,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方式實現

作者:羅那爾少

鏈接:imooc.com/article/28833

來源:慕課網

本文原創發佈於慕課網 ,轉載請註明出處,謝謝合作

推薦閱讀:

相關文章