最近新參與的項目用到了cas單點登陸,我還不會,這怎麼能容忍!空了學習並搭建了一個spring-boot 集成CAS 的demo。實現了單點登陸與登出。java
單點登陸英文全稱是:Single Sign On,簡稱SSO。
含義:在多個相互信任的系統中,只要登陸一個系統其餘系統都可訪問。mysql
CAS 是一種使用普遍的單點登陸實現,分爲客戶端CAS Client和服務端 CAS Service,客戶端就是咱們的系統,服務端是認證中心,由CAS提供,咱們須要稍做修改,啓動起來就能夠用。~~~~git
CAS Service 須要用https的方式,那麼就須要證書,能夠買也能夠本身生成一個。
其實這一步也能夠省略,訪問的時候使用http便可,只是cas 會給警告。github
步驟和把大象裝進冰箱同樣簡單,總共三步:web
keytool -genkey -alias cainiao -keyalg RSA -keystore E:sslcainiao.keystore
參數說明:算法
在執行中會問你不少問題,當問到 :您的名字與姓氏是什麼?
此時須要填寫域名,做爲以後的訪問地址,其餘隨意。
執行完後生成一個密鑰文件 cainiao.keystorespring
keytool -export -alias cainiao -storepass 123456 -file E:/ssl/cainiao.cer -keystore E:/ssl/cainiao.keystore
參數說明:sql
執行完後目錄下會生成一個cainiao.cer證書數據庫
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
.json
下面是整個過程:
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, OU=cainian, O=cainiao, L=wx, ST=js, C=CN 發佈者: CN=www.cainiao.com, OU=cainian, O=cainiao, L=wx, ST=js, 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
須要從github上拉取模板 https://github.com/apereo/cas...
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"
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', '0');
用戶名和密碼同樣,密碼在數據庫中是MD5加密的。
這樣就完成了CAS service 的搭建,在根目錄使用 build.cmd run
命令啓動
出現 READY 的branner就啓動好了 訪問地址 https://www.cainiao.com:8443/cas/login
mashu正常登陸,cainiao須要修改密碼,admin被禁用,符合預期。
建立一個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),就直接登陸了,到此完成了單點登陸。
添加兩個配置文件;
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; } }
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,String> initParameters = new HashMap<String, String>(); initParameters.put("casServerUrlPrefix", 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 = new HashMap<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都不行。會報錯。