天天看點

java實作 SSO 單點登入(最終版)--補充完全跨域SSO前言同父域單點登入在這個版本上補充完全跨域SSO内容

版權聲明:本文為部落客原創文章,未經部落客允許不得轉載。 https://blog.csdn.net/zhangjingao/article/details/89052764

前言

  前面我寫了一篇文章,java實作完全跨域SSO單點登入,最後我會比較兩種方案。

  那篇文章主要說明完全跨域SSO單點登入的實作,但是我最終并沒有使用那篇,當然,那篇完全可以實作SSO跨域,但是那篇有一些不太優雅的地方,我綜合我的場景等各方面考慮,最終選擇了我下面的這個方案。因為那篇并沒有被選用,是以代碼大家可以随意看,但是下面這個方案因為代碼已經在使用,是以不太友善分享代碼,見諒。大家有什麼問題,歡迎留言或者qq私我,如果是qq(1763608200)私我,建議加驗證資訊:sso探讨(内心os: 不加驗證資訊我不會通過的,最近資訊洩露太嚴重了,好多頭像是雜七雜八的不知道是賣茶還是幹啥的老加我 )

同父域單點登入

什麼是SSO

  SSO(Single Sign On)單點登入是實作多個系統之間統一登入的驗證系統,簡單來說就是:有A,B,C三個系統,在A處登入過後,再通路B系統,B系統就已經處于了登入狀态,C系統也是一樣。舉個生活中栗子:你同時打開天貓和淘寶,都進入login界面,都要求你登入的,現在你在淘寶處登入後,直接在天貓處重新整理,你會發現,你已經登入了,而且就是你在淘寶上登入的使用者。說明他們實作了SSO,并且持有相同的資訊。

  當然這個特性意味着它的使用場景是:同一公司下的不同子系統,因為對于SSO來說,每一個子系統擁有的資訊都一樣,是使用者的全部資訊,如果是不同公司,那這肯定不合适。

這套方案我們要實作的效果

  假設此時有A,B,C三套系統,他們有相同的頂級域名,就是說他們是同一個域名的子域名,此處使用a.xxx.com,b.xxx.com,c.xxx.com來表示,有一個獨立的驗證中心,使用sso.xxx.com表示。當我們在a.xxx.com下登入後,我目前的浏覽器通路b.xxx.com依然能直接通路,并且已經處于登入狀态。當我在A系統中登出登入時,B系統也會被登出登入。但是如果是b.yyy.com在該方案下就不支援了。

具體流程

java實作 SSO 單點登入(最終版)--補充完全跨域SSO前言同父域單點登入在這個版本上補充完全跨域SSO内容

  初始A,B,C全部處于未登入狀态,沒有令牌(tokenid)。

  1、使用者通路A系統,沒有令牌,A系統驗證未登入,直接傳回A的登入界面;

  2、A輸入賬号密碼進行登入請求到SSO中(附帶重定向url);

  3、然後SSO驗證使用者資訊正确,生成身份令牌,将身份令牌轉為jwt,将使用者資訊使用AES加密,将jwt身份令牌和使用者資訊作為鍵值對存進redis,時間設為30分鐘,将身份令牌種進cookie,重定向回攜帶的url(到達了子系統);

  4、子系統發現有身份令牌,發送請求到達SSO驗證令牌真僞;

  5、SSO收到驗證請求,驗證通過,傳回AES加密的使用者資訊;

  6、子系統收到通過資訊,将使用者資訊解密,存進session一分鐘(token),跳轉頁面至首頁或使用者請求頁;

  7、一分鐘失效後或者子系統通路時,發現有令牌但session中無資訊,再次重複4,5,6。

解釋幾個疑問點

1、sso中redis存儲的是全局會話,而每個子系統中session存儲的是臨時會話。

2、為什麼臨時會話定義一分鐘?

  定義一分鐘,就是為了當A系統退出後,登出了本地臨時會話,再到sso登出全局會話,一分鐘的話即使不去登出B系統的臨時會話,也很快就會消失,這個時間可以忽略,再者一分鐘可以保證子系統和sso實時保證使用者資訊的統一,假設此時該使用者修改了個人資訊,其他系統也能保證盡快得到最新的資訊。

3、為什麼sso中要用redis存儲身份令牌呢?為什麼不用session呢?

  首先各個子系統驗證身份令牌時是由各個子系統發出的,不一定是登入的那個域名了,是以無法保證session還能跟登入的那個session一緻,是以使用redis儲存,這樣身份令牌和sso的redis之間也模拟出來一個全局會話關系出來。

4、為什麼要用JWT呢?

  是為了實作數字簽名,如果黑客竊取到身份令牌,暴力枚舉令牌,是有機會洩露的,使用JWT後,sso可以驗證這是否被篡改。

5、為什麼要重定向回子系統?

  首先A系統登入時是在自己的登入界面,然後直接送出到sso驗證中心,這個時候這個請求是浏覽器和sso建立的聯系,跟子系統沒關系,直接傳回或者轉發什麼的是沒辦法回到子系統的。

6、為什麼登入要直接送出到SSO驗證中心?(内心os:我感覺我快成了杠精,可能我适合去工地,2333 )

  因為身份令牌要儲存在浏覽器用戶端,儲存在cookie中,也就是說sso此時要拿到浏覽器請求才能給浏覽器的父域種下cookie,如果是通過子系統再送出請求,那樣是種不了cookie的,當然如果是登入請求送出到子系統,子系統發出驗證,由子系統去種cookie也是可以的,但是這樣的話你内聚就降低了很多,耦合度也高了,子系統原本不用管的登入的事也要管了,sso原本要全管的登入被别人幹了一部分。

簡單看下代碼

SSO的統一登入代碼

/**
     * 統一驗證登入中心
     * @param username 使用者名
     * @param password 密碼
     */
    @PostMapping
    public void checkLogin(String username, String password, String returnUrl,HttpServletResponse response) {
        TbUser user = userService.login(username, password);
        log.info("user: "+user);
        String jwtValue = null;
        if (user != null) {
            /*
             * 獲得uuid,作為tokenId(TGC)
             * 将tokenId存進jedis,傳回用戶端以jwt存儲的tokenId,
             * 即使tokenId被截取也無所謂
             */
            String tokenId = UUID.randomUUID().toString();
            //生成jwt
            jwtValue = new JwtUtil(tokenId, null).creatJwt();
            log.info("tokenId: " + tokenId+" jwt: "+jwtValue);
            if (jwtValue != null) {
                Jedis jedis = jedisPool.getResource();
                jedis.set(tokenId, user.toString());
                jedis.expire(tokenId,1800);
                log.info("檢視key的剩餘生存時間:"+jedis.ttl(tokenId));
            }
        }
        //将jwt加密的TGC存進cookie
        Cookie cookie = new Cookie("tokenId", jwtValue);
        cookie.setPath("/"); //設定根域名
        cookie.setHttpOnly(true);
        response.addCookie(cookie);
        try {
            response.sendRedirect(returnUrl);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
           

子系統使用身份令牌驗證

/**
     * 驗證wlId
     * 用jedis的原因是不用的話無法從所有session會話中找到sessionid為tokenid的會話
     * @param wlId wlId
     * @return  如果失效傳回null,反之傳回aes加密的使用者資訊
     */
    @GetMapping
    public String tokenCheck (String wlId) {
        //從jedis中獲得token
        Jedis jedis = jedisPool.getResource();
        String user = null;
        if (wlId != null) {
            wlId = new JwtUtil(null, wlId).decryptJwt();
            user = jedis.get(wlId);
            jedis.expire(wlId,1800);
            log.info("tokencheck檢視key的剩餘生存時間:"+jedis.ttl(wlId));
        }
        return user;
    }

           

登出的方法

/**
     * 清除全局會話,子系統跳轉到回到登入頁的方法
     * @param wlId 身份令牌
     */
    @GetMapping("/loginout")
    public void loginOut(String wlId) {
        log.info("clear token");
        Jedis jedis = jedisPool.getResource();
        jedis.del(wlId);
    }


           

與我另一篇完全跨域sso對比

  首先附上篇文章連結:java實作完全跨域SSO單點登入

  1、在以後你們的系統可能短時間内不會使用多個根域名的話,建議使用該版本或者修改該版本。

  2、兩者都可以實作單點登入,我已經自己試驗過了。并且另一個版本有github代碼提供。

  3、上篇是完全跨域SSO,即使以後使用不同的域名,完全沒問題。該版本隻适用于同父域單點登入。

  4、上篇雖然是完全跨域sso,但是sso需要多元護一個子系統表,用來記錄向子系統添加cookie的連結,該篇不需要。

  5、當以後使用者量大,業務拓展的時候,第一個登入的子系統不僅要使用更多的時間驗證使用者是否正确,并且還要花時間對那麼多個子系統進行重定向種cookie,雖然可以先跳轉到使用者首頁再去請求,但是也會造成第一個登陸的登入速度慢一些,使用者體驗可能就不那麼好。

在這個版本上補充完全跨域SSO内容

這個補充是我後來想的,我并未實踐,但是我覺得可行,歡迎留言或加q探讨

先看下草圖

java實作 SSO 單點登入(最終版)--補充完全跨域SSO前言同父域單點登入在這個版本上補充完全跨域SSO内容

有a,b兩個系統,域名為a.com,b.com。然後sso為 sso.com。首先都處于未登入狀态a系統和b系統都是自己獨立的登入界面。

1、a首先去請求,然後a的server發現未登入,這個時候首先重定向sso.checkTokenId,這樣sso發現cookie下确實沒有種下身份令牌這樣就重定向回a.login界面

2、a在登入界面送出賬号密碼到達sso.login方法,sso驗證資訊正确,那麼就為sso.com的cookie種下身份令牌這個就是全局會話,重定向回最初的通路連結,并攜帶身份令牌,在a處生成了局部會話。

3、現在用戶端再去通路b.com下的頁面,這個時候b.server發現沒有局部會話,未登入,這個時候首先重定向到sso.checkTokenId,sso驗證是否有全局的身份令牌

4、sso驗證有身份令牌,那麼就傳回b最初的通路連結,攜帶身份令牌,在b處生成局部會話,b處也就成了已登入

5、sso驗證沒有身份令牌,那麼就重新到b.com下的b.login界面,重複2步驟,最終生成全局會話和局部會話

涉及到的問題

1、每次重定向攜帶資訊,這個時候可以使用302重定向跳轉,可以實作get重定向并攜帶參數

2、sso系統加了一個checkTokenId方法就是精華,這樣每次判斷是否有會話,就可以重定向到此獲得sso.com的cookie,這樣就可以得到全局的身份令牌

3、此時各個子系統使用的是不同的登入界面,如果想要使用統一的登入界面,依然要采用本設計,因為如果隻是單純的靠統一的登入界面來獲得sso.com的全局會話,這樣做不到使用者無感覺,加一個方法就是為了不做頁面跳轉。

繼續閱讀