SpringBoot 集成CAS Server
一、CAS Service服務(wù)介紹
? CAS(Central Authentication Service)中心授權(quán)服務(wù),是一個(gè)開(kāi)源項(xiàng)目,目的在于為Web應(yīng)用系統(tǒng)提供一種可靠的單點(diǎn)登錄。
? 在整個(gè)認(rèn)證的流程中的整個(gè)流程大概是:首先由CAS Client(我們的客戶端應(yīng)用)發(fā)起請(qǐng)求,CAS Client 會(huì)重定向到CAS Server進(jìn)行登錄,CAS Server進(jìn)行賬戶校驗(yàn)且多個(gè)CAS Client 之間可以共享登錄的 session ,Server 和 Client 是一對(duì)多的關(guān)系?;贑AS的SSO訪問(wèn)流程步驟:
- 訪問(wèn)服務(wù): CAS Client 客戶端發(fā)送請(qǐng)求訪問(wèn)應(yīng)用系統(tǒng)提供的服務(wù)資源。
- 定向認(rèn)證: CAS Client 客戶端會(huì)重定向用戶請(qǐng)求到 CAS Server 服務(wù)器。
- 用戶認(rèn)證: 用戶在瀏覽器端輸入用戶驗(yàn)證信息,CAS Server服務(wù)端完成用戶身份認(rèn)證。
- 發(fā)放票據(jù): CAS Server服務(wù)器會(huì)產(chǎn)生一個(gè)隨機(jī)的 Service Ticket 。
- 驗(yàn)證票據(jù): CAS Server服務(wù)器驗(yàn)證票據(jù) Service Ticket 的合法性,驗(yàn)證通過(guò)后,允許客戶端訪問(wèn)服務(wù)。
- 傳輸用戶信息: CAS Server 服務(wù)器驗(yàn)證票據(jù)通過(guò)后,傳輸用戶認(rèn)證結(jié)果信息給客戶端。
? 從結(jié)構(gòu)上看,CAS 包含兩個(gè)部分: CAS Server 和 CAS Client 。 CAS Server 需要獨(dú)立部署,主要負(fù)責(zé)對(duì)用戶的認(rèn)證工作; CAS Client 負(fù)責(zé)處理對(duì)客戶端受保護(hù)資源的訪問(wèn)請(qǐng)求,需要登錄時(shí),重定向到 CAS Server。
? CAS Client 與受保護(hù)的客戶端應(yīng)用部署在一起,以 Filter 方式保護(hù)受保護(hù)的資源。對(duì)于訪問(wèn)受保護(hù)資源的每個(gè) Web 請(qǐng)求, CAS Client 會(huì)分析該請(qǐng)求的 Http 請(qǐng)求中是否包含 Service Ticket。如果沒(méi)有,則說(shuō)明當(dāng)前用戶尚未登錄,于是將請(qǐng)求重定向到指定好的 CAS Server 登錄地址,并傳遞 Service (也就是要訪問(wèn)的目的資源地址),以便登錄成功過(guò)后轉(zhuǎn)回該地址。
? 在流程圖中的第三步輸入認(rèn)證信息,登陸成功后,CAS Server 隨機(jī)產(chǎn)生一個(gè)相當(dāng)長(zhǎng)度、唯一、不可偽造的 Service Ticket,并緩存以待將來(lái)驗(yàn)證。之后系統(tǒng)自動(dòng)重定向到 Service 所在地址,并為客戶端瀏覽器設(shè)置一個(gè) Ticket Granted Cookie(TGC),CAS Client 在拿到 Service 和新產(chǎn)生的 Ticket 過(guò)后,在第 5,6 步中與 CAS Server 進(jìn)行身份核實(shí),以確保 Service Ticket 的合法性。
二、CAS Server服務(wù)的搭建
2.1 下載cas-overlay-template
? 這里為大家提供一個(gè) 5.1版本的 git地址:<https://github.com/apereo/cas-overlay-template/tree/5.1
5.3版本的git地址
網(wǎng)上都能找到很多下載方式的,或者私信我 我測(cè)試使用的是cas-overlay-template-5.3
? 獲取到項(xiàng)目后zip的方式解壓出來(lái)后的目錄如下:
2.2 使用外部Tomcat部署CAS Server
- 解壓出來(lái)文件夾之后,就可以進(jìn)行打包運(yùn)行了。在解壓的目錄下打開(kāi)命令行 到安裝目錄下 使用build.cmd run 來(lái)進(jìn)行編譯打包。過(guò)程可能需要花點(diǎn)時(shí)間,
- 在打包完成之后就會(huì)啟動(dòng)我們的CAS Server,但是服務(wù)現(xiàn)在是沒(méi)有正常啟動(dòng)的,因?yàn)镃as server 配置證書(shū)路徑是基于linux的,而我們是在windows環(huán)境下部署,目錄結(jié)構(gòu)不一致導(dǎo)致無(wú)法找到相應(yīng)的文件,如果是linux環(huán)境的話就可以成功啟動(dòng)了。
? 啟動(dòng)失?。?/p>
打包完成之后會(huì)在解壓的目錄下面多了target文件夾:
- 我們將Cas.war(或者直接使用Cas文件夾) 復(fù)制到本機(jī)的 Tomcat 的 webapp目錄中,啟動(dòng)Tomcat即可
啟動(dòng)Tomcat,通過(guò)Tomcat日志就可以看見(jiàn)我們的CAS Server是否啟動(dòng)成功了:
然后我們就能訪問(wèn)部署到本地CAS Server 服務(wù)了。通過(guò) 127.0.0.1:8080/cas/login 來(lái)訪問(wèn)CAS服務(wù),這里的地址需要根據(jù)自己的實(shí)際情況來(lái)定(比如你部署的Tomcat服務(wù)默認(rèn)地址8080是否改變過(guò) 等情況):
至此我們使用外部Tomcat部署CAS Server服務(wù)就成功了!
這里可以使用默認(rèn)的賬戶密碼(賬:casuser 密: Mellon)進(jìn)行登錄,
至于后續(xù)可能出現(xiàn)的一些疑難雜癥(包括靜態(tài)用戶的設(shè)置、http的支持等),文章下面部門章節(jié)會(huì)進(jìn)行介紹。目前先簡(jiǎn)單的將CAS Server服務(wù)部署上去!
2.3 使用IDEA部署CAS Server
? 在IDEA中我們直接將項(xiàng)目通過(guò)maven工具加載pom文件中的jar包和package命令生成運(yùn)行包target。然后在項(xiàng)目中建立本地項(xiàng)目的src/main/java 和 src/main/resources目錄。最后將target包中的/cas/WEB_INF/classes/services和aplication.properties和log4j2.xml以及/cas/WEB_INF/classes/META-INF復(fù)制到resources目錄中。
-
首先通過(guò)IDEA將解壓后的CAS Server文件打開(kāi)進(jìn)行打包
打開(kāi)文件后,首相將項(xiàng)目的mave配置好(File/Settings/Build,…/Build Tools/Maven 中配置好自己本地的maven地址),然后加載pom文件中的Jar包。
maven配置好之后利用maven對(duì)項(xiàng)目進(jìn)行打包
-
移動(dòng)相關(guān)配置文件
項(xiàng)目打包完成之后,在項(xiàng)目中創(chuàng)建Java文件夾和resources文件夾,將上述提到的target文件中的4個(gè)文件復(fù)制到我們自己創(chuàng)建的resources目錄下
?文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-794904.html
-
給CAS Server 項(xiàng)目配置tomcat服務(wù)
首先給CAS Server 添加tamcat服務(wù)器
? 配置好Tomcat服務(wù)的地址 以及 端口號(hào)等基本信息
?
? 部署Tomcat服務(wù)器
選擇war exploded模式進(jìn)行部署我們的Tomcat,選擇之后,將下面的 Application context 基路徑中的數(shù)據(jù)改為 / 即可。
-
為CAS Server 配置JDK
打開(kāi) File/Project Stucture/Project中進(jìn)行設(shè)置
所有配置都準(zhǔn)備完畢之后 我們就可以啟動(dòng)Tomcat了,tomcat啟動(dòng)之后就能夠正常訪問(wèn)到我們的CAS Server服務(wù)了!
通過(guò) 127.0.0.1:8080/login 即可訪問(wèn)到我們的CAS Service。這里可以使用默認(rèn)的賬戶密碼(賬:casuser 密: Mellon)進(jìn)行登錄。
推出登錄的地址: 127.0.0.1:8080/logout
三、CAS Server的其他配置
3.1 CAS Server 去掉https驗(yàn)證
? 這里這樣設(shè)置的目的是為了,后續(xù)我們通過(guò)項(xiàng)目去請(qǐng)求CAS Server 服務(wù)時(shí),能夠通過(guò) http 的方式去訪問(wèn)我們的CAS Server服務(wù)!
? 在CAS Server服務(wù) 4.2版本時(shí)對(duì)整體的架構(gòu)進(jìn)行了一個(gè)優(yōu)化。
允許Http訪問(wèn)CAS Server 的配置設(shè)置:
- application.properties 文件中的最后一行配置
#忽略https安全協(xié)議,使用 HTTP 協(xié)議
cas.tgc.secure=false
- src/main/resources/services中的 HTTPSandIMAPS-10000001.json文件
//原數(shù)據(jù)
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "^(https|imaps)://.*",
"name" : "HTTPS and IMAPS",
"id" : 10000001,
"description" : "This service definition authorizes all application urls that support HTTPS and IMAPS protocols.",
"evaluationOrder" : 10000
}
// 需要將 "serviceId" : "^(https|imaps)://.*",
//修該成為:"serviceId" : "^(https|imaps|http)://.*" 即可!
如果你的CAS Server 服務(wù)的版本號(hào)在4.2 以下的,可以在去查詢一下配置方法,這里就不在記錄 4.2版本以下的修改方式!
3.2 靜態(tài)認(rèn)證用戶的添加
? 靜態(tài)認(rèn)證用戶是通過(guò) WEb-INF\classes\application.properties 文件中去配置的
? 在配置文件中,我們的認(rèn)證用戶數(shù)量可以添加配置多個(gè),如上圖所示,就配置了兩個(gè)認(rèn)證用戶。
? 如果你是通過(guò)IDEA來(lái)實(shí)現(xiàn)的CAS Server 服務(wù)的部署,那么只需要修改 main/resoources/application.properties文件。
3.3 配置數(shù)據(jù)庫(kù)查詢認(rèn)證用戶
- 首先是在maven中導(dǎo)入相關(guān)依賴jar
<dependencies>
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-jdbc</artifactId>
<version>6.5.0</version>
</dependency>
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-jdbc-drivers</artifactId>
<version>6.5.0</version>
</dependency>
<!--數(shù)據(jù)庫(kù)驅(qū)動(dòng)-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
</dependency>
</dependencies>
- 在通過(guò)application.properties文件中添加下面配置
# 注釋靜態(tài)驗(yàn)證的配置
cas.tgc.secure=false
cas.serviceRegistry.initFromJson=true
#加密迭代次數(shù)
cas.authn.jdbc.encode[0].numberOfIterations=3
#該列名的值可替代上面的值,但對(duì)密碼加密時(shí)必須取該值進(jìn)行處理
cas.authn.jdbc.encode[0].numberOfIterationsFieldName=
#鹽值固定列
cas.authn.jdbc.encode[0].saltFieldName=account
#靜態(tài)鹽值
cas.authn.jdbc.encode[0].staticSalt=.
cas.authn.jdbc.encode[0].sql=SELECT * FROM user WHERE account =?
#對(duì)處理鹽值后的算法
cas.authn.jdbc.encode[0].algorithmName=MD5
cas.authn.jdbc.encode[0].passwordFieldName=password
cas.authn.jdbc.encode[0].expiredFieldName=expired
cas.authn.jdbc.encode[0].disabledFieldName=disabled
#數(shù)據(jù)庫(kù)連接
cas.authn.jdbc.encode[0].url=jdbc:mysql://127.0.0.1:3306/test?useSSL=false&useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&characterEncoding=UTF-8
#cas.authn.jdbc.encode[0].dialect=org.hibernate.dialect.MySQL5Dialect
cas.authn.jdbc.encode[0].driverClass=com.mysql.jdbc.Driver
cas.authn.jdbc.encode[0].user=root
cas.authn.jdbc.encode[0].password=123456
同時(shí)我們需要注釋掉 之前配置在application.properties文件中的 靜態(tài)認(rèn)證用戶,即: cas.auth.accept.users=xxxx數(shù)據(jù)需要注釋掉。
3.4 解決未認(rèn)證授權(quán)的服務(wù)
? 在我們的CAS Client 客戶端服務(wù),跳轉(zhuǎn)到CAS Server 進(jìn)行用戶授權(quán)登錄認(rèn)證時(shí),我們的CAS Server 服務(wù)提示:
“未認(rèn)證授權(quán)的服務(wù)
CAS的服務(wù)記錄是空的,沒(méi)有定義服務(wù)。 希望通過(guò)CAS進(jìn)行認(rèn)證的應(yīng)用程序必須在服務(wù)記錄中明確定義?!?/p>
? 出現(xiàn)這種情況的原因是,我們的CAS Server 服務(wù)端中還沒(méi)有定義對(duì)應(yīng)的服務(wù),也就是我們的應(yīng)用服務(wù)(客戶端),需要在CAS Server 服務(wù)端進(jìn)行記錄信息,這樣才能通過(guò)Client客戶端跳轉(zhuǎn)到我CAS Server 服務(wù)端來(lái)進(jìn)行用戶認(rèn)證。
? 對(duì)應(yīng)客戶端在CAS Server 服務(wù)端中是否注冊(cè)成功,通過(guò)我們的CAS Server 服務(wù)啟動(dòng)時(shí)Tomcat的日志也能看出來(lái)。
? 沒(méi)有CAS Client 客戶端注冊(cè)的情況下的日志:
2023-11-09 16:37:59,935 INFO [org.apereo.cas.services.AbstractServicesManager] - <Loaded [0] service(s) from [InMemoryServiceRegistry].>
2023-11-09 16:38:59,947 INFO [org.apereo.cas.services.AbstractServicesManager] - <Loaded [0] service(s) from [InMemoryServiceRegistry].>
? CAS Server中注冊(cè)了CAS Client客戶端時(shí),Tomcat的啟動(dòng)日志:
2023-11-09 16:43:15,594 INFO [org.apereo.cas.ticket.registry.DefaultTicketRegistryCleaner] - <[0] expired tickets removed.>
2023-11-09 16:44:05,568 INFO [org.apereo.cas.services.AbstractServicesManager] - <Loaded [2] service(s) from [InMemoryServiceRegistry].>
2023-11-09 16:45:05,573 INFO [org.apereo.cas.services.AbstractServicesManager] - <Loaded [2] service(s) from [InMemoryServiceRegistry].>
- 詳細(xì)的配置過(guò)程:
Client Server 的配置位置: 在 HTTPSandIMAPS-10000001.json 文件中配置我們的客戶端信息。
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "^(https|http|imaps)://.*",
"name" : "HTTPS and IMAPS",
"id" : 10000001,
"description" : "This service definition authorizes all application urls that support HTTPS and IMAPS protocols.",
"evaluationOrder" : 10000
}
配置完json數(shù)據(jù)后,還需要改動(dòng)application.properties 文件中的信息,在改配置文件中添加下面兩行數(shù)據(jù),這樣才能使得我們的CAS Server服務(wù)端能夠讀取到配置的客戶端信息。
#是否開(kāi)啟json識(shí)別功能,默認(rèn)為false
cas.serviceRegistry.initFromJson=true
#忽略https安全協(xié)議,使用 HTTP 協(xié)議
cas.tgc.secure=false
上面兩個(gè)配置完成之后,重啟Tomcat服務(wù)。
通過(guò)Tomcat日志可以看出,我們的配置已經(jīng)生效,CAS Server服務(wù)端已經(jīng)讀取到配置文件中的客戶端。
三、SpringBoot集成cas-client-core實(shí)現(xiàn)CAS認(rèn)證
? 在部署完CAS Server 認(rèn)證服務(wù)端之后,我們就需要通過(guò)CAS Client客戶端集成CAS 服務(wù)實(shí)現(xiàn)集成認(rèn)證了。在SpringBoot 中 可以通過(guò)集成CAS-Client即可對(duì)Cas認(rèn)證進(jìn)行集成。
集成這部分文章引薦:https://blog.csdn.net/uziuzi669/article/details/119486588
- 引入POM依賴
這里需要根據(jù)自己部署的CAS Server 服務(wù)版本選擇合適的依賴版本
<!--Cas單點(diǎn)登錄認(rèn)證-->
<dependency>
<groupId>org.jasig.cas.client</groupId>
<artifactId>cas-client-core</artifactId>
<version>3.5.0</version>
</dependency>
- CAS集成的核心配置類
package com.wxxssf.CasAuthLogin.casConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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.Cas20ProxyReceivingTicketValidationFilter;
import org.jasig.cas.client.validation.Cas30ProxyReceivingTicketValidationFilter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
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 org.springframework.core.Ordered;
/**
* @Description: cas集成核心配置類
* @ClassName: CasConfig
*/
@Configuration
@Slf4j
@ConditionalOnProperty(value ="cas.validation-type",havingValue = "cas") //根據(jù)應(yīng)用程序配置文件中的屬性值來(lái)控制Bean的創(chuàng)建和加載
public class CasConfig {
/**
*@Description 需要走cas攔截器的地址
*/
@Value("${cas.urlPattern:/cas/loginByNameAndCardNo}")
private String filterUrl;
/**
* 默認(rèn)的cas地址,防止通過(guò) 配置信息獲取不到,CAS服務(wù)端的登錄地址,login為固定值
*/
@Value("${cas.server-url-prefix:https://ciap7.wisedu.com/authserver/login}")
private String casServerUrl;
/**
* 應(yīng)用校驗(yàn)訪問(wèn)地址(這個(gè)地址需要在cas服務(wù)端進(jìn)行配置)
*/
@Value("${cas.authentication-url:https://ciap7.wisedu.com/authserver}")
private String authenticationUrl;
/**
* 應(yīng)用訪問(wèn)地址(這個(gè)地址需要在cas服務(wù)端進(jìn)行配置)
*/
@Value("${cas.client-host-url:http://localhost:8090}")
private String appServerUrl;
@Bean
public ServletListenerRegistrationBean servletListenerRegistrationBean() {
log.info(" servletListenerRegistrationBean \n cas 單點(diǎn)登錄配置 \n appServerUrl = " + appServerUrl + "\n casServerUrl = " + casServerUrl);
ServletListenerRegistrationBean listenerRegistrationBean = new ServletListenerRegistrationBean();
listenerRegistrationBean.setListener(new SingleSignOutHttpSessionListener());
listenerRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return listenerRegistrationBean;
}
/**
* 單點(diǎn)登錄退出
*/
@Bean
public FilterRegistrationBean singleSignOutFilter() {
log.info(" servletListenerRegistrationBean ");
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new SingleSignOutFilter());
registrationBean.addUrlPatterns(filterUrl);
registrationBean.addInitParameter("casServerUrlPrefix", casServerUrl);
registrationBean.setName("CAS Single Sign Out Filter");
registrationBean.setOrder(1);
return registrationBean;
}
/**
* 單點(diǎn)登錄認(rèn)證
*/
@Bean
public FilterRegistrationBean AuthenticationFilter() {
log.info(" AuthenticationFilter ");
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new AuthenticationFilter());
registrationBean.addUrlPatterns(filterUrl);
registrationBean.setName("CAS Filter");
registrationBean.addInitParameter("casServerLoginUrl", casServerUrl);
registrationBean.addInitParameter("serverName", appServerUrl);
registrationBean.setOrder(1);
return registrationBean;
}
/**
* 決定票據(jù)驗(yàn)證過(guò)濾器的版本,默認(rèn)30,old是20版
*/
@Value("${cas.filterVersion:new}")
private String filterVersion;
/**
* 單點(diǎn)登錄校驗(yàn)
*/
@Bean
public FilterRegistrationBean Cas30ProxyReceivingTicketValidationFilter() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
if (StringUtils.isNotBlank(filterVersion) && filterVersion.equals("old")){
log.info(" Cas20ProxyReceivingTicketValidationFilter ");
registrationBean.setFilter(new Cas20ProxyReceivingTicketValidationFilter());
}else {
log.info(" Cas30ProxyReceivingTicketValidationFilter ");
registrationBean.setFilter(new Cas30ProxyReceivingTicketValidationFilter());
}
registrationBean.addUrlPatterns(filterUrl);
registrationBean.setName("CAS Validation Filter");
registrationBean.addInitParameter("casServerUrlPrefix", authenticationUrl);
registrationBean.addInitParameter("serverName", appServerUrl);
registrationBean.setOrder(1);
return registrationBean;
}
/**
* 單點(diǎn)登錄請(qǐng)求包裝
*/
@Bean
public FilterRegistrationBean httpServletRequestWrapperFilter() {
log.info(" httpServletRequestWrapperFilter ");
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
//registrationBean.setFilter(new HttpServletRequestWrapperFilter());
registrationBean.setFilter(new HttpServletRequestWrapperFilter());
registrationBean.addUrlPatterns(filterUrl);
registrationBean.setName("CAS HttpServletRequest Wrapper Filter");
registrationBean.setOrder(1);
return registrationBean;
}
}
? 上面的單點(diǎn)登錄校驗(yàn)器,需要我們創(chuàng)建票據(jù)驗(yàn)證 TicketValidationFiter ,但是需要注意的是票據(jù)驗(yàn)證過(guò)濾器有兩種類型分別是:Cas30ProxyReceivingTicketValidationFilter 和 Cas20ProxyReceivingTicketValidationFilter。
? 在認(rèn)證票據(jù)是會(huì)出現(xiàn)報(bào)錯(cuò)的情況,這時(shí)候就需要考慮這個(gè)票據(jù)驗(yàn)證器TicketValidationFiter的版本問(wèn)題,CAS Server 的版本是否兼容Filter,從而引起沖突問(wèn)題,具體使用哪一種票據(jù)驗(yàn)證器,需要根據(jù)實(shí)際情況去調(diào)整,這個(gè)票據(jù)驗(yàn)證器的選擇也是通過(guò)application.properties配置文件中去實(shí)現(xiàn)的動(dòng)態(tài)選擇。
?
- 上面的各個(gè)連接地址是通過(guò)application.properties文件進(jìn)行動(dòng)態(tài)配置讀取的,示例如下:
#===Cas集成認(rèn)證===
#需要走攔截器的地址 /api/loginByNameAndCardNo :驗(yàn)票攔截路徑
cas.urlPattern = /cas/loginByNameAndCardNo
# 客戶端如果要登錄,會(huì)跳轉(zhuǎn)到CAS服務(wù)端的登錄地址(認(rèn)證地址) : 認(rèn)證中心登錄頁(yè)面地址
# http://192.168.0.145:8080/cas/login
cas.server-url-prefix = http://192.168.0.145:8080/cas/login
# CAS 服務(wù)端地址(認(rèn)證平臺(tái)地址):認(rèn)證中心地址
# http://192.168.0.145:8080/cas
cas.authentication-url = http://192.168.0.145:8080/cas
# 客戶端在CAS服務(wù)端登錄成功后,自動(dòng)從CAS服務(wù)端跳轉(zhuǎn)回客戶端的地址 :應(yīng)用地址,也就是自己的系統(tǒng)地址。 https://cwfw.mtxy.edu.cn
cas.client-host-url = http://192.168.0.145:8998
# Ticket校驗(yàn)器使用 Cas30ProxyReceivingTicketValidationFilter :動(dòng)態(tài)開(kāi)啟 cas 單點(diǎn)登錄
cas.validation-type = cas
# 驗(yàn)票器版本
cas.filterVersion = new
- CAS認(rèn)證用戶信息Vo類
package com.wxxssf.CasAuthLogin.Vo;
import lombok.Setter;
import java.util.Map;
/**
* @Description: Cas認(rèn)證用戶信息
*/
@Getter
@Setter
public class CasUserInfo {
/** 用戶名 */
private String userName;
/** 用戶 */
private String userAccount;
/** 用戶信息 */
private Map<String, Object> attributes;
}
- 認(rèn)證通過(guò)后返回?cái)?shù)據(jù),獲取用戶信息的工具類封裝
package com.wxxssf.CasAuthLogin.casUtils;
import com.wxxssf.CasAuthLogin.Vo.CasUserInfo;
import lombok.extern.slf4j.Slf4j;
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.validation.Assertion;
import javax.servlet.http.HttpServletRequest;
import java.security.Principal;
import java.util.Map;
/**
* @Description: Cas認(rèn)證工具類
*/
@Slf4j
public class CasUtil {
/**
* cas client 默認(rèn)的session key _const_cas_assertion_
*/
public final static String CAS = "_const_cas_assertion_";
/**
* 封裝CasUserInfo
*/
public static CasUserInfo getCasUserInfoFromCas(HttpServletRequest request) {
System.out.println("request.toString() = " + request.toString());
Object object = request.getSession().getAttribute(CAS);
if (null == object) {
return null;
}
Assertion assertion = (Assertion) object;
return buildCasUserInfoByCas(assertion);
}
/**
* 構(gòu)建CasUserInfo
*/
private static CasUserInfo buildCasUserInfoByCas(Assertion assertion) {
if (null == assertion) {
log.error(" Cas沒(méi)有獲取到用戶 ");
return null;
}
CasUserInfo casUserInfo = new CasUserInfo();
String userName = assertion.getPrincipal().getName();
log.info(" cas對(duì)接登錄用戶= " + userName);
log.info("用戶消息:"+assertion.getPrincipal().toString());
casUserInfo.setUserAccount(userName);
//獲取屬性值
Map<String, Object> attributes = assertion.getPrincipal().getAttributes();
Object name = attributes.get("cn");
casUserInfo.setUserName(name == null ? userName : name.toString());
casUserInfo.setAttributes(attributes);
return casUserInfo;
}
/**
* @Description new:獲取用戶信息全部數(shù)據(jù)展示示例:
*
* userInfo = {
* "userAccount":"20220037",
* "attributes":{"isFromNewLogin":"false",
* "authenticationDate":"2023-07-27T09:15:46.799+08:00[GMT+08:00]",
* "loginType":"1",
* "successfulAuthenticationHandlers":"com.wisedu.minos.config.login.RememberMeUsernamePasswordHandler",
* "cn":"xxx",
* "userName":"xxx",
* "samlAuthenticationStatementAuthMethod":"urn:oasis:names:tc:SAML:1.0:am:unspecified",
* "credentialType":"MyRememberMeCaptchaCredential",
* "uid":"20220037",
* "authenticationMethod":"com.wisedu.minos.config.login.RememberMeUsernamePasswordHandler",
* "longTermAuthenticationRequestTokenUsed":"false",
* "containerId":"ou=1000001,ou=People",
* "cllt":"userNameLogin",
* "dllt":"generalLogin"},
* "userName":"xxx"}
*/
public static CasUserInfo getUserInfo(HttpServletRequest request){
Principal userPrincipal = request.getUserPrincipal();
CasUserInfo casUserInfo = new CasUserInfo();
if (userPrincipal != null && userPrincipal instanceof AttributePrincipal){
AttributePrincipal attributePrincipal = (AttributePrincipal) userPrincipal;
//獲取用戶信息中公開(kāi)的Attributes部分
Map<String, Object> map = attributePrincipal.getAttributes();
String cn = (String)map.get("cn");
String user_name = (String)map.get("userName");
casUserInfo.setUserAccount(userPrincipal.getName());
casUserInfo.setAttributes(map);
casUserInfo.setUserName(cn == null ? userPrincipal.getName() : cn );
}
return casUserInfo;
}
// TODO: 2023-07-24 AttributePrincipal類和Assertion 區(qū)別~~?。?/span>
}
- 單點(diǎn)登錄接口
/**
* cas 單點(diǎn)登錄
*
* @param request 請(qǐng)求頭(姓名+身份證號(hào))
* @param ticket cas 票據(jù)
* @return
*/
@GetMapping(value = "/api/loginByNameAndCardNo")
@ApiOperation("cas單點(diǎn)登錄")
public String loginByNameAndCardNo(HttpServletRequest request) {
CasUserInfo userInfo = CasUtil.getCasUserInfoFromCas(request);
log.info("userInfo = " + JSONObject.toJSON(userInfo));
String url = "main";
MadStudent student = new MadStudent();
student.setName(userInfo.getAttributes().get("Name").toString());
student.setCardNo(userInfo.getAttributes().get("IdCard").toString());
// 登錄用戶校驗(yàn)
// xxxxx
// 用戶數(shù)據(jù)為 true
// 跳轉(zhuǎn)頁(yè)面
return "url";
} else {
return "redirect:" + casUrl;
}
}
四、其他方式實(shí)現(xiàn)的認(rèn)證案例記錄(略):
? 注:該部分內(nèi)容僅僅為自己記錄使用,可跳過(guò)!文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-794904.html
4.1 基于深信服IDTrust 實(shí)現(xiàn)Cas認(rèn)證
- 方式一:基于JDK的java.net包中已經(jīng)提供了訪問(wèn)Http協(xié)議的 HttpURLConnection 類實(shí)現(xiàn)
<%@ page import="java.net.URL" %>
<%@ page import="java.net.URLConnection" %>
<%@ page import="javax.net.ssl.HttpsURLConnection" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.io.ByteArrayOutputStream" %>
<%@ page import="java.net.HttpURLConnection" %>
<%@ page import="javax.net.ssl.SSLSession" %>
<%@ page import="javax.net.ssl.HostnameVerifier" %>
<%@ page import="java.net.URLEncoder" %><%--
Created by IntelliJ IDEA.
User: AD
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%!
private static void trustAllHttpsCertificates() throws Exception {
javax.net.ssl.TrustManager[] trustAllCerts = new javax.net.ssl.TrustManager[1];
javax.net.ssl.TrustManager tm = new miTM();
trustAllCerts[0] = tm;
javax.net.ssl.SSLContext sc = javax.net.ssl.SSLContext
.getInstance("SSL");
sc.init(null, trustAllCerts, null);
javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(sc
.getSocketFactory());
}
static class miTM implements javax.net.ssl.TrustManager,
javax.net.ssl.X509TrustManager {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public boolean isServerTrusted(
java.security.cert.X509Certificate[] certs) {
return true;
}
public boolean isClientTrusted(
java.security.cert.X509Certificate[] certs) {
return true;
}
public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType)
throws java.security.cert.CertificateException {
return;
}
public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType)
throws java.security.cert.CertificateException {
return;
}
}
%>
<%
String infoMessage =null;
HostnameVerifier hv = new HostnameVerifier() {
public boolean verify(String urlHostName, SSLSession session) {
String infoMessage ="Warning: URL Host: " + urlHostName + " vs. " + session.getPeerHost();
System.out.println("info = " + infoMessage);
return true;
}
};
trustAllHttpsCertificates();
HttpsURLConnection.setDefaultHostnameVerifier(hv);
String service = "http://xxxxx:xx/casLoginTicket.jsp"; //回調(diào)地址
String encode = URLEncoder.encode(service);
URL url = new URL("https://xxxx/cas/login?service="+encode); //認(rèn)證地址
HttpsURLConnection httpsURLConnection = (HttpsURLConnection) url.openConnection();
httpsURLConnection.setDoInput(true);
httpsURLConnection.setRequestMethod("GET");
httpsURLConnection.setRequestProperty("Content-Type","application/json;charset=utf-8");
System.out.println("準(zhǔn)備執(zhí)行李連接!!");
httpsURLConnection.connect();
InputStream inputStream = httpsURLConnection.getInputStream();
byte[] buff = new byte[1024];
int len = -1;
StringBuffer stringBuffer = new StringBuffer();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
while((len = inputStream.read(buff)) != -1){
stringBuffer.append(new String(buff,0,len,"utf-8"));
byteArrayOutputStream.write(buff,0,len);
}
System.out.println("stringBuffer = " + stringBuffer);
System.out.println("byteArrayOutputStream.toString() = " + byteArrayOutputStream.toString());
//關(guān)閉資源
byteArrayOutputStream.close();
inputStream.close();
httpsURLConnection.disconnect();
//response.getWriter().write( stringBuffer.toString());
%>
<html>
<head>
<title>加載中請(qǐng)稍等....</title>
</head>
<script>
console.log("加載中....")
</script>
<body>
<h3><%=encode%></h3>
<h3><%=stringBuffer%></h3>
</body>
<script>
</script>
</html>
- 方式二:基于cn.hutool 的http 包中已經(jīng)提供了訪問(wèn)Http協(xié)議的 Hutool-http 類實(shí)現(xiàn)
<%@ page import="cn.hutool.http.HttpRequest" %>
<%@ page import="cn.hutool.http.HttpResponse" %>
<%@ page import="java.net.URLEncoder" %>
<%--
Created by IntelliJ IDEA.
User: AD
To change this template use File | Settings | File Templates.
--%>
<%
System.out.println("進(jìn)入集成跳轉(zhuǎn)頁(yè)面?。?);
String service = "http://xxxx:8888/casLoginTicket.jsp"; //回調(diào)地址
String encode = URLEncoder.encode(service);
HttpResponse result = HttpRequest
.get("https://xxxx/cas/login?service="+encode) //CAS認(rèn)證地址
.header("Content-Type", "application/json;charset=UTF-8")
.timeout(60 * 1000)
.execute();
int status = result.getStatus();
System.out.println("status = " + status);
String body = result.body();
System.out.println("body = " + body);
response.getWriter().write(body);
%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>加載中請(qǐng)稍等....</title>
</head>
<script>
console.log("加載中....")
</script>
<body>
<h3><%=status%></h3>
<h3><%=body%></h3>
</body>
<script>
console.log("請(qǐng)求響應(yīng)數(shù)據(jù)展示:<%=status%>")
</script>
</html>
4.2 基于職教云平臺(tái)的統(tǒng)一認(rèn)證案例
- 統(tǒng)一認(rèn)證訪問(wèn)主路徑 jsp文件 (該平臺(tái)的認(rèn)證是需要提前申請(qǐng)入駐,然后綁定對(duì)應(yīng)回調(diào)地址和認(rèn)證地址之后才能進(jìn)行認(rèn)證)
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%--<%@ page import="org.apache.commons.lang.StringUtils"%>--%>
<%--<%@ page import="com.ibatis.sqlmap.client.SqlMapClient,com.ufgov.midas.yy.util.*,java.util.*"%>--%>
<%--<%@ page import="com.ufgov.midas.qx.util.*,com.ufgov.midas.qx.sqlmap.*,java.sql.*,com.ufgov.midas.pt.common.DAOFactory" %>--%>
<%--<%@ page import="org.ly.uap.client.authentication.AttributePrincipal"%>--%>
<%@ page import="sun.misc.*"%>
<%--應(yīng)用訪問(wèn)主路徑j(luò)sp--%>
<%
//認(rèn)證地址:
String url ="https://xxxxxxx/provider/oauth2/authorize?" +
"response_type=code" +
"&client_id=XXXX"+
"&redirect_uri=http://XXXXX:8888/ASXYSSOLoginSuccessNEW.jsp"+
"&scope=openid";
//response.sendRedirect(url);
%>
<head>
<meta charset="utf-8">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<link href="css/style.css" rel="stylesheet" type="text/css">
<script type="text/javascript" src="js/jquery-1.4.2.min.js"></script>
<title>SSO_RZ_Welcome!!!</title>
<style>
.button {
......
}
.button2:hover {
box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24),0 17px 50px 0 rgba(0,0,0,0.19);
}
div{
....
}
</style>
<script LANGUAGE="JavaScript">
var url ="<%=url%>"
// var urlQd = "https://idaastest.gzzjzhy.com/provider/oauth2/authorize?" +
// "response_type=code" +
// "&client_id=1688831259003981824"+
// "&redirect_uri=http://111.85.31.2:8888/ASXYSSOLoginSuccess.jsp"+
// "&scope=????";
function getAuthCode(){
// alert("跳轉(zhuǎn)統(tǒng)一認(rèn)證URL="+url)
window.location.href=url;
}
</script>
</head>
<body style="background: #fff;">
<div>
</div>
</body>
</html>
<script language="javascript">getAuthCode();</script>
4.3 基于釘釘?shù)募烧J(rèn)證登錄
- 首先是認(rèn)證訪問(wèn)頁(yè)面代碼
<%@ page contentType="text/html; charset=GBK" language="java" import="java.sql.*" errorPage="" %>
<%@ page import="com.alibaba.fastjson.JSONObject"%>
<%@ page import="com.dingtalk.api.DefaultDingTalkClient"%>
<%@ page import="com.dingtalk.api.DingTalkClient"%>
<%@ page import="com.dingtalk.api.request.OapiGettokenRequest"%>
<%@ page import="com.dingtalk.api.request.OapiUserGetuserinfoRequest"%>
<%@ page import="com.dingtalk.api.response.OapiGettokenResponse"%>
<%@ page import="com.dingtalk.api.request.OapiV2UserGetRequest"%>
<%@ page import="com.dingtalk.api.response.OapiUserGetuserinfoResponse"%>
<%@ page import="com.taobao.api.ApiException"%>
<%@ page import="com.dingtalk.api.response.OapiV2UserGetResponse"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<%@page import="java.util.Date"%>
<%@page import="java.text.SimpleDateFormat"%>
<%@ page import="java.util.List" %>
<%@ page import="java.util.Map" %>
<%@ page import="javax.lang.model.element.NestingKind" %>
<script language="javascript" src="./des.js"></script>
<meta http-equiv="Content-Type" content="text/html; charset=GBK" />
<title>xxxx</title>
<style>
html,body {。。。。。}
#info{}
#down-info{。。。。。}
</style>
<SCRIPT LANGUAGE=javascript>
{
<%
if(ckey!=""){
//com.ufgov.midas.pt.service.changeLogin cl = new com.ufgov.midas.pt.service.changeLogin();
//String skey = cl.MD5(yonghu + sj + ip);
String skey = (yonghu + sj + ip);
Date now=new Date();
SimpleDateFormat f=new SimpleDateFormat("yyyyMMdd");
String DateStr = f.format(now);
skey = skey.toLowerCase();
ckey = ckey.toLowerCase();
if("".equals(sj)){
sj = DateStr;
}
String cood = request.getParameter("msg");
System.out.println("獲取到的cood值為=" + cood);
//與釘釘進(jìn)行交互
if(yonghu == "" || yonghu == null){
// 獲取access_token,注意正式代碼要有異常流處理
DingTalkClient client = new DefaultDingTalkClient("https://xxxxx/gettoken");
OapiGettokenRequest request1 = new OapiGettokenRequest();
request1.setAppkey("dingcz1ruaglmqxxxx");
request1.setAppsecret("nVgvFHLiYiHxxxGmcK-Widi_xp29vIxxxXu71mCkOtxxxxs");
request1.setHttpMethod("GET");
OapiGettokenResponse response1 = null;
try {
response1 = client.execute(request1);
} catch (ApiException e) {
throw new RuntimeException(e);
}
System.out.println("獲取用戶token:response1.getBody() = " + response1.getBody());
JSONObject jsonObject =JSONObject.parseObject(response1.getBody());
String access_token = (String) jsonObject.get("access_token");
//獲取用戶id
DingTalkClient client2 = new DefaultDingTalkClient("https://xxxxx/user/getuserinfo");
OapiUserGetuserinfoRequest request2 = new OapiUserGetuserinfoRequest();
//todo--SSO單點(diǎn)登錄授權(quán)碼
String requestAuthCode = cood;
request2.setCode(requestAuthCode);
request2.setHttpMethod("GET");
OapiUserGetuserinfoResponse response2= null;
try {
response2 = client2.execute(request2, access_token);
System.out.println("獲取用戶id信息info:response2.getBody() = " + response2.getBody());
} catch (ApiException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 查詢得到當(dāng)前用戶的userId
// 獲得到userId之后應(yīng)用應(yīng)該處理應(yīng)用自身的登錄會(huì)話管理(session),避免后續(xù)的業(yè)務(wù)交互(前端到應(yīng)用服務(wù)端)每次都要重新獲取用戶身份,提升用戶體驗(yàn)
String userId = response2.getUserid();
System.out.println("userId = " + userId);
//獲取用戶信息
String userInfo = null;
try {
DingTalkClient client3 = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/user/get");
OapiV2UserGetRequest req = new OapiV2UserGetRequest();
// req.setUserid("001");
req.setUserid(userId);
req.setLanguage("zh_CN");
OapiV2UserGetResponse rsp = client3.execute(req, access_token);
System.out.println("獲取用戶資料info:rsp.getBody() = " + rsp.getBody());
userInfo = rsp.getBody();
} catch (ApiException e) {
e.printStackTrace();
//獲取userid
System.out.println("JSONObject.parseObject(userInfo) = " + JSONObject.parseObject(userInfo));
JSONObject result = (JSONObject) JSONObject.parseObject(userInfo).get("result");
System.out.println(result.get("userid"));
//yonghu = (String) result.get("userid");
// 獲取job_number
JSONObject result2 = (JSONObject) JSONObject.parseObject(userInfo).get("result");
if (result2 == null){
System.out.println("不存在該用戶");
}else {
//yonghu = (String) result2.get("job_number");
//工號(hào)
String number =(String) result2.get("job_number");
System.out.println("result2.get(\"job_number\") = " +number );
yonghu = number;
}
//role_list:id
JSONObject result3 = (JSONObject) JSONObject.parseObject(userInfo).get("result");
List roleList = (List) result3.get("role_list");
if (roleList.size()>0){
Map map =(Map) roleList.get(0);
System.out.println("map="+map);
//yonghu = map.get("id").toString();
}
}
%>
// if (code==""||code==null){
// alert("免登授權(quán)碼獲取失敗!");//免登授權(quán)碼獲取失敗
// closeWin();
// return false;
// } else
if("<%=yonghu%>"=="" || "<%=yonghu%>"==null){
alert("用戶信息錯(cuò)誤,請(qǐng)重新登錄!");//可能是key值不正確
alert("yonghu="+"<%=yonghu%>");
closeWin();
return false;
}else{
alert("yonghu="+"<%=yonghu%>")
alert("驗(yàn)證成功自動(dòng)登錄!!")
alert("用戶登錄名正確!")
}
<%
}
%>
var uid="<%=yonghu%>";
doLogin(productName,uid);
}
</SCRIPT>
</head>
<body>
<table border=1 width=100% height=100%><tr><td align="center" valign="middle">
<div id="u8check">
<div id="info"> 客戶端程序, 請(qǐng)稍候...</div>
</div>
</td></tr></table>
</body>
</html><script language="javascript">doCallU8();</script>
到了這里,關(guān)于SpringBoot 實(shí)現(xiàn)CAS Server統(tǒng)一登錄認(rèn)證的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!