springboot整合CAS實現單點登入的示例程式碼
最近新參與的專案用到了cas單點登入,我還不會,這怎麼能容忍!空了學習並搭建了一個spring-boot 整合CAS 的demo。實現了單點登入與登出。
單點登入英文全稱是:Single Sign On,簡稱SSO。
含義:在多個相互信任的系統中,只要登入一個系統其他系統均可訪問。
CAS 是一種使用廣泛的單點登入實現,分為客戶端CAS Client和服務端 CAS Service,客戶端就是我們的系統,服務端是認證中心,由CAS提供,我們需要稍作修改,啟動起來就可以用。~~~~
效果演示
https證書
CAS Service 需要用https的方式,那麼就需要證書,可以買也可以自己生成一個。
步驟和把大象裝進冰箱一樣簡單,總共三步:
- 生成金鑰
- 生成證書
- 匯入證書
1. 生成金鑰
keytool -genkey -alias cainiao -keyalg RSA -keystore E:sslcainiao.keystore
引數說明:
- -genkey 生成金鑰
- -keyalg 指定金鑰演算法,這時指定RSA
- -alias 指定別名
- -keystore 指定金鑰庫儲存位置,這裡存在 E:/ssl/目錄下
在執行中會問你很多問題,當問到 :您的名字與姓氏是什麼?
此時需要填寫域名,作為之後的訪問地址,其他隨意。
執行完後生成一個金鑰檔案 cainiao.keystore
2. 生成證書
keytool -export -alias cainiao -storepass 123456 -file E:/ssl/cainiao.cer -keystore E:/ssl/cainiao.keystore
引數說明:
-storepass 剛剛生成金鑰檔案時候的設定的密碼
-file指定匯出證書的檔名為cainiao.cer
-keystore指定之前生成的金鑰檔案的檔名
執行完後目錄下會生成一個cainiao.cer證書
3. 匯入證書
keytool -import -alias cainiao -keystore C:/"Program Files"/Java/jdk1.8.0_181/jre/lib/security/cacerts -file E:/ssl/cainiao.cer -trustcacerts
將證書匯入到JDK信任庫
把原來的$JAVA_HOME/jre/lib/security/cacerts檔案要先刪掉,否則會報出 Keystore was tampered with,or password was incorrect
.
下面是整個過程:
PS E:\ssl> keytool -genkey -alias cainiao -keyalg RSA -keystore E:\ssl\cainiao.keystore 輸入金鑰庫口令: 再次輸入新口令: 您的名字與姓氏是什麼? [Unknown]: www.cainiao.com 您的組織單位名稱是什麼? [Unknown]: cainian 您的組織名稱是什麼? [Unknown]: cainiao 您所在的城市或區域名稱是什麼? [Unknown]: wx 您所在的省/市/自治區名稱是什麼? [Unknown]: js 該單位的雙字母國家/地區程式碼是什麼? [Unknown]: CN CN=www.cainiao.com,OU=cainian,O=cainiao,L=wx,ST=js,C=CN是否正確? [否]: y 輸入 <cainiao> 的金鑰口令 (如果和金鑰庫口令相同,按回車): 再次輸入新口令: ------------------------------------------------------------------------------------ PS E:\ssl> keytool -export -alias cainiao -storepass 123456 -file E:/ssl/cainiao.cer -keystore E:/ssl/cainiao.keystore 儲存在檔案 <E:/ssl/cainiao.cer> 中的證書 ------------------------------------------------------------------------------------ PS E:\ssl> keytool -import -alias cainiao -keystore C:/"Program Files"/Java/jdk1.8.0_181/jre/lib/security/cacerts -file E:/ssl/cainiao.cer -trustcacerts 輸入金鑰庫口令: 所有者: CN=www.cainiao.com,C=CN 釋出者: CN=www.cainiao.com,C=CN 序列號: 509d1aea 有效期為 Wed Jun 17 22:02:55 CST 2020 至 Tue Sep 15 22:02:55 CST 2020 證書指紋: MD5: 5B:B2:7C:D7:B7:31:C5:7C:1C:BC:F7:DA:A8:2D:1C:B2 SHA1: F6:76:55:55:D7:48:E3:9F:3A:B6:EE:68:1F:BE:DC:DE:51:B1:33:E5 SHA256: 24:53:18:CD:E8:95:65:D8:6E:6A:7B:8E:79:CB:91:BD:F4:2E:C3:99:59:D1:76:12:A8:95:45:2A:4B:03:E4:AD 簽名演算法名稱: SHA256withRSA 主體公共金鑰演算法: 2048 位 RSA 金鑰 版本: 3 擴充套件: #1: ObjectId: 2.5.29.14 Criticality=false SubjectKeyIdentifier [ KeyIdentifier [ 0000: 70 B3 D5 76 36 EA 54 BA 75 C1 A1 5C DA 76 82 0E p..v6.T.u..\.v.. 0010: 4D F4 C9 05 M... ] ] 是否信任此證書? [否]: y 證書已新增到金鑰庫中
最後,hosts 配置 127.0.0.1 www.cainiao.com
搭建CAS service
需要從github上拉取模板 https://github.com/apereo/cas-overlay-template
5.3之後的都是gradle專案,5.3以之前都是maven 專案,我下載5.3版本的。
1.> 把pom 裡面的<repositories>倉庫地址去掉,國外的倉庫地址比較慢。你懂得。
2.> 在根目錄下建/src/main/resources目錄
3.> 將生成的金鑰檔案複製到/src/main/resources目錄下
4.> 將overlays/org.apereo.cas.cas-server-webapp-tomcat-5.3.14/WEB-INF/classes/application.properties檔案複製到第二步建的目錄下。
5.> 修改複製過來的/src/main/resources/application.properties檔案,根據上面的證書資訊如實填寫。
server.ssl.key-store=classpath:cainiao.keystore server.ssl.key-store-password=123456 server.ssl.key-password=123456
6.> 連線mysql資料庫,在pom 中新增依賴
<dependency> <groupId>org.apereo.cas</groupId> <artifactId>cas-server-support-jdbc</artifactId> <version>${cas.version}</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.21</version> </dependency>
或許你會發現有個xmlsectool-2.0.0.jar包下不下來,這是阿里雲的倉庫沒有,需要到maven中央倉庫下載,後安裝到本地倉庫,可不是直接放到本地倉庫,jar包都是必須使用命令安裝到本地倉庫。
mvn install:install-file -Dfile="E:下載xmlsectool-2.0.0.jar" "-DgroupId=net.shibboleth.tool" "-DartifactId=xmlsectool" "-Dversion=2.0.0" "-Dpackaging=jar"
安裝jar包到本地倉庫筆記
7.> 在複製過來的/src/main/resources/application.properties檔案中在新增如下資訊
#查詢賬號密碼sql,必須包含密碼欄位 cas.authn.jdbc.query[0].sql=select * from sys_user where username=? #指定上面的sql查詢欄位名(必須) cas.authn.jdbc.query[0].fieldPassword=password #指定過期欄位,1為過期,若過期需要修改密碼 cas.authn.jdbc.query[0].fieldExpired=expired #為不可用欄位段,1為不可用, cas.authn.jdbc.query[0].fieldDisabled=disabled #資料庫方言hibernate的知識 cas.authn.jdbc.query[0].dialect=org.hibernate.dialect.MySQLDialect #資料庫驅動 cas.authn.jdbc.query[0].driverClass=com.mysql.jdbc.Driver #資料庫連線 cas.authn.jdbc.query[0].url=jdbc:mysql://localhost:3306/cas?useUnicode=true&characterEncoding=UTF-8 #資料庫使用者名稱 cas.authn.jdbc.query[0].user=root #資料庫密碼 cas.authn.jdbc.query[0].password=123456 #預設加密策略,通過encodingAlgorithm來指定演算法,預設NONE不加密 cas.authn.jdbc.query[0].passwordEncoder.type=DEFAULT cas.authn.jdbc.query[0].passwordEncoder.characterEncoding=UTF-8 cas.authn.jdbc.query[0].passwordEncoder.encodingAlgorithm=MD5
附上資料庫sql,使用者資訊表
-- ---------------------------- -- Table structure for sys_user -- ---------------------------- DROP TABLE IF EXISTS `sys_user`; CREATE TABLE `sys_user` ( `id` int(11) NOT NULL AUTO_INCREMENT,`username` varchar(255) DEFAULT NULL,`password` varchar(255) DEFAULT NULL,`expired` int(11) DEFAULT NULL,`disabled` int(11) DEFAULT NULL,PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of sys_user -- ---------------------------- INSERT INTO `sys_user` VALUES ('1','admin','21232f297a57a5a743894a0e4a801fc3','0','1'); INSERT INTO `sys_user` VALUES ('2','cainiao','6b757206058785025cd90c8d865c8e43','1','0'); INSERT INTO `sys_user` VALUES ('3','mashu','d1f21ceb3f710ebbd9f408274aee1193','0');
使用者名稱和密碼一樣,密碼在資料庫中是MD5加密的。
這樣就完成了CAS service 的搭建,在根目錄使用 build.cmd run
命令啟動
出現 READY 的branner就啟動好了 訪問地址 https://www.cainiao.com:8443/cas/login
mashu正常登入,cainiao需要修改密碼,admin被禁用,符合預期。
搭建CAS client
建立一個spring boot 專案.
1.加入cas 客戶端 的依賴,我選擇目前最新的 2.3.0-GA 版本
<dependency> <groupId>net.unicon.cas</groupId> <artifactId>cas-client-autoconfig-support</artifactId> <version>2.3.0-GA</version> </dependency>
2.在啟動類上加上註解 @EnableCasClient
3.在application.properties中新增配置
#cas服務端的地址 cas.server-url-prefix=https://www.cainiao.com:8443/cas #cas服務端的登入地址 cas.server-login-url=https://www.cainiao.com:8443/cas/login #客戶端訪問地址 cas.client-host-url=http://www.mashu.com:8080 cas.validation-type=CAS3
4.新增hosts 配置,把客戶端的訪問地址配置到hosts
127.0.0.1 www.mashu.com
這樣就客戶端就配置好了。
單點登入
我寫一個controller,訪問一下。
@RestController public class TestController { @RequestMapping("/hello") public String hello() { return "word"; } }
訪問 http://www.mashu.com:8080/hello
未認證授權的服務
喜提報錯:
原因是服務端不允許客戶端的http協議的請求。需要對服務端做以下修改,讓他妥協。
1.>修改overlays/org.apereo.cas.cas-server-webapp-tomcat-5.3.14/WEB-INF/classes/services/HTTPSandIMAPS-10000001.json檔案
"serviceId" 由原來的"^(https|imaps)://.*"改成 "^(https|imaps|http)://.*"
2.>在application.properties檔案中新增:
#允許http cas.tgc.secure=false cas.serviceRegistry.initFromJson=true
再次訪問 http://www.mashu.com:8080/hello,可以看到已經帶著地址轉發到服務端的登入頁。
輸入賬號密碼mashu/mashu,登入成功後又回來了!哈哈哈哈嗝,並攜帶了登入憑證。
多系統登入
再啟動一個客戶端,開啟idea 的edit configurations設定。勾選 Allow parallel run
修改application.properties,服務的埠等資訊,再點選啟動,就可以同時啟動了(8081/8080)兩個客戶端
server.port=8081 #cas服務端的地址 cas.server-url-prefix=https://www.cainiao.com:8443/cas #cas服務端的登入地址 cas.server-login-url=https://www.cainiao.com:8443/cas/login #客戶端訪問地址 cas.client-host-url=http://www.mshu.com:8081 cas.validation-type=CAS3
訪問第二個客戶端 http://www.mshu.com:8081/hello,(需要先配置host),就直接登入了,到此完成了單點登入。
點單登出
新增兩個配置檔案;
1. CasProperties.java
import org.springframework.boot.context.properties.ConfigurationProperties; import javax.validation.constraints.NotNull; @ConfigurationProperties(prefix = "cas",ignoreUnknownFields = true) public class CasProperties { /** * CAS server URL E.g. https://example.com/cas or https://cas.example. Required. * CAS 服務端 url 不能為空 */ @NotNull private String serverUrlPrefix; /** * CAS server login URL E.g. https://example.com/cas/login or https://cas.example/login. Required. * CAS 服務端登入地址 上面的連線 加上/login 該引數不能為空 */ @NotNull private String serverLoginUrl; /** * CAS-protected client application host URL E.g. https://myclient.example.com Required. * 當前客戶端的地址 */ @NotNull private String clientHostUrl; /** * 忽略規則,訪問那些地址 不需要登入 */ private String ignorePattern; /** * 自定義UrlPatternMatcherStrategy驗證 */ private String ignoreUrlPatternType; public String getServerUrlPrefix() { return serverUrlPrefix; } public void setServerUrlPrefix(String serverUrlPrefix) { this.serverUrlPrefix = serverUrlPrefix; } public String getServerLoginUrl() { return serverLoginUrl; } public void setServerLoginUrl(String serverLoginUrl) { this.serverLoginUrl = serverLoginUrl; } public String getClientHostUrl() { return clientHostUrl; } public void setClientHostUrl(String clientHostUrl) { this.clientHostUrl = clientHostUrl; } public String getIgnorePattern() { return ignorePattern; } public void setIgnorePattern(String ignorePattern) { this.ignorePattern = ignorePattern; } public String getIgnoreUrlPatternType() { return ignoreUrlPatternType; } public void setIgnoreUrlPatternType(String ignoreUrlPatternType) { this.ignoreUrlPatternType = ignoreUrlPatternType; } }
2. Configs.java
import org.jasig.cas.client.authentication.AuthenticationFilter; import org.jasig.cas.client.session.SingleSignOutFilter; import org.jasig.cas.client.session.SingleSignOutHttpSessionListener; import org.jasig.cas.client.util.HttpServletRequestWrapperFilter; import org.jasig.cas.client.validation.Cas30ProxyReceivingTicketValidationFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletListenerRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.EventListener; import java.util.HashMap; import java.util.Map; @Configuration @EnableConfigurationProperties(CasProperties.class) public class Configs { @Autowired private CasProperties configProps; /** * 配置登出過濾器 * @return */ @Bean public FilterRegistrationBean filterSingleRegistration() { final FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new SingleSignOutFilter()); // 設定匹配的路徑 registration.addUrlPatterns("/*"); Map<String,String> initParameters = new HashMap<String,String>(); initParameters.put("casServerUrlPrefix",configProps.getServerUrlPrefix()); registration.setInitParameters(initParameters); // 設定載入的順序 registration.setOrder(1); return registration; } /** * 配置過濾驗證器 這裡用的是Cas30ProxyReceivingTicketValidationFilter * @return */ @Bean public FilterRegistrationBean filterValidationRegistration() { final FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new Cas30ProxyReceivingTicketValidationFilter()); // 設定匹配的路徑 registration.addUrlPatterns("/*"); Map<String,configProps.getServerUrlPrefix()); initParameters.put("serverName",configProps.getClientHostUrl()); initParameters.put("useSession","true"); registration.setInitParameters(initParameters); // 設定載入的順序 registration.setOrder(2); return registration; } /** * 配置授權過濾器 * @return */ @Bean public FilterRegistrationBean filterAuthenticationRegistration() { final FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new AuthenticationFilter()); // 設定匹配的路徑 registration.addUrlPatterns("/*"); Map<String,String>(); initParameters.put("casServerLoginUrl",configProps.getServerLoginUrl()); initParameters.put("serverName",configProps.getClientHostUrl()); if(configProps.getIgnorePattern() != null && !"".equals(configProps.getIgnorePattern())){ initParameters.put("ignorePattern",configProps.getIgnorePattern()); } //自定義UrlPatternMatcherStrategy 驗證規則 if(configProps.getIgnoreUrlPatternType() != null && !"".equals(configProps.getIgnoreUrlPatternType())){ initParameters.put("ignoreUrlPatternType",configProps.getIgnoreUrlPatternType()); } registration.setInitParameters(initParameters); // 設定載入的順序 registration.setOrder(3); return registration; } /** * request wraper過濾器 * @return */ @Bean public FilterRegistrationBean filterWrapperRegistration() { final FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new HttpServletRequestWrapperFilter()); // 設定匹配的路徑 registration.addUrlPatterns("/*"); // 設定載入的順序 registration.setOrder(4); return registration; } /** * 新增監聽器 * @return */ @Bean public ServletListenerRegistrationBean<EventListener> singleSignOutListenerRegistration(){ ServletListenerRegistrationBean<EventListener> registrationBean = new ServletListenerRegistrationBean<EventListener>(); registrationBean.setListener(new SingleSignOutHttpSessionListener()); registrationBean.setOrder(1); return registrationBean; } }
登出地址: https://www.cainiao.com:8443/cas/logout,退出服務端。
再次訪問客戶端發現自動跳到了登入頁面,即客戶端也自動退出成功。
一些問題
最開始我想把客戶端也加一個證書,用https訪問。免得在服務端做修改去支援http,
當我新增證書後,單點登入正常,但是登出功能總是失敗,表現為服務端退出,客戶端沒有退出。
我一直以為客戶端配置的登出有問題,搞了半天都沒成功,後來我把客戶端的證書去掉,就成功了。想了想大概是因為我們自己生成的證書不能被服務端認可,因為登出的時候需要服務端向客戶端發起廣播,而我們之前修改的HTTPSandIMAPS-10000001.json
檔案只是作用於客戶端向服務端的請求。和登出相反。
在我使用springboot配置證書的時候,2.1.0.RELEASE以上版本的spring-boot-starter-parent都不行。會報錯。
到此這篇關於springboot整合CAS實現單點登入的示例程式碼的文章就介紹到這了,更多相關springboot整合CAS單點登入內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!