sso源碼
『壹』 單點登錄SSO原理和實現
歡迎大家關注?github.com/hsfxuebao?,希望對大家有所幫助,要是覺得可以的話麻煩給點一下Star哈
1. 單系統登錄機制1.1 http無狀態協議web應用採用browser/server架構,http作為通信協議。http是無狀態協議,瀏覽器的每一次請求,伺服器會獨立處理,不與之前或之後的請求產生關聯,這個過程用下圖說明,三次請求/響應對之間沒有任何聯系
但這也同時意味著,任何用戶都能通過瀏覽器訪問伺服器資源,如果想保護伺服器的某些資源,必須限制瀏覽器請求;要限制瀏覽器請求,必須鑒別瀏覽器請求,響應合法請求,忽略非法請求;要鑒別瀏覽器請求,必須清楚瀏覽器請求狀態。既然http協議無狀態,那就讓伺服器和瀏覽器共同維護一個狀態吧!這就是會話機制
1.2 會話機制瀏覽器第一次請求伺服器,伺服器創建一個會話,並將會話的id作為響應的一部分發送給瀏覽器,瀏覽器存儲會話id,並在後續第二次和第三次請求中帶上會話id,伺服器取得請求中的會話id就知道是不是同一個用戶了,這個過程用下圖說明,後續請求與第一次請求產生了關聯
伺服器在內存中保存會話對象,瀏覽器怎麼保存會話id呢?你可能會想到兩種方式:
請求參數
cookie
將會話id作為每一個請求的參數,伺服器接收請求自然能解析參數獲得會話id,並藉此判斷是否來自同一會話,很明顯,這種方式不靠譜。那就瀏覽器自己來維護這個會話id吧,每次發送http請求時瀏覽器自動發送會話id,cookie機制正好用來做這件事。cookie是瀏覽器用來存儲少量數據的一種機制,數據以」key/value「形式存儲,瀏覽器發送http請求時自動附帶cookie信息
tomcat會話機制當然也實現了cookie,訪問tomcat伺服器時,瀏覽器中可以看到一個名為「JSESSIONID」的cookie,這就是tomcat會話機制維護的會話id,使用了cookie的請求響應過程如下圖
1.3 登錄狀態有了會話機制,登錄狀態就好明白了,我們假設瀏覽器第一次請求伺服器需要輸入用戶名與密碼驗證身份,伺服器拿到用戶名密碼去資料庫比對,正確的話說明當前持有這個會話的用戶是合法用戶,應該將這個會話標記為「已授權」或者「已登錄」等等之類的狀態,既然是會話的狀態,自然要保存在會話對象中,tomcat在會話對象中設置登錄狀態如下
HttpSession?session?=?request.getSession();session.setAttribute("isLogin",?true);用戶再次訪問時,tomcat在會話對象中查看登錄狀態
HttpSession?session?=?request.getSession();session.getAttribute("isLogin");實現了登錄狀態的瀏覽器請求伺服器模型如下圖描述
每次請求受保護資源時都會檢查會話對象中的登錄狀態,只有 isLogin=true 的會話才能訪問,登錄機制因此而實現。
2. 多系統的復雜性web系統早已從久遠的單系統發展成為如今由多系統組成的應用群,面對如此眾多的系統,用戶難道要一個一個登錄、然後一個一個注銷嗎?就像下圖描述的這樣
web系統由單系統發展成多系統組成的應用群,復雜性應該由系統內部承擔,而不是用戶。無論web系統內部多麼復雜,對用戶而言,都是一個統一的整體,也就是說,用戶訪問web系統的整個應用群與訪問單個系統一樣,登錄/注銷只要一次就夠了
2.1 為什麼會出現跨域問題瀏覽器從一個域名的網頁去請求另一個域名的資源時,域名、埠、協議任一不同,都是跨域。在前後端分離的模式下,前後端的域名是不一致的,此時就會發生跨域訪問問題。在請求的過程中我們要想回去數據一般都是post/get請求,所以..跨域問題出現。出於瀏覽器的同源策略限制。
同源策略(Sameoriginpolicy)是一種約定,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,則瀏覽器的正常功能可能都會受到影響。
可以說Web是構建在同源策略基礎之上的,瀏覽器只是針對同源策略的一種實現。
同源策略會阻止一個域的javascript腳本和另外一個域的內容進行交互。
所謂同源(即指在同一個域)就是兩個頁面具有相同的協議(protocol),主機(host)和埠號(port)
2.2 什麼是跨域當一個請求 url 的協議、域名、埠三者之間任意一個與當前頁面 url 不同即為跨域
當前頁面url 被請求頁面url 是否跨域 原因
http://www.test.com/ 和?http://www.test.com/index.html ?否 同源(協議、域名、埠號相同)
http://www.test.com/ 和?https://www.test.com/index.html?跨域 協議不同(http/https)
http://www.test.com/ 和?網路一下,你就知道?跨域 主域名不同(test/)
http://www.test.com/? 和 http://blog.test.com/?跨域 子域名不同(www/blog)
http://www.test.com:8080/?和 http://www.test.com:7001/?跨域 埠號不同(8080/7001)
2.3 多系統解決方案雖然單系統的登錄解決方案很完美,但對於多系統應用群已經不再適用了,為什麼呢?
單系統登錄解決方案的核心是cookie,cookie攜帶會話id在瀏覽器與伺服器之間維護會話狀態。但cookie是有限制的,這個限制就是cookie的域(通常對應網站的域名),瀏覽器發送http請求時會自動攜帶與該域匹配的cookie,而不是所有cookie
既然這樣,為什麼不將web應用群中所有子系統的域名統一在一個頂級域名下,例如「*..com」,然後將它們的cookie域設置為「.com」,這種做法理論上是可以的,甚至早期很多多系統登錄就採用這種同域名共享cookie的方式。然而,可行並不代表好,共享cookie的方式存在眾多局限:
首先,應用群域名得統一;
其次,應用群各系統使用的技術(至少是web伺服器)要相同,不然cookie的key值(tomcat為JSESSIONID)不同,無法維持會話,共享cookie的方式是無法實現跨語言技術平台登錄的,比如java、php、.net系統之間;
第三,cookie本身不安全。
因此,我們需要一種全新的登錄方式來實現多系統應用群的登錄,這就是單點登錄
3. 單點登錄什麼是單點登錄?單點登錄全稱Single Sign On(以下簡稱SSO),是指在多系統應用群中登錄一個系統,便可在其他所有系統中得到授權而無需再次登錄,包括單點登錄與單點注銷兩部分
3.1 登錄相比於單系統登錄,sso需要一個獨立的認證中心,只有認證中心能接受用戶的用戶名密碼等安全信息,其他系統不提供登錄入口,只接受認證中心的間接授權。間接授權通過令牌實現,sso認證中心驗證用戶的用戶名密碼沒問題,創建授權令牌,在接下來的跳轉過程中,授權令牌作為參數發送給各個子系統,子系統拿到令牌,即得到了授權,可以藉此創建局部會話,局部會話登錄方式與單系統的登錄方式相同。這個過程,也就是單點登錄的原理,用下圖說明
下面對上圖簡要描述:
用戶訪問系統1的受保護資源,系統1發現用戶未登錄,跳轉至sso認證中心,並將自己的地址作為參數
sso認證中心發現用戶未登錄,將用戶引導至登錄頁面
用戶輸入用戶名密碼提交登錄申請
sso認證中心校驗用戶信息,創建用戶與sso認證中心之間的會話,稱為全局會話,同時創建授權令牌
sso認證中心帶著令牌跳轉會最初的請求地址(系統1)
系統1拿到令牌,去sso認證中心校驗令牌是否有效
sso認證中心校驗令牌,返回有效,注冊系統1
系統1使用該令牌創建與用戶的會話,稱為局部會話,返回受保護資源
用戶訪問系統2的受保護資源
系統2發現用戶未登錄,跳轉至sso認證中心,並將自己的地址作為參數
sso認證中心發現用戶已登錄,跳轉回系統2的地址,並附上令牌
系統2拿到令牌,去sso認證中心校驗令牌是否有效
sso認證中心校驗令牌,返回有效,注冊系統2
系統2使用該令牌創建與用戶的局部會話,返回受保護資源
用戶登錄成功之後,會與sso認證中心及各個子系統建立會話,用戶與sso認證中心建立的會話稱為全局會話,用戶與各個子系統建立的會話稱為局部會話,局部會話建立之後,用戶訪問子系統受保護資源將不再通過sso認證中心,全局會話與局部會話有如下約束關系:
局部會話存在,全局會話一定存在
全局會話存在,局部會話不一定存在
全局會話銷毀,局部會話必須銷毀
你可以通過博客園、網路、csdn、淘寶等網站的登錄過程加深對單點登錄的理解,注意觀察登錄過程中的跳轉url與參數
3.2 注銷單點登錄自然也要單點注銷,在一個子系統中注銷,所有子系統的會話都將被銷毀,用下面的圖來說明
sso認證中心一直監聽全局會話的狀態,一旦全局會話銷毀,監聽器將通知所有注冊系統執行注銷操作
下面對上圖簡要說明:
用戶向系統1發起注銷請求
系統1根據用戶與系統1建立的會話id拿到令牌,向sso認證中心發起注銷請求
sso認證中心校驗令牌有效,銷毀全局會話,同時取出所有用此令牌注冊的系統地址
sso認證中心向所有注冊系統發起注銷請求
各注冊系統接收sso認證中心的注銷請求,銷毀局部會話
sso認證中心引導用戶至登錄頁面
4. 實現只是簡要介紹下基於java的實現過程,不提供完整源碼,明白了原理,我相信你們可以自己實現。sso採用客戶端/服務端架構,我們先看sso-client與sso-server要實現的功能(下面:sso認證中心=sso-server)
sso-client:
攔截子系統未登錄用戶請求,跳轉至sso認證中心
接收並存儲sso認證中心發送的令牌
與sso-server通信,校驗令牌的有效性
建立局部會話
攔截用戶注銷請求,向sso認證中心發送注銷請求
接收sso認證中心發出的注銷請求,銷毀局部會話
sso-server:
驗證用戶的登錄信息
創建全局會話
創建授權令牌
與sso-client通信發送令牌
校驗sso-client令牌有效性
系統注冊
接收sso-client注銷請求,注銷所有會話
接下來,我們按照原理來一步步實現sso吧!
4.1 sso-client攔截未登錄請求java攔截請求的方式有servlet、filter、listener三種方式,我們採用filter。在sso-client中新建LoginFilter.java類並實現Filter介面,在doFilter()方法中加入對未登錄用戶的攔截
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");}4.2 sso-server攔截未登錄請求攔截從sso-client跳轉至sso認證中心的未登錄請求,跳轉至登錄頁面,這個過程與sso-client完全一樣
4.3 sso-server驗證用戶登錄信息用戶在登錄頁面輸入用戶名密碼,請求登錄,sso認證中心校驗用戶信息,校驗成功,將會話狀態標記為「已登錄」
@RequestMapping("/login")public?String?login(String?username,?String?password,?HttpServletRequest?req)?{????this.checkLoginInfo(username,?password);????req.getSession().setAttribute("isLogin",?true);????return?"success";}4.4、sso-server創建授權令牌授權令牌是一串隨機字元,以什麼樣的方式生成都沒有關系,只要不重復、不易偽造即可,下面是一個例子
String?token?=?UUID.randomUUID().toString();4.5、sso-client取得令牌並校驗sso認證中心登錄後,跳轉回子系統並附上令牌,子系統(sso-client)取得令牌,然後去sso認證中心校驗,在LoginFilter.java的doFilter()中添加幾行
//?請求附帶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詳細使用方法請參考官方文檔
HttpPost?httpPost?=?new?HttpPost("sso-server-verify-url-with-token");HttpResponse?httpResponse?=?httpClient.execute(httpPost);4.6 sso-server接收並處理校驗令牌請求用戶在sso認證中心登錄成功後,sso-server創建授權令牌並存儲該令牌,所以,sso-server對令牌的校驗就是去查找這個令牌是否存在以及是否過期,令牌校驗成功後sso-server將發送校驗請求的系統注冊到sso認證中心(就是存儲起來的意思)
令牌與注冊系統地址通常存儲在key-value資料庫(如redis)中,redis可以為key設置有效時間也就是令牌的有效期。redis運行在內存中,速度非常快,正好sso-server不需要持久化任何數據。
令牌與注冊系統地址可以用下圖描述的結構存儲在redis中,可能你會問,為什麼要存儲這些系統的地址?如果不存儲,注銷的時候就麻煩了,用戶向sso認證中心提交注銷請求,sso認證中心注銷全局會話,但不知道哪些系統用此全局會話建立了自己的局部會話,也不知道要向哪些子系統發送注銷請求注銷局部會話
4.7 sso-client校驗令牌成功創建局部會話令牌校驗成功後,sso-client將當前局部會話標記為「已登錄」,修改LoginFilter.java,添加幾行
if?(verifyResult)?{????session.setAttribute("isLogin",?true);}sso-client還需將當前會話id與令牌綁定,表示這個會話的登錄狀態與令牌相關,此關系可以用java的hashmap保存,保存的數據用來處理sso認證中心發來的注銷請求
4.8、注銷過程用戶向子系統發送帶有「logout」參數的請求(注銷請求),sso-client攔截器攔截該請求,向sso認證中心發起注銷請求
String?logout?=?req.getParameter("logout");if?(logout?!=?null)?{????this.ssoServer.logout(token);}sso認證中心也用同樣的方式識別出sso-client的請求是注銷請求(帶有「logout」參數),sso認證中心注銷全局會話
@RequestMapping("/logout")public?String?logout(HttpServletRequest?req)?{????HttpSession?session?=?req.getSession();????if?(session?!=?null)?{????????session.invalidate();//觸發LogoutListener????}????return?"redirect:/";}sso認證中心有一個全局會話的監聽器,一旦全局會話注銷,將通知所有注冊系統注銷
HttpSession?session?=?request.getSession();session.getAttribute("isLogin");05. 快速接入SSO5.1 xxl-sso特性簡潔:API直觀簡潔,可快速上手
輕量級:環境依賴小,部署與接入成本較低
單點登錄:只需要登錄一次就可以訪問所有相互信任的應用系統
分布式:接入SSO認證中心的應用,支持分布式部署
HA:Server端與Client端,均支持集群部署,提高系統可用性
跨域:支持跨域應用接入SSO認證中心
Cookie+Token均支持:支持基於Cookie和基於Token兩種接入方式,並均提供Sample項目
Web+APP均支持:支持Web和APP接入
實時性:系統登陸、注銷狀態,全部Server與Client端實時共享
CS結構:基於CS結構,包括Server"認證中心"與Client"受保護應用"
記住密碼:未記住密碼時,關閉瀏覽器則登錄態失效;記住密碼時,支持登錄態自動延期,在自定義延期時間的基礎上,原則上可以無限延期
路徑排除:支持自定義多個排除路徑,支持Ant表達式,用於排除SSO客戶端不需要過濾的路徑
5.2 環境JDK:1.7+
Redis:4.0+
5.3 下載xxl-sso源碼源碼倉庫地址Release Downloadgithub.com/xuxueli/xxl…Downloadgitee.com/xuxueli0323…Download5.4 文檔地址中文文檔
5.5 項目結構說明HttpSession?session?=?request.getSession();session.getAttribute("isLogin");15.6 基於Token方式部署由於前後端分離開發的模式較多,這里只介紹基於Token方式部署,在一些無法使用Cookie的場景下,可使用該方式,如需要Cookie查看基於cookie方式部署
5.6.1 認證中心(SSO Server)搭建項目名:xxl-sso-server
配置文件位置:application.properties
HttpSession?session?=?request.getSession();session.getAttribute("isLogin");25.6.2 單點登陸Client端搭建項目名:xxl-sso-token-sample-springboot
maven依賴
HttpSession?session?=?request.getSession();session.getAttribute("isLogin");3配置文件:application.properties
HttpSession?session?=?request.getSession();session.getAttribute("isLogin");4配置 XxlSsoTokenFilter
HttpSession?session?=?request.getSession();session.getAttribute("isLogin");55.6.3 驗證 (模擬請求 Token 方式接入SSO的介面)修改Host文件:域名方式訪問認證中心,模擬跨域與線上真實環境
HttpSession?session?=?request.getSession();session.getAttribute("isLogin");6分別運行 "xxl-sso-server" 與 "xxl-sso-token-sample-springboot"
認證中心搭建成功後,默認為Token方式登陸提供API介面:
1、登陸介面:/app/login
code:200 表示成功、其他失敗
msg:錯誤提示
data: 登陸用戶的 sso sessionid
username:賬號
password:賬號
參數:POST參數
響應:JSON格式
2、注銷介面:/app/logout
code:200 表示成功、其他失敗
msg:錯誤提示
sessionId:登陸用戶的 sso sessionid
參數:POST參數
響應:JSON格式
3、登陸狀態校驗介面:/app/l
『貳』 一些推薦的CCFCSP認證試題參考網址
首先貼上歷年真題刷題網址,就是在官網上哈
http://118.190.20.162/
https://passport.ccf.org.cn/sso/login?from===
其實kuangbin那本書還有點看不懂,附上其它的演算法模板
演算法褲老模胡扒升板
源碼:
學院老師寫的
github中找到的好東西
學院大佬們和老師的鏈接,菜雞學弟眯著眼偷偷看此拿嘿嘿
https://blog.csdn.net/wl16wzl
https://blog.csdn.net/tigerisland45
https://blog.csdn.net/Miranda_ymz
https://me.csdn.net/cfarmerreally