單點登錄(SSO)


單點登錄的英文名叫做:Single Sign On(簡稱SSO)。

在初學/以前的時候,一般我們就單系統,所有的功能都在同一個系統上

后來,我們為了合理利用資源和降低耦合性,於是把單系統拆分成多個子系統。

 

比如阿里系的淘寶和天貓,很明顯地我們可以知道這是兩個系統,但是你在使用的時候,登錄了天貓,淘寶也會自動登錄。

簡單來說,單點登錄就是在多個系統中,用戶只需一次登錄,各個系統即可感知該用戶已經登錄。

眾所周知,HTTP是無狀態的協議,這意味着服務器無法確認用戶的信息。於是乎,W3C就提出了:給每一個用戶都發一個通行證,無論誰訪問的時候都需要攜帶通行證,這樣服務器就可以從通行證上確認用戶的信息。通行證就是Cookie。

如果說Cookie是檢查用戶身上的”通行證“來確認用戶的身份,那么Session就是通過檢查服務器上的”客戶明細表“來確認用戶的身份的。Session相當於在服務器中建立了一份“客戶明細表”。

HTTP協議是無狀態的,Session不能依據HTTP連接來判斷是否為同一個用戶。於是乎:服務器向用戶瀏覽器發送了一個名為JESSIONID的Cookie,它的值是Session的id值。其實Session是依據Cookie來識別是否是同一個用戶。

所以,一般我們單系統實現登錄會這樣做:

    • 登錄:將用戶信息保存在Session對象中
      • 如果在Session對象中能查到,說明已經登錄
      • 如果在Session對象中查不到,說明沒登錄(或者已經退出了登錄)
    • 注銷(退出登錄):從Session中刪除用戶的信息
    • 記住我(關閉掉瀏覽器后,重新打開瀏覽器還能保持登錄狀態):配合Cookie來用

 

總結一下上面的思路:

  • 用戶登錄時,驗證用戶的賬戶和密碼
  • 生成一個Token保存在數據庫中,將Token寫到Cookie中
  • 將用戶數據保存在Session中
  • 請求時都會帶上Cookie,檢查有沒有登錄,如果已經登錄則放行

如果沒看懂的同學,建議回顧Session和Cookie和HTTP:

解決系統之間Session不共享問題有一下幾種方案:

  • Tomcat集群Session全局復制(集群內每個tomcat的session完全同步)【會影響集群的性能呢,不建議】
  • 根據請求的IP進行Hash映射到對應的機器上(這就相當於請求的IP一直會訪問同一個服務器)【如果服務器宕機了,會丟失了一大部分Session的數據,不建議】
  • 把Session數據放在Redis中(使用Redis模擬Session)【建議】
    • 如果還不了解Redis的同學,建議移步(Redis合集

我們可以將登錄功能單獨抽取出來,做成一個子系統。

SSO(登錄系統)的邏輯如下:

// 登錄功能(SSO單獨的服務)
@Override
public TaotaoResult login(String username, String password) throws Exception {

    //根據用戶名查詢用戶信息
    TbUserExample example = new TbUserExample();
    Criteria criteria = example.createCriteria();
    criteria.andUsernameEqualTo(username);
    List<TbUser> list = userMapper.selectByExample(example);
    if (null == list || list.isEmpty()) {
        return TaotaoResult.build(400, "用戶不存在");
    }
    //核對密碼
    TbUser user = list.get(0);
    if (!DigestUtils.md5DigestAsHex(password.getBytes()).equals(user.getPassword())) {
        return TaotaoResult.build(400, "密碼錯誤");
    }
    //登錄成功,把用戶信息寫入redis
    //生成一個用戶token
    String token = UUID.randomUUID().toString();
    jedisCluster.set(USER_TOKEN_KEY + ":" + token, JsonUtils.objectToJson(user));
    //設置session過期時間
    jedisCluster.expire(USER_TOKEN_KEY + ":" + token, SESSION_EXPIRE_TIME);
    return TaotaoResult.ok(token);
}

其他子系統登錄時,請求SSO(登錄系統)進行登錄,將返回的token寫到Cookie中,下次訪問時則把Cookie帶上:

public TaotaoResult login(String username, String password, 
        HttpServletRequest request, HttpServletResponse response) {
    //請求參數
    Map<String, String> param = new HashMap<>();
    param.put("username", username);
    param.put("password", password);
    //登錄處理
    String stringResult = HttpClientUtil.doPost(REGISTER_USER_URL + USER_LOGIN_URL, param);
    TaotaoResult result = TaotaoResult.format(stringResult);
    //登錄出錯
    if (result.getStatus() != 200) {
        return result;
    }
    //登錄成功后把取token信息,並寫入cookie
    String token = (String) result.getData();
    //寫入cookie
    CookieUtils.setCookie(request, response, "TT_TOKEN", token);
    //返回成功
    return result;

}

總結:

  • SSO系統生成一個token,並將用戶信息存到Redis中,並設置過期時間
  • 其他系統請求SSO系統進行登錄,得到SSO返回的token,寫到Cookie中
  • 每次請求時,Cookie都會帶上,攔截器得到token,判斷是否已經登錄

到這里,其實我們會發現其實就兩個變化:

  • 將登陸功能抽取為一個系統(SSO),其他系統請求SSO進行登錄
  • 本來將用戶信息存到Session,現在將用戶信息存到Redis

    3.2 Cookie跨域的問題

    上面我們解決了Session不能共享的問題,但其實還有另一個問題。Cookie是不能跨域的

    比如說,我們請求<https://www.google.com/>時,瀏覽器會自動把google.com的Cookie帶過去給google的服務器,而不會把<https://www.baidu.com/>的Cookie帶過去給google的服務器。

    這就意味着,由於域名不同,用戶向系統A登錄后,系統A返回給瀏覽器的Cookie,用戶再請求系統B的時候不會將系統A的Cookie帶過去。

    針對Cookie存在跨域問題,有幾種解決方案:

    1. 服務端將Cookie寫到客戶端后,客戶端對Cookie進行解析,將Token解析出來,此后請求都把這個Token帶上就行了
    2. 多個域名共享Cookie,在寫到客戶端的時候設置Cookie的domain。
    3. 將Token保存在SessionStroage中(不依賴Cookie就沒有跨域的問題了)

    到這里,我們已經可以實現單點登錄了。

    3.3 CAS原理

    說到單點登錄,就肯定會見到這個名詞:CAS (Central Authentication Service),下面說說CAS是怎么搞的。

    如果已經將登錄單獨抽取成系統出來,我們還能這樣玩。現在我們有兩個系統,分別是www.java3y.comwww.java4y.com,一個SSOwww.sso.com

    首先,用戶想要訪問系統Awww.java3y.com受限的資源(比如說購物車功能,購物車功能需要登錄后才能訪問),系統Awww.java3y.com發現用戶並沒有登錄,於是重定向到sso認證中心,並將自己的地址作為參數。請求的地址如下:

    • www.sso.com?service=www.java3y.com

    sso認證中心發現用戶未登錄,將用戶引導至登錄頁面,用戶進行輸入用戶名和密碼進行登錄,用戶與認證中心建立全局會話(生成一份Token,寫到Cookie中,保存在瀏覽器上)

    隨后,認證中心重定向回系統A,並把Token攜帶過去給系統A,重定向的地址如下:

    • www.java3y.com?token=xxxxxxx

    接着,系統A去sso認證中心驗證這個Token是否正確,如果正確,則系統A和用戶建立局部會話(創建Session)。到此,系統A和用戶已經是登錄狀態了。

    此時,用戶想要訪問系統Bwww.java4y.com受限的資源(比如說訂單功能,訂單功能需要登錄后才能訪問),系統Bwww.java4y.com發現用戶並沒有登錄,於是重定向到sso認證中心,並將自己的地址作為參數。請求的地址如下:

    • www.sso.com?service=www.java4y.com

    注意,因為之前用戶與認證中心www.sso.com已經建立了全局會話(當時已經把Cookie保存到瀏覽器上了),所以這次系統B重定向到認證中心www.sso.com是可以帶上Cookie的。

    認證中心根據帶過來的Cookie發現已經與用戶建立了全局會話了,認證中心重定向回系統B,並把Token攜帶過去給系統B,重定向的地址如下:

    • www.java4y.com?token=xxxxxxx

    接着,系統B去sso認證中心驗證這個Token是否正確,如果正確,則系統B和用戶建立局部會話(創建Session)。到此,系統B和用戶已經是登錄狀態了。

    看到這里,其實SSO認證中心就類似一個中轉站。
    最后:在本地進行前端開發過程中,單點登錄存在跨域攜帶cookie的問題,在谷歌高於90版本的瀏覽器,chrome在后續的跨站安全機制, 導致跨站cookie不能共享 , 下載低版本的90的chrome 然后chrome flags 設置關閉chrome same site的cookie的設置默認禁止攜帶cookie,同時需要禁止chrome自動更新

  •  


  • 解決  跨站cookie不能共
  •  



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM