1. 程式人生 > 程式設計 >springboot整合CAS實現單點登入的示例程式碼

springboot整合CAS實現單點登入的示例程式碼

最近新參與的專案用到了cas單點登入,我還不會,這怎麼能容忍!空了學習並搭建了一個spring-boot 整合CAS 的demo。實現了單點登入與登出。

單點登入英文全稱是:Single Sign On,簡稱SSO。
含義:在多個相互信任的系統中,只要登入一個系統其他系統均可訪問。

CAS 是一種使用廣泛的單點登入實現,分為客戶端CAS Client和服務端 CAS Service,客戶端就是我們的系統,服務端是認證中心,由CAS提供,我們需要稍作修改,啟動起來就可以用。~~~~

springboot整合CAS實現單點登入的示例程式碼

效果演示

springboot整合CAS實現單點登入的示例程式碼

https證書

CAS Service 需要用https的方式,那麼就需要證書,可以買也可以自己生成一個。

其實這一步也可以省略,訪問的時候使用http即可,只是cas 會給警告。

步驟和把大象裝進冰箱一樣簡單,總共三步:

  • 生成金鑰
  • 生成證書
  • 匯入證書

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

springboot整合CAS實現單點登入的示例程式碼

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

未認證授權的服務

喜提報錯:

springboot整合CAS實現單點登入的示例程式碼

原因是服務端不允許客戶端的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,可以看到已經帶著地址轉發到服務端的登入頁。

springboot整合CAS實現單點登入的示例程式碼

輸入賬號密碼mashu/mashu,登入成功後又回來了!哈哈哈哈嗝,並攜帶了登入憑證。

springboot整合CAS實現單點登入的示例程式碼

多系統登入

再啟動一個客戶端,開啟idea 的edit configurations設定。勾選 Allow parallel run

springboot整合CAS實現單點登入的示例程式碼

修改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),就直接登入了,到此完成了單點登入。

springboot整合CAS實現單點登入的示例程式碼

點單登出

新增兩個配置檔案;

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,退出服務端。
再次訪問客戶端發現自動跳到了登入頁面,即客戶端也自動退出成功。

springboot整合CAS實現單點登入的示例程式碼

一些問題

最開始我想把客戶端也加一個證書,用https訪問。免得在服務端做修改去支援http,
當我新增證書後,單點登入正常,但是登出功能總是失敗,表現為服務端退出,客戶端沒有退出。
我一直以為客戶端配置的登出有問題,搞了半天都沒成功,後來我把客戶端的證書去掉,就成功了。想了想大概是因為我們自己生成的證書不能被服務端認可,因為登出的時候需要服務端向客戶端發起廣播,而我們之前修改的HTTPSandIMAPS-10000001.json檔案只是作用於客戶端向服務端的請求。和登出相反。

在我使用springboot配置證書的時候,2.1.0.RELEASE以上版本的spring-boot-starter-parent都不行。會報錯。

到此這篇關於springboot整合CAS實現單點登入的示例程式碼的文章就介紹到這了,更多相關springboot整合CAS單點登入內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!