天天看點

前後端分離 | 關于登入狀态那些事背景同源政策同域下的前後端分離不同域下的前後端分離總結

背景

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

密碼加密、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方式實作

繼續閱讀