、在談項目實踐之前,先說兩個概念:
(1)ruoyi-vue:采用前后端分離的單體web框架,參見介紹 | RuoYi,使用MIT開源協議,對商業使用友好。
(2)CAS:英文為Central Authentication Service,即中央認證服務,為耶魯大學發起的一個開源項目,是實現sso單點登錄的框架。官網地址:https://www.apereo.org/projects/cas。關于spring secuity集成cas可參考:https://docs.spring.io/spring-security/site/docs/5.4.1/reference/html5/#servlet-cas。
CAS官方架構:https://apereo.github.io/cas/development/planning/Architecture.html。
CAS架構圖
CAS執行邏輯:CAS分CAS Server和CAS Client。其中CAS Server用于分發和驗證tickets,CAS Client從CAS Server檢索授權用戶的身份。
時序圖參見:https://apereo.github.io/cas/development/protocol/CAS-Protocol.html。
(1)訪問服務: SSO 客戶端發送請求訪問應用系統。
(2)定向認證: SSO 客戶端重定向請求到 CAS服務器。
(3)用戶認證:用戶身份認證。
(4)發放票據: 成功登錄后,CAS服務器向用戶發放TGT(ticket-granting ticket)全局票據,創建SSO會話。Server端是TGT,Client端是TGC(Ticket Granted Cookie),類似于session和cookie。同時TGT簽發一個ST返回給瀏覽器(如不同應用請求,發現TGC對應了一個TGT,同樣會簽發ST)。
(5)驗證票據: CAS服務器驗證票據 Service Ticket (ST)的合法性,驗證通過后,允許客戶端訪問服務。ST作為Url中的get參數傳遞。
(6)傳輸用戶信息:CAS服務器驗證票據通過后,傳輸用戶認證結果信息給客戶端。
CAS執行邏輯圖
二、回到正題,如何在ruoyi-vue(3.8.1)項目中集成CAS呢。由于ruoyi-vue基于spring seruity。需集成spring-security-cas依賴。另外認證要增加和CAS服務端的集成。
本文參考:RuoYi-Vue前后端分離版集成cas_GGX-520的博客-CSDN博客_ruoyi分離版cas
后端集成:
1、common模塊添加對CAS的支持。
<!-- 添加spring security cas支持 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-cas</artifactId>
</dependency>
2、配置文件修改
#CAS
cas:
server:
host:
#CAS服務地址
url: http://localhost:8888/cas
#CAS服務登錄地址
login_url: ${cas.server.host.url}/login
#CAS服務登出地址
logout_url: ${cas.server.host.url}/logout?service=${app.server.host.url}
# 應用訪問地址
app:
#開啟cas
casEnable: true
server:
host:
url: http://localhost:${server.port}
#應用登錄地址
login_url: /
#應用登出地址
logout_url: /logout
#前端登錄地址
web_url: http://localhost/
3、修改LoginUser.java
由于CAS認證需要authorities屬性,在common模塊中直接new HashSet():
@Override
public Collection<? extends GrantedAuthority> getAuthorities()
{
return new HashSet();
}
4、在common模塊中修改Constants.java
/**
* CAS登錄成功后的后臺標識
*/
public static final String CAS_TOKEN = "cas_token";
/**
* CAS登錄成功后的前臺Cookie的Key
*/
public static final String WEB_TOKEN_KEY = "Admin-Token";
5、在framework模塊config.properties下新增CasProperties.java
CasProperties代碼
6、在framework模塊web.service下新增CasUserDetailsService.java
CasUserDetailService代碼
7、在framework模塊security.handle下添加CasAuthenticationSuccessHandler.java
CasAuthenticationSuccessHandler代碼
8、在framework模塊config.SecurityConfig下修改SecurityConfig。(setCasServerUrlPrefix 從cas-client 3.6.0已移除)
SecurityConfig代碼
前端集成:
1、修改settings.js
settings.js代碼
2、修改permission.js,判斷沒有token時訪問cas登錄頁面
permission.js 代碼
3、修改request.js、Navbar.vue,登出后不做響應
request.js代碼
Navbar.vue代碼
4、修改user.js,登出后跳轉到cas登出頁面
user.js代碼
另外CAS Server需另外搭建。地址為:https://github.com/apereo/cas/releases/
JDK 5之前Java語言是靠synchronized關鍵字保證同步的,這會導致有鎖
(1)在多線程競爭下,加鎖、釋放鎖會導致比較多的上下文切換和調度延時,引起性能問題。
(2)一個線程持有鎖會導致其它所有需要此鎖的線程掛起。
(3)如果一個優先級高的線程等待一個優先級低的線程釋放鎖會導致優先級倒置,引起性能風險。
volatile是不錯的機制,但是volatile不能保證原子性。因此對于同步最終還是要回到鎖機制上來。
獨占鎖是一種悲觀鎖,synchronized就是一種獨占鎖,會導致其它所有需要鎖的線程掛起,等待持有鎖的線程釋放鎖。而另一個更加有效的鎖就是樂觀鎖。所謂樂觀鎖就是,每次不加鎖而是假設沒有沖突而去完成某項操作,如果因為沖突失敗就重試,直到成功為止。樂觀鎖用到的機制就是CAS,Compare and Swap。
CAS,compare and swap的縮寫,中文翻譯成比較并交換。
CAS的語義是“我認為V的值應該為A,如果是,那么將V的值更新為B,否則不修改并告訴V的值實際為多少,”CAS是項樂觀鎖技術,當多個線程嘗試使用CAS同時更新同一個變量時,只有其中一個線程能更新變量的值,而其它線程都失敗,失敗的線程并不會被掛起,而是被告知這次競爭中失敗,并可以再次嘗試。CAS有3個操作數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改為B,否則什么都不做。
CAS 操作包含三個操作數 —— 內存位置(V)、預期原值(A)和新值(B)。 如果內存位置的值與預期原值相匹配,那么處理器會自動將該位置值更新為新值 。否則,處理器不做任何操作。無論哪種情況,它都會在 CAS 指令之前返回該 位置的值。(在 CAS 的一些特殊情況下將僅返回 CAS 是否成功,而不提取當前 值。)CAS 有效地說明了“我認為位置 V 應該包含值 A;如果包含該值,則將 B 放到這個位置;否則,不要更改該位置,只告訴我這個位置現在的值即可。”
通常將 CAS 用于同步的方式是從地址 V 讀取值 A,執行多步計算來獲得新 值 B,然后使用 CAS 將 V 的值從 A 改為 B。如果 V 處的值尚未同時更改,則 CAS 操作成功。
類似于 CAS 的指令允許算法執行讀-修改-寫操作,而無需害怕其他線程同時 修改變量,因為如果其他線程修改變量,那么 CAS 會檢測它(并失敗),算法 可以對該操作重新計算。
利用CPU的CAS指令,同時借助JNI來完成Java的非阻塞算法。其它原子操作都是利用類似的特性完成的。而整個J.U.C都是建立在CAS之上的,因此對于synchronized阻塞算法,J.U.C在性能上有了很大的提升。
CAS雖然很高效的解決原子操作,但是CAS仍然存在三大問題。ABA問題,循環時間長開銷大和只能保證一個共享變量的原子操作
1. ABA問題。因為CAS需要在操作值的時候檢查下值有沒有發生變化,如果沒有發生變化則更新,但是如果一個值原來是A,變成了B,又變成了A,那么使用CAS進行檢查時會發現它的值沒有發生變化,但是實際上卻變化了。ABA問題的解決思路就是使用版本號。在變量前面追加上版本號,每次變量更新的時候把版本號加一,那么A-B-A 就會變成1A-2B-3A。
從Java1.5開始JDK的atomic包里提供了一個類AtomicStampedReference來解決ABA問題。這個類的compareAndSet方法作用是首先檢查當前引用是否等于預期引用,并且當前標志是否等于預期標志,如果全部相等,則以原子方式將該引用和該標志的值設置為給定的更新值。
關于ABA問題參考文檔: http://blog.hesey.net/2011/09/resolve-aba-by-atomicstampedreference.html
2. 循環時間長開銷大。自旋CAS如果長時間不成功,會給CPU帶來非常大的執行開銷。如果JVM能支持處理器提供的pause指令那么效率會有一定的提升,pause指令有兩個作用,第一它可以延遲流水線執行指令(de-pipeline),使CPU不會消耗過多的執行資源,延遲的時間取決于具體實現的版本,在一些處理器上延遲時間是零。第二它可以避免在退出循環的時候因內存順序沖突(memory order violation)而引起CPU流水線被清空(CPU pipeline flush),從而提高CPU的執行效率。
3. 只能保證一個共享變量的原子操作。當對一個共享變量執行操作時,我們可以使用循環CAS的方式來保證原子操作,但是對多個共享變量操作時,循環CAS就無法保證操作的原子性,這個時候就可以用鎖,或者有一個取巧的辦法,就是把多個共享變量合并成一個共享變量來操作。比如有兩個共享變量i=2,j=a,合并一下ij=2a,然后用CAS來操作ij。從Java1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性,你可以把多個變量放在一個對象里來進行CAS操作。
四、concurrent包的實現
由于java的CAS同時具有 volatile 讀和volatile寫的內存語義,因此Java線程之間的通信現在有了下面四種方式:
Java的CAS會使用現代處理器上提供的高效機器級別原子指令,這些原子指令以原子方式對內存執行讀-改-寫操作,這是在多處理器中實現同步的關鍵(從本質上來說,能夠支持原子性讀-改-寫指令的計算機器,是順序計算圖靈機的異步等價機器,因此任何現代的多處理器都會去支持某種能對內存執行原子性讀-改-寫操作的原子指令)。同時,volatile變量的讀/寫和CAS可以實現線程之間的通信。把這些特性整合在一起,就形成了整個concurrent包得以實現的基石。如果我們仔細分析concurrent包的源代碼實現,會發現一個通用化的實現模式:
AQS,非阻塞數據結構和原子變量類(java.util.concurrent.atomic包中的類),這些concurrent包中的基礎類都是使用這種模式來實現的,而concurrent包中的高層類又是依賴于這些基礎類來實現的。從整體來看,concurrent包的實現示意圖如下:
點登錄 (SingleSign-On,SSO) ,是一種幫助用戶快捷訪問網絡中多個站點的安全通信技術。單點登錄系統基于一種安全的通信協議,該協議通過多個系統之間的用戶身份信息的交換來實現單點登錄。使用單點登錄系統時,用戶只需要登錄一次,就可以訪問多個系統,不需要記憶多個口令密碼。
云程平臺支持CAS、OAuth2、JWT三種主流的單點登錄技術,客戶可根據需求選擇對應技術方案。
CAS(Central Authentication Service)是 Yale大學發起的一個企業級的、開源的項目,旨在為 Web 應用系統提供一種可靠的單點登錄解決方法。CAS的目標是允許用戶訪問多個應用程序只提供一次用戶憑據(如用戶名和密碼)。
CAS 體系包含兩個部分: CAS Server 和 CAS Client。CAS Server 需要獨立部署,主要負責對用戶的認證工作;CAS Client 負責處理對客戶端受保護資源的訪問請求,需要登錄時,重定向到 CAS Server。
CAS 具有以下特點:
CAS官方文檔:https://apereo.github.io/cas/5.3.x/index.html#
在 CAS 的整個登錄過程中,有三個重要的概念。
CAS的單點登錄SSO流程如下, 應用系統要做單點登錄,需要跟CAS服務進行集成,首先要理解CAS集成流程和原理。
云程平臺對CAS 5.3.x版本無縫集成,并對CAS認證校驗進行了擴展,項目上請使用平臺提供的CAS 5.3.x運行包。運行CAS之前需要在數據庫先執行平臺的腳本,CAS獲取用戶信息需訪問平臺的SYS_USER表。
1 修改數據庫連接
打開 cas\WEB-INF\classes\application.properties
修改如下配置:
#數據庫配置
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/yuncheng2021?characterEncoding=UTF-8&useUnicode=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.type=org.apache.commons.dbcp2.BasicDataSource
spring.jpa.database=mysql
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
2 啟動cas
需要準備tomcat,把cas包放到tomcat/webapps目錄下,在tomcat/bin目錄下執行startup.bat(windows)或startup.sh(linux)。
啟動成功后訪問cas地址,界面如下圖所示:
云程平臺后端已集成CAS代碼,在yml配置文件中配置cas服務地址即可。
application.yml 進行如下配置:
#cas單點登錄
cas:
prefixUrl: http://cas.example.org:8443/cas
修改public/config/bootConfig.js
VUE_APP_SSO設置為true
VUE_APP_CAS_BASE_URL配置單點登錄服務地址
*請認真填寫需求信息,我們會在24小時內與您取得聯系。