[轉]單點登陸原理

單點登陸原理與簡單實現

 轉載地址:https://www.cnblogs.com/ywlaker/p/6113927.htmlhttps://www.cnblogs.com/ywlaker/p/6113927.html

(2017-09-22更新)GitHub:https://github.com/sheefee/simple-ssophp

1、單系統登陸機制

一、http無狀態協議

  web應用採用browser/server架構,http做爲通訊協議。http是無狀態協議,瀏覽器的每一次請求,服務器會獨立處理,不與以前或以後的請求產生關聯,這個過程用下圖說明,三次請求/響應對之間沒有任何聯繫html

3c91a3bf-25d8-4b1f-8e4a-68535c51aaa8

  但這也同時意味着,任何用戶都能經過瀏覽器訪問服務器資源,若是想保護服務器的某些資源,必須限制瀏覽器請求;要限制瀏覽器請求,必須鑑別瀏覽器請求,響應合法請求,忽略非法請求;要鑑別瀏覽器請求,必須清楚瀏覽器請求狀態。既然http協議無狀態,那就讓服務器和瀏覽器共同維護一個狀態吧!這就是會話機制java

二、會話機制

  瀏覽器第一次請求服務器,服務器建立一個會話,並將會話的id做爲響應的一部分發送給瀏覽器,瀏覽器存儲會話id,並在後續第二次和第三次請求中帶上會話id,服務器取得請求中的會話id就知道是否是同一個用戶了,這個過程用下圖說明,後續請求與第一次請求產生了關聯git

8a9fb230-d506-4b19-b821-4001c68c4588

  服務器在內存中保存會話對象,瀏覽器怎麼保存會話id呢?你可能會想到兩種方式github

  1. 請求參數
  2. cookie

  將會話id做爲每個請求的參數,服務器接收請求天然能解析參數得到會話id,並藉此判斷是否來自同一會話,很明顯,這種方式不靠譜。那就瀏覽器本身來維護這個會話id吧,每次發送http請求時瀏覽器自動發送會話id,cookie機制正好用來作這件事。cookie是瀏覽器用來存儲少許數據的一種機制,數據以」key/value「形式存儲,瀏覽器發送http請求時自動附帶cookie信息web

  tomcat會話機制固然也實現了cookie,訪問tomcat服務器時,瀏覽器中能夠看到一個名爲「JSESSIONID」的cookie,這就是tomcat會話機制維護的會話id,使用了cookie的請求響應過程以下圖redis

518293d9-64b2-459c-9d45-9f353c757d1f

三、登陸狀態

  有了會話機制,登陸狀態就好明白了,咱們假設瀏覽器第一次請求服務器須要輸入用戶名與密碼驗證身份,服務器拿到用戶名密碼去數據庫比對,正確的話說明當前持有這個會話的用戶是合法用戶,應該將這個會話標記爲「已受權」或者「已登陸」等等之類的狀態,既然是會話的狀態,天然要保存在會話對象中,tomcat在會話對象中設置登陸狀態以下數據庫

1
2
HttpSession session = request.getSession();
session.setAttribute( "isLogin" true );

  用戶再次訪問時,tomcat在會話對象中查看登陸狀態api

1
2
HttpSession session = request.getSession();
session.getAttribute( "isLogin" );

  實現了登陸狀態的瀏覽器請求服務器模型以下圖描述瀏覽器

70e396fa-1bf2-42f8-a504-ce20306e31fa

  每次請求受保護資源時都會檢查會話對象中的登陸狀態,只有 isLogin=true 的會話才能訪問,登陸機制所以而實現。

2、多系統的複雜性

  web系統早已從久遠的單系統發展成爲現在由多系統組成的應用羣,面對如此衆多的系統,用戶難道要一個一個登陸、而後一個一個註銷嗎?就像下圖描述的這樣

6dfbb0b1-46c0-4945-a3bf-5f060fa80710

  web系統由單系統發展成多系統組成的應用羣,複雜性應該由系統內部承擔,而不是用戶。不管web系統內部多麼複雜,對用戶而言,都是一個統一的總體,也就是說,用戶訪問web系統的整個應用羣與訪問單個系統同樣,登陸/註銷只要一次就夠了

9fe14ab3-4254-447b-b850-0436e628c254

  雖然單系統的登陸解決方案很完美,但對於多系統應用羣已經再也不適用了,爲何呢?

  單系統登陸解決方案的核心是cookie,cookie攜帶會話id在瀏覽器與服務器之間維護會話狀態。但cookie是有限制的,這個限制就是cookie的域(一般對應網站的域名),瀏覽器發送http請求時會自動攜帶與該域匹配的cookie,而不是全部cookie

4d58ccfa-0114-486d-bec2-c28f2f9eb513

  既然這樣,爲何不將web應用羣中全部子系統的域名統一在一個頂級域名下,例如「*.baidu.com」,而後將它們的cookie域設置爲「baidu.com」,這種作法理論上是能夠的,甚至早期不少多系統登陸就採用這種同域名共享cookie的方式。

  然而,可行並不表明好,共享cookie的方式存在衆多侷限。首先,應用羣域名得統一;其次,應用羣各系統使用的技術(至少是web服務器)要相同,否則cookie的key值(tomcat爲JSESSIONID)不一樣,沒法維持會話,共享cookie的方式是沒法實現跨語言技術平臺登陸的,好比java、php、.net系統之間;第三,cookie自己不安全。

  所以,咱們須要一種全新的登陸方式來實現多系統應用羣的登陸,這就是單點登陸

3、單點登陸

  什麼是單點登陸?單點登陸全稱Single Sign On(如下簡稱SSO),是指在多系統應用羣中登陸一個系統,即可在其餘全部系統中獲得受權而無需再次登陸,包括單點登陸與單點註銷兩部分

一、登陸

  相比於單系統登陸,sso須要一個獨立的認證中心,只有認證中心能接受用戶的用戶名密碼等安全信息,其餘系統不提供登陸入口,只接受認證中心的間接受權。間接受權經過令牌實現,sso認證中心驗證用戶的用戶名密碼沒問題,建立受權令牌,在接下來的跳轉過程當中,受權令牌做爲參數發送給各個子系統,子系統拿到令牌,即獲得了受權,能夠藉此建立局部會話,局部會話登陸方式與單系統的登陸方式相同。這個過程,也就是單點登陸的原理,用下圖說明

  下面對上圖簡要描述

  1. 用戶訪問系統1的受保護資源,系統1發現用戶未登陸,跳轉至sso認證中心,並將本身的地址做爲參數
  2. sso認證中心發現用戶未登陸,將用戶引導至登陸頁面
  3. 用戶輸入用戶名密碼提交登陸申請
  4. sso認證中心校驗用戶信息,建立用戶與sso認證中心之間的會話,稱爲全局會話,同時建立受權令牌
  5. sso認證中心帶着令牌跳轉會最初的請求地址(系統1)
  6. 系統1拿到令牌,去sso認證中心校驗令牌是否有效
  7. sso認證中心校驗令牌,返回有效,註冊系統1
  8. 系統1使用該令牌建立與用戶的會話,稱爲局部會話,返回受保護資源
  9. 用戶訪問系統2的受保護資源
  10. 系統2發現用戶未登陸,跳轉至sso認證中心,並將本身的地址做爲參數
  11. sso認證中心發現用戶已登陸,跳轉回系統2的地址,並附上令牌
  12. 系統2拿到令牌,去sso認證中心校驗令牌是否有效
  13. sso認證中心校驗令牌,返回有效,註冊系統2
  14. 系統2使用該令牌建立與用戶的局部會話,返回受保護資源

  用戶登陸成功以後,會與sso認證中心及各個子系統創建會話,用戶與sso認證中心創建的會話稱爲全局會話,用戶與各個子系統創建的會話稱爲局部會話,局部會話創建以後,用戶訪問子系統受保護資源將再也不經過sso認證中心,全局會話與局部會話有以下約束關係

  1. 局部會話存在,全局會話必定存在
  2. 全局會話存在,局部會話不必定存在
  3. 全局會話銷燬,局部會話必須銷燬

  你能夠經過博客園、百度、csdn、淘寶等網站的登陸過程加深對單點登陸的理解,注意觀察登陸過程當中的跳轉url與參數

二、註銷

  單點登陸天然也要單點註銷,在一個子系統中註銷,全部子系統的會話都將被銷燬,用下面的圖來講明

3b139d2e-0b83-4a69-b4f2-316adb8997ce

  sso認證中心一直監聽全局會話的狀態,一旦全局會話銷燬,監聽器將通知全部註冊系統執行註銷操做

  下面對上圖簡要說明

  1. 用戶向系統1發起註銷請求
  2. 系統1根據用戶與系統1創建的會話id拿到令牌,向sso認證中心發起註銷請求
  3. sso認證中心校驗令牌有效,銷燬全局會話,同時取出全部用此令牌註冊的系統地址
  4. sso認證中心向全部註冊系統發起註銷請求
  5. 各註冊系統接收sso認證中心的註銷請求,銷燬局部會話
  6. sso認證中心引導用戶至登陸頁面

4、部署圖

  單點登陸涉及sso認證中心與衆子系統,子系統與sso認證中心須要通訊以交換令牌、校驗令牌及發起註銷請求,於是子系統必須集成sso的客戶端,sso認證中心則是sso服務端,整個單點登陸過程實質是sso客戶端與服務端通訊的過程,用下圖描述

fb29685c-487c-42b9-9ceb-6c7ee29e98c9

  sso認證中心與sso客戶端通訊方式有多種,這裏以簡單好用的httpClient爲例,web service、rpc、restful api均可以

5、實現

  只是簡要介紹下基於java的實現過程,不提供完整源碼,明白了原理,我相信大家能夠本身實現。sso採用客戶端/服務端架構,咱們先看sso-client與sso-server要實現的功能(下面:sso認證中心=sso-server)

  sso-client

  1. 攔截子系統未登陸用戶請求,跳轉至sso認證中心
  2. 接收並存儲sso認證中心發送的令牌
  3. 與sso-server通訊,校驗令牌的有效性
  4. 創建局部會話
  5. 攔截用戶註銷請求,向sso認證中心發送註銷請求
  6. 接收sso認證中心發出的註銷請求,銷燬局部會話

  sso-server

  1. 驗證用戶的登陸信息
  2. 建立全局會話
  3. 建立受權令牌
  4. 與sso-client通訊發送令牌
  5. 校驗sso-client令牌有效性
  6. 系統註冊
  7. 接收sso-client註銷請求,註銷全部會話

  接下來,咱們按照原理來一步步實現sso吧!

一、sso-client攔截未登陸請求

  java攔截請求的方式有servlet、filter、listener三種方式,咱們採用filter。在sso-client中新建LoginFilter.java類並實現Filter接口,在doFilter()方法中加入對未登陸用戶的攔截

1
2
3
4
5
6
7
8
9
10
11
12
public  void  doFilter(ServletRequest request, ServletResponse response, FilterChain chain)  throws  IOException, ServletException {
     HttpServletRequest req = (HttpServletRequest) request;
     HttpServletResponse res = (HttpServletResponse) response;
     HttpSession session = req.getSession();
     
     if  (session.getAttribute( "isLogin" )) {
         chain.doFilter(request, response);
         return ;
     }
     //跳轉至sso認證中心
     res.sendRedirect( "sso-server-url-with-system-url" );
}

二、sso-server攔截未登陸請求

  攔截從sso-client跳轉至sso認證中心的未登陸請求,跳轉至登陸頁面,這個過程與sso-client徹底同樣

三、sso-server驗證用戶登陸信息

  用戶在登陸頁面輸入用戶名密碼,請求登陸,sso認證中心校驗用戶信息,校驗成功,將會話狀態標記爲「已登陸」

1
2
3
4
5
6
@RequestMapping ( "/login" )
public  String login(String username, String password, HttpServletRequest req) {
     this .checkLoginInfo(username, password);
     req.getSession().setAttribute( "isLogin" true );
     return  "success" ;
}

四、sso-server建立受權令牌

  受權令牌是一串隨機字符,以什麼樣的方式生成都沒有關係,只要不重複、不易僞造便可,下面是一個例子

1
String token = UUID.randomUUID().toString();

五、sso-client取得令牌並校驗

  sso認證中心登陸後,跳轉回子系統並附上令牌,子系統(sso-client)取得令牌,而後去sso認證中心校驗,在LoginFilter.java的doFilter()中添加幾行

1
2
3
4
5
6
7
8
9
10
11
// 請求附帶token參數
String token = req.getParameter( "token" );
if  (token !=  null ) {
     // 去sso認證中心校驗token
     boolean  verifyResult =  this .verify( "sso-server-verify-url" , token);
     if  (!verifyResult) {
         res.sendRedirect( "sso-server-url" );
         return ;
     }
     chain.doFilter(request, response);
}

  verify()方法使用httpClient實現,這裏僅簡略介紹,httpClient詳細使用方法請參考官方文檔

1
2
HttpPost httpPost =  new  HttpPost( "sso-server-verify-url-with-token" );
HttpResponse httpResponse = httpClient.execute(httpPost);

六、sso-server接收並處理校驗令牌請求

  用戶在sso認證中心登陸成功後,sso-server建立受權令牌並存儲該令牌,因此,sso-server對令牌的校驗就是去查找這個令牌是否存在以及是否過時,令牌校驗成功後sso-server將發送校驗請求的系統註冊到sso認證中心(就是存儲起來的意思)

  令牌與註冊系統地址一般存儲在key-value數據庫(如redis)中,redis能夠爲key設置有效時間也就是令牌的有效期。redis運行在內存中,速度很是快,正好sso-server不須要持久化任何數據。

  令牌與註冊系統地址能夠用下圖描述的結構存儲在redis中,可能你會問,爲何要存儲這些系統的地址?若是不存儲,註銷的時候就麻煩了,用戶向sso認證中心提交註銷請求,sso認證中心註銷全局會話,但不知道哪些系統用此全局會話創建了本身的局部會話,也不知道要向哪些子系統發送註銷請求註銷局部會話

3b221593-f9c4-45af-a567-4937786993e8

七、sso-client校驗令牌成功建立局部會話

  令牌校驗成功後,sso-client將當前局部會話標記爲「已登陸」,修改LoginFilter.java,添加幾行

1
2
3
if  (verifyResult) {
     session.setAttribute( "isLogin" true );
}

  sso-client還需將當前會話id與令牌綁定,表示這個會話的登陸狀態與令牌相關,此關係能夠用java的hashmap保存,保存的數據用來處理sso認證中心發來的註銷請求

八、註銷過程

  用戶向子系統發送帶有「logout」參數的請求(註銷請求),sso-client攔截器攔截該請求,向sso認證中心發起註銷請求

1
2
3
4
String logout = req.getParameter( "logout" );
if  (logout !=  null ) {
     this .ssoServer.logout(token);
}

  sso認證中心也用一樣的方式識別出sso-client的請求是註銷請求(帶有「logout」參數),sso認證中心註銷全局會話

1
2
3
4
5
6
7
8
@RequestMapping ( "/logout" )
public  String logout(HttpServletRequest req) {
     HttpSession session = req.getSession();
     if  (session !=  null ) {
         session.invalidate(); //觸發LogoutListener
     }
     return  "redirect:/" ;
}

  sso認證中心有一個全局會話的監聽器,一旦全局會話註銷,將通知全部註冊系統註銷

1
2
3
4
5
6
7
8
public  class  LogoutListener  implements  HttpSessionListener {
     @Override
     public  void  sessionCreated(HttpSessionEvent event) {}
     @Override
     public  void  sessionDestroyed(HttpSessionEvent event) {
         //經過httpClient向全部註冊系統發送註銷請求
     }
}

(完)

做者:凌承一 
出處:http://www.cnblogs.com/ywlaker/ 聲明:本文版權歸做者和博客園共有,歡迎轉載,但轉載必須保留此段聲明,並在文章頁面明顯位置給出原文連接,不然做者將保留追究法律責任的權利。

相關文章
相關標籤/搜索