以前在學校做項目的時候,登錄注銷,權限驗證這些事情,都是交給框架來做的,每次都是把這個架子拿到項目中去,也沒有真正思考過它的過程,總覺的這些都是十分簡單的邏輯。
然而來公司工作之后,慢慢覺得登錄和權限雖然固定,但確實不容出錯的。並且,在一個大型的公司或是多系統中,登錄和權限都是抽離開來的,它們有復雜的邏輯以及高安全性,並且使得多個系統可以有一個統一登錄和認證的接口。這就是今天想描述的單點登錄SSO。
1. 關於SSO:
SSO是一種統一認證和授權機制,指訪問同一服務器不同應用中的受保護資源的同一用戶,只需要登錄一次,即通過一個應用中的安全驗證后,再訪問其他應用中的受保護資源時,不再需要重新登錄驗證。
它包含的核心有兩點:
1)所有應用系統共享一個身份認證系統。
統一的認證系統是SSO的前提之一。認證系統的主要功能是將用戶的登錄信息和用戶信息庫相比較,對用戶進行登錄認證;認證成功后,認證系統應該生成統一的認證標志(ticket),返還給用戶。另外,認證系統還應該對ticket進行效驗,判斷其有效性。
2)所有應用系統能夠識別和提取ticket信息
要實現SSO的功能,讓用戶只登錄一次,就必須讓應用系統能夠識別已經登錄過的用戶。應用系統應該能對ticket進行識別和提取,通過與認證系統的通訊,能自動判斷當前用戶是否登錄過,從而完成單點登錄的功能。
那具體公司的SSO是怎樣的原理和過程呢?
首先是我看到的一個原理圖:
其實主要包含兩種情況:
1. 當在統一認證處沒有認證,或者本身訪問子系統瀏覽器客戶端沒有cookie保存的ticket時,會進行賬號密碼登錄的過程,登錄成功便會通過回調的url返回code給子系統。這時子系統需要拿code到checkCodeUrl去拿ticket和用戶信息。拿到userInfo和ticket后,可以做一些權限的調用,也可以對ticket,userInfo信息進行處理加密生成token串(這里我們用的是JWT,一會會講到),然后講token放入客戶端的cookie中,以便下次使用。
2. 當訪問子系統的瀏覽器有token信息時,則會通過JWT進行token串的解密,拿到userInfo和ticket,然后攜帶ticket去checkTicketUrl去校驗ticket,而如果統一認證處保存了用戶的登錄信息,則認證通過。
講完這兩種情況,想貼一下我前兩天看了這部分代碼的畫的一個邏輯草圖:
這里還有一點在wiki上看到了,想記錄一下:
由於所有項目都采用前后端分離設計,而前后端分離后,靜態頁面無法受到后端服務的權限控制。通常作法是頁面請求后發ajax到后端驗證,如果驗證失敗后后端返回跳SSO的必要數據,由前端再302到SSO登錄頁。但這並不符合SSO的接入標准的要求。
現在的解決方式是:
后端加入對根路徑的請求攔截。以現在前端架構設計看,類似於http://domain/#/XXX的請求格式,其實都是對根路徑的請求。
在nginx中加入轉發規則,對根路徑的請求轉到后端服務器上。
后端接收到頁面請求后先通過SSO Check,如果驗證不通過,在后端直接發起重定向到SSO登錄頁。
2. 關於JWT
首先想比較一下傳統的session認證和token的認證方式:
http協議本身是一種無狀態的協議,而這就意味着如果用戶向我們的應用提供了用戶名和密碼來進行用戶認證,那么下一次請求時,用戶還要再一次進行用戶認證才行,因為根據http協議,我們並不能知道是哪個用戶發出的請求,所以為了讓我們的應用能識別是哪個用戶發出的請求,我們只能在服務器存儲一份用戶登錄的信息,這份登錄信息會在響應時傳遞給瀏覽器,告訴其保存為cookie,以便下次請求時發送給我們的應用,這樣我們的應用就能識別請求來自哪個用戶了,這就是傳統的基於session認證。基於session的認證使應用本身很難得到擴展,隨着不同客戶端用戶的增加,獨立的服務器已無法承載更多的用戶,而這時候基於session認證應用的問題就會暴露出來。
總結一下,session的認證方式,除了增加服務器壓力,還會導致不好擴展,分布式環境會涉及到session同步的問題,並且還會容易受到CSRF攻擊(這個我還沒有細看)。
那么token的方式就顯得十分輕便和靈活了。客戶端負責存儲token(一般用cookie作為存儲方式),並在每次請求時附送上這個token值,由服務端完成校驗(就是剛剛SSO的那個流程),這樣一來,也為擴展提供了遍歷,客戶端不需要知道請求哪一台服務器。
然后關於JWT的格式,這里就不細說了,它有三段部分,並用到了Base64加密,使得信息更加安全。通過jwt工具的加密和解析可以很方便的把需要的信息存入token,滿足系統需要的各個需求。
最后再說兩個點吧:
1) 由於jwt是對稱加密,是可以被解密的,所以有些極為敏感的數據還是不要放在token中為好。
2) jwt還支持token的過期處理和刷新等功能,但我們的系統中並沒有用到,如果今后有這方面的需求,可以做一些改進吧。