SpringBoot?實現(xiàn)CAS?Server統(tǒng)一登錄認證的詳細步驟
SpringBoot 集成CAS Server
一、CAS Service服務介紹
? CAS(Central Authentication Service)中心授權(quán)服務,是一個開源項目,目的在于為Web應用系統(tǒng)提供一種可靠的單點登錄。
? 在整個認證的流程中的整個流程大概是:首先由CAS Client(我們的客戶端應用)發(fā)起請求,CAS Client 會重定向到CAS Server進行登錄,CAS Server進行賬戶校驗且多個CAS Client 之間可以共享登錄的 session ,Server 和 Client 是一對多的關(guān)系?;贑AS的SSO訪問流程步驟:
- 訪問服務: CAS Client 客戶端發(fā)送請求訪問應用系統(tǒng)提供的服務資源。
- 定向認證: CAS Client 客戶端會重定向用戶請求到 CAS Server 服務器。
- 用戶認證: 用戶在瀏覽器端輸入用戶驗證信息,CAS Server服務端完成用戶身份認證。
- 發(fā)放票據(jù): CAS Server服務器會產(chǎn)生一個隨機的 Service Ticket 。
- 驗證票據(jù): CAS Server服務器驗證票據(jù) Service Ticket 的合法性,驗證通過后,允許客戶端訪問服務。
- 傳輸用戶信息: CAS Server 服務器驗證票據(jù)通過后,傳輸用戶認證結(jié)果信息給客戶端。
從結(jié)構(gòu)上看,CAS 包含兩個部分: CAS Server 和 CAS Client 。 CAS Server 需要獨立部署,主要負責對用戶的認證工作; CAS Client 負責處理對客戶端受保護資源的訪問請求,需要登錄時,重定向到 CAS Server。
? CAS Client 與受保護的客戶端應用部署在一起,以 Filter 方式保護受保護的資源。對于訪問受保護資源的每個 Web 請求, CAS Client 會分析該請求的 Http 請求中是否包含 Service Ticket。如果沒有,則說明當前用戶尚未登錄,于是將請求重定向到指定好的 CAS Server 登錄地址,并傳遞 Service (也就是要訪問的目的資源地址),以便登錄成功過后轉(zhuǎn)回該地址。
? 在流程圖中的第三步輸入認證信息,登陸成功后,CAS Server 隨機產(chǎn)生一個相當長度、唯一、不可偽造的 Service Ticket,并緩存以待將來驗證。之后系統(tǒng)自動重定向到 Service 所在地址,并為客戶端瀏覽器設(shè)置一個 Ticket Granted Cookie(TGC),CAS Client 在拿到 Service 和新產(chǎn)生的 Ticket 過后,在第 5,6 步中與 CAS Server 進行身份核實,以確保 Service Ticket 的合法性。
二、CAS Server服務的搭建
2.1 下載cas-overlay-template
? 這里為大家提供一個 5.1版本的 git地址:<https://github.com/apereo/cas-overlay-template/tree/5.1
5.3版本的git地址
網(wǎng)上都能找到很多下載方式的,或者私信我 我測試使用的是cas-overlay-template-5.3
? 獲取到項目后zip的方式解壓出來后的目錄如下:
2.2 使用外部Tomcat部署CAS Server
解壓出來文件夾之后,就可以進行打包運行了。在解壓的目錄下打開命令行 到安裝目錄下 使用build.cmd run 來進行編譯打包。過程可能需要花點時間,
在打包完成之后就會啟動我們的CAS Server,但是服務現(xiàn)在是沒有正常啟動的,因為Cas server 配置證書路徑是基于linux的,而我們是在windows環(huán)境下部署,目錄結(jié)構(gòu)不一致導致無法找到相應的文件,如果是linux環(huán)境的話就可以成功啟動了。
? 啟動失?。?/p>
打包完成之后會在解壓的目錄下面多了target文件夾:
我們將Cas.war(或者直接使用Cas文件夾) 復制到本機的 Tomcat 的 webapp目錄中,啟動Tomcat即可
啟動Tomcat,通過Tomcat日志就可以看見我們的CAS Server是否啟動成功了:
然后我們就能訪問部署到本地CAS Server 服務了。通過 127.0.0.1:8080/cas/login 來訪問CAS服務,這里的地址需要根據(jù)自己的實際情況來定(比如你部署的Tomcat服務默認地址8080是否改變過 等情況):
至此我們使用外部Tomcat部署CAS Server服務就成功了!
這里可以使用默認的賬戶密碼(賬:casuser 密: Mellon)進行登錄,
至于后續(xù)可能出現(xiàn)的一些疑難雜癥(包括靜態(tài)用戶的設(shè)置、http的支持等),文章下面部門章節(jié)會進行介紹。目前先簡單的將CAS Server服務部署上去!
2.3 使用IDEA部署CAS Server
? 在IDEA中我們直接將項目通過maven工具加載pom文件中的jar包和package命令生成運行包target。然后在項目中建立本地項目的src/main/java 和 src/main/resources目錄。最后將target包中的/cas/WEB_INF/classes/services和aplication.properties和log4j2.xml以及/cas/WEB_INF/classes/META-INF復制到resources目錄中。
- 首先通過IDEA將解壓后的CAS Server文件打開進行打包
打開文件后,首相將項目的mave配置好(File/Settings/Build,…/Build Tools/Maven 中配置好自己本地的maven地址),然后加載pom文件中的Jar包。
maven配置好之后利用maven對項目進行打包
移動相關(guān)配置文件
項目打包完成之后,在項目中創(chuàng)建Java文件夾和resources文件夾,將上述提到的target文件中的4個文件復制到我們自己創(chuàng)建的resources目錄下
?
給CAS Server 項目配置tomcat服務
首先給CAS Server 添加tamcat服務器
? 配置好Tomcat服務的地址 以及 端口號等基本信息
?
? 部署Tomcat服務器
選擇war exploded模式進行部署我們的Tomcat,選擇之后,將下面的 Application context 基路徑中的數(shù)據(jù)改為 / 即可。
- 為CAS Server 配置JDK
打開 File/Project Stucture/Project中進行設(shè)置
所有配置都準備完畢之后 我們就可以啟動Tomcat了,tomcat啟動之后就能夠正常訪問到我們的CAS Server服務了!
通過 127.0.0.1:8080/login 即可訪問到我們的CAS Service。這里可以使用默認的賬戶密碼(賬:casuser 密: Mellon)進行登錄。
推出登錄的地址: 127.0.0.1:8080/logout
三、CAS Server的其他配置
3.1 CAS Server 去掉https驗證
? 這里這樣設(shè)置的目的是為了,后續(xù)我們通過項目去請求CAS Server 服務時,能夠通過 http 的方式去訪問我們的CAS Server服務!
? 在CAS Server服務 4.2版本時對整體的架構(gòu)進行了一個優(yōu)化。
允許Http訪問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 服務的版本號在4.2 以下的,可以在去查詢一下配置方法,這里就不在記錄 4.2版本以下的修改方式!
3.2 靜態(tài)認證用戶的添加
? 靜態(tài)認證用戶是通過 WEb-INF\classes\application.properties 文件中去配置的
? 在配置文件中,我們的認證用戶數(shù)量可以添加配置多個,如上圖所示,就配置了兩個認證用戶。
? 如果你是通過IDEA來實現(xiàn)的CAS Server 服務的部署,那么只需要修改 main/resoources/application.properties文件。
3.3 配置數(shù)據(jù)庫查詢認證用戶
首先是在maven中導入相關(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ù)庫驅(qū)動--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.17</version> </dependency> </dependencies>
在通過application.properties文件中添加下面配置
# 注釋靜態(tài)驗證的配置 cas.tgc.secure=false cas.serviceRegistry.initFromJson=true #加密迭代次數(shù) cas.authn.jdbc.encode[0].numberOfIterations=3 #該列名的值可替代上面的值,但對密碼加密時必須取該值進行處理 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 =? #對處理鹽值后的算法 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ù)庫連接 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
同時我們需要注釋掉 之前配置在application.properties文件中的 靜態(tài)認證用戶,即: cas.auth.accept.users=xxxx數(shù)據(jù)需要注釋掉。
3.4 解決未認證授權(quán)的服務
? 在我們的CAS Client 客戶端服務,跳轉(zhuǎn)到CAS Server 進行用戶授權(quán)登錄認證時,我們的CAS Server 服務提示:
“未認證授權(quán)的服務
CAS的服務記錄是空的,沒有定義服務。 希望通過CAS進行認證的應用程序必須在服務記錄中明確定義。”
? 出現(xiàn)這種情況的原因是,我們的CAS Server 服務端中還沒有定義對應的服務,也就是我們的應用服務(客戶端),需要在CAS Server 服務端進行記錄信息,這樣才能通過Client客戶端跳轉(zhuǎn)到我CAS Server 服務端來進行用戶認證。
? 對應客戶端在CAS Server 服務端中是否注冊成功,通過我們的CAS Server 服務啟動時Tomcat的日志也能看出來。
? 沒有CAS Client 客戶端注冊的情況下的日志:
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中注冊了CAS Client客戶端時,Tomcat的啟動日志:
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].>
詳細的配置過程:
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ù)后,還需要改動application.properties 文件中的信息,在改配置文件中添加下面兩行數(shù)據(jù),這樣才能使得我們的CAS Server服務端能夠讀取到配置的客戶端信息。
#是否開啟json識別功能,默認為false
cas.serviceRegistry.initFromJson=true
#忽略https安全協(xié)議,使用 HTTP 協(xié)議
cas.tgc.secure=false
上面兩個配置完成之后,重啟Tomcat服務。
通過Tomcat日志可以看出,我們的配置已經(jīng)生效,CAS Server服務端已經(jīng)讀取到配置文件中的客戶端。
三、SpringBoot集成cas-client-core實現(xiàn)CAS認證
? 在部署完CAS Server 認證服務端之后,我們就需要通過CAS Client客戶端集成CAS 服務實現(xiàn)集成認證了。在SpringBoot 中 可以通過集成CAS-Client即可對Cas認證進行集成。
集成這部分文章引薦://www.dbjr.com.cn/article/226058.htm
引入POM依賴
這里需要根據(jù)自己部署的CAS Server 服務版本選擇合適的依賴版本
<!--Cas單點登錄認證--> <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ù)應用程序配置文件中的屬性值來控制Bean的創(chuàng)建和加載 public class CasConfig { /** *@Description 需要走cas攔截器的地址 */ @Value("${cas.urlPattern:/cas/loginByNameAndCardNo}") private String filterUrl; /** * 默認的cas地址,防止通過 配置信息獲取不到,CAS服務端的登錄地址,login為固定值 */ @Value("${cas.server-url-prefix:https://ciap7.wisedu.com/authserver/login}") private String casServerUrl; /** * 應用校驗訪問地址(這個地址需要在cas服務端進行配置) */ @Value("${cas.authentication-url:https://ciap7.wisedu.com/authserver}") private String authenticationUrl; /** * 應用訪問地址(這個地址需要在cas服務端進行配置) */ @Value("${cas.client-host-url:http://localhost:8090}") private String appServerUrl; @Bean public ServletListenerRegistrationBean servletListenerRegistrationBean() { log.info(" servletListenerRegistrationBean \n cas 單點登錄配置 \n appServerUrl = " + appServerUrl + "\n casServerUrl = " + casServerUrl); ServletListenerRegistrationBean listenerRegistrationBean = new ServletListenerRegistrationBean(); listenerRegistrationBean.setListener(new SingleSignOutHttpSessionListener()); listenerRegistrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); return listenerRegistrationBean; } /** * 單點登錄退出 */ @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; } /** * 單點登錄認證 */ @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ù)驗證過濾器的版本,默認30,old是20版 */ @Value("${cas.filterVersion:new}") private String filterVersion; /** * 單點登錄校驗 */ @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; } /** * 單點登錄請求包裝 */ @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; } }
? 上面的單點登錄校驗器,需要我們創(chuàng)建票據(jù)驗證 TicketValidationFiter ,但是需要注意的是票據(jù)驗證過濾器有兩種類型分別是:Cas30ProxyReceivingTicketValidationFilter 和 Cas20ProxyReceivingTicketValidationFilter。
? 在認證票據(jù)是會出現(xiàn)報錯的情況,這時候就需要考慮這個票據(jù)驗證器TicketValidationFiter的版本問題,CAS Server 的版本是否兼容Filter,從而引起沖突問題,具體使用哪一種票據(jù)驗證器,需要根據(jù)實際情況去調(diào)整,這個票據(jù)驗證器的選擇也是通過application.properties配置文件中去實現(xiàn)的動態(tài)選擇。
上面的各個連接地址是通過application.properties文件進行動態(tài)配置讀取的,示例如下:
#===Cas集成認證=== #需要走攔截器的地址 /api/loginByNameAndCardNo :驗票攔截路徑 cas.urlPattern = /cas/loginByNameAndCardNo # 客戶端如果要登錄,會跳轉(zhuǎn)到CAS服務端的登錄地址(認證地址) : 認證中心登錄頁面地址 # http://192.168.0.145:8080/cas/login cas.server-url-prefix = http://192.168.0.145:8080/cas/login # CAS 服務端地址(認證平臺地址):認證中心地址 # http://192.168.0.145:8080/cas cas.authentication-url = http://192.168.0.145:8080/cas # 客戶端在CAS服務端登錄成功后,自動從CAS服務端跳轉(zhuǎn)回客戶端的地址 :應用地址,也就是自己的系統(tǒng)地址。 https://cwfw.mtxy.edu.cn cas.client-host-url = http://192.168.0.145:8998 # Ticket校驗器使用 Cas30ProxyReceivingTicketValidationFilter :動態(tài)開啟 cas 單點登錄 cas.validation-type = cas # 驗票器版本 cas.filterVersion = new
CAS認證用戶信息Vo類
package com.wxxssf.CasAuthLogin.Vo; import lombok.Setter; import java.util.Map; /** * @Description: Cas認證用戶信息 */ @Getter @Setter public class CasUserInfo { /** 用戶名 */ private String userName; /** 用戶 */ private String userAccount; /** 用戶信息 */ private Map<String, Object> attributes; }
認證通過后返回數(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認證工具類 */ @Slf4j public class CasUtil { /** * cas client 默認的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沒有獲取到用戶 "); return null; } CasUserInfo casUserInfo = new CasUserInfo(); String userName = assertion.getPrincipal().getName(); log.info(" cas對接登錄用戶= " + 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; //獲取用戶信息中公開的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ū)別~~!! }
單點登錄接口
/** * cas 單點登錄 * * @param request 請求頭(姓名+身份證號) * @param ticket cas 票據(jù) * @return */ @GetMapping(value = "/api/loginByNameAndCardNo") @ApiOperation("cas單點登錄") 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()); // 登錄用戶校驗 // xxxxx // 用戶數(shù)據(jù)為 true // 跳轉(zhuǎn)頁面 return "url"; } else { return "redirect:" + casUrl; } }
四、其他方式實現(xiàn)的認證案例記錄(略):
? 注:該部分內(nèi)容僅僅為自己記錄使用,可跳過!
4.1 基于深信服IDTrust 實現(xiàn)Cas認證
方式一:基于JDK的java.net包中已經(jīng)提供了訪問Http協(xié)議的 HttpURLConnection 類實現(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); //認證地址 HttpsURLConnection httpsURLConnection = (HttpsURLConnection) url.openConnection(); httpsURLConnection.setDoInput(true); httpsURLConnection.setRequestMethod("GET"); httpsURLConnection.setRequestProperty("Content-Type","application/json;charset=utf-8"); System.out.println("準備執(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>加載中請稍等....</title> </head> <script> console.log("加載中....") </script> <body> <h3><%=encode%></h3> <h3><%=stringBuffer%></h3> </body> <script> </script> </html>
方式二:基于cn.hutool 的http 包中已經(jīng)提供了訪問Http協(xié)議的 Hutool-http 類實現(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("進入集成跳轉(zhuǎn)頁面?。?); 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認證地址 .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>加載中請稍等....</title> </head> <script> console.log("加載中....") </script> <body> <h3><%=status%></h3> <h3><%=body%></h3> </body> <script> console.log("請求響應數(shù)據(jù)展示:<%=status%>") </script> </html>
4.2 基于職教云平臺的統(tǒng)一認證案例
統(tǒng)一認證訪問主路徑 jsp文件 (該平臺的認證是需要提前申請入駐,然后綁定對應回調(diào)地址和認證地址之后才能進行認證)
<%@ 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.*"%> <%--應用訪問主路徑jsp--%> <% //認證地址: 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)一認證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證登錄
首先是認證訪問頁面代碼
<%@ 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); //與釘釘進行交互 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單點登錄授權(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(); } // 查詢得到當前用戶的userId // 獲得到userId之后應用應該處理應用自身的登錄會話管理(session),避免后續(xù)的業(yè)務交互(前端到應用服務端)每次都要重新獲取用戶身份,提升用戶體驗 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"); //工號 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("用戶信息錯誤,請重新登錄!");//可能是key值不正確 alert("yonghu="+"<%=yonghu%>"); closeWin(); return false; }else{ alert("yonghu="+"<%=yonghu%>") alert("驗證成功自動登錄?。?) 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"> 客戶端程序, 請稍候...</div> </div> </td></tr></table> </body> </html><script language="javascript">doCallU8();</script>
到此這篇關(guān)于SpringBoot 實現(xiàn)CAS Server統(tǒng)一登錄認證的文章就介紹到這了,更多相關(guān)SpringBoot 統(tǒng)一登錄認證內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用SpringBoot發(fā)送郵箱驗證碼的簡單實現(xiàn)
這篇文章主要介紹了使用SpringBoot發(fā)送郵箱驗證碼的簡單實現(xiàn),咱們今天來講使用QQ郵箱來發(fā)送和接收驗證碼,首先來介紹一下它在SpringBoot項目中的具體應用,需要的朋友可以參考下2023-04-04Apache Commons Math3探索之多項式曲線擬合實現(xiàn)代碼
這篇文章主要介紹了Apache Commons Math3探索之多項式曲線擬合實現(xiàn)代碼,小編覺得挺不錯的,這里分享給大家,供需要的朋友參考。2017-10-10springboot集成redis實現(xiàn)簡單秒殺系統(tǒng)
這篇文章主要為大家詳細介紹了springboot集成redis實現(xiàn)簡單秒殺系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-12-12SpringBoot?Security權(quán)限控制自定義failureHandler實例
這篇文章主要為大家介紹了SpringBoot?Security權(quán)限控制自定義failureHandler實例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-11-11解讀Spring定義Bean的兩種方式:<bean>和@Bean
這篇文章主要介紹了Spring定義Bean的兩種方式:<bean>和@Bean,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04