javascript
CAS学习笔记五:SpringBoot自动/手动配置方式集成CAS单点登出
本文目標
基于SpringBoot + Maven 分別使用自動配置與手動配置過濾器方式實現CAS客戶端登出及單點登出。
本文基于《CAS學習筆記三:SpringBoot自動/手動配置方式集成CAS單點登錄》的代碼擴充而來,完整代碼見 https://github.com/hellxz/cas-integration-demo
CAS服務端配置
單點登出跟隨 service 給出的跳轉地址重定向功能 在 CAS 服務端默認是關閉的,所以需要先開啟它。
vim webapps/cas/WEB-INF/classes/application.properties在最下方追加配置項 cas.logout.followServiceRedirects=true,保存重啟CAS服務端。
代碼目錄結構
以上紅字僅對本文修改的部分進行說明,其余請參考之前單點登錄的實現文章。
代碼實現
僅增量介紹關鍵類
SpringBoot自動配置登出實現
CasClientConfigurerImpl.java
package com.hellxz.cas;import java.util.Map;import org.jasig.cas.client.boot.configuration.CasClientConfigurer; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.stereotype.Component;/*** cas-client-support-springboot 依賴提供了CAS客戶端的自動配置,* 當自動配置不滿足需要時,可通過實現{@link CasClientConfigurer}接口來重寫需要自定義的邏輯*/ @Component public class CasClientConfigurerImpl implements CasClientConfigurer {/*** 配置認證過濾器,添加忽略參數,使/logoutPage登出提示頁免登錄*/@Override@SuppressWarnings({ "rawtypes", "unchecked" })public void configureAuthenticationFilter(final FilterRegistrationBean authenticationFilter) {Map initParameters = authenticationFilter.getInitParameters();initParameters.put("ignorePattern", "/logoutPage");}}上邊這個配置類的作用是自定義認證過濾器,將 /logoutPage 排除不走認證邏輯,此頁面用于顯示登出提示。
CasAutoConfigApp.java
package com.hellxz.cas;import java.io.IOException;import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession;import org.jasig.cas.client.boot.configuration.EnableCasClient; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;@SpringBootApplication @RestController @EnableCasClient public class CasAutoConfigApp {@Value("${custom.cas.single-logout-url:}")public String casSingleLogoutUrl;public static void main(String[] args) {SpringApplication.run(CasAutoConfigApp.class, args);}@GetMapping("/test")public String test(HttpServletRequest request) {return "服務A測試通過";}/*** 首頁,需要登錄*/@GetMapping("/index")public String index(HttpServletRequest request) {//@formatter:offreturn "<h1>登錄成功</h1><br><br>"+ "<a href=\"/logout\">退出登錄</a><br><br>"+ "<a href=\"" + casSingleLogoutUrl + "\">全局退出登錄</a>";//@formatter:on}/*** 登出提示頁,免登錄*/@GetMapping("/logoutPage")public String logoutPage(HttpServletResponse response) {//@formatter:offreturn "<h1>您已退出登錄成功。</h1><br><br>"+ "<a href=\"/index\">去登錄</a><br><br>"+ "<a href=\"" + casSingleLogoutUrl + "\">全局退出登錄</a>";//@formatter:on}/*** 退出登錄,跳轉登出提示頁*/@GetMapping("/logout")public void logout(HttpServletRequest request, HttpServletResponse response) throws IOException {HttpSession session = request.getSession(false);if (session != null) {// 過期會話session.invalidate();}// 跳轉登出提示頁response.sendRedirect("/logoutPage");}}較上一迭代新增了 /index(主頁)、/logoutPage(登出提示頁)、/logout (客戶端退出登錄)這三個接口。其中只有 /logout 接口是免登錄的,為了防止出現重定向回來自動登錄的情況。
application.properties
這里啟用了單點登出配置項,CasClientConfiguration 中的 casSingleSignOutFilter() 與 casSingleSignOutListener() 這兩個方法激活,注冊Bean到MVC容器中。
自定義單點登出地址相當于拼接 CAS服務端登出地址與回調重定向地址,這里配置成免登錄的客戶端地址 /logoutPage。
手動配置登出實現
CasConfig.java,是上一迭代的Config.java重命名而來。
package com.hellxz.cas;import java.util.EventListener; import java.util.HashMap; import java.util.Map;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.Value; 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.annotation.Order;@Configuration public class CasConfig {/*** 自定義cas服務地址*/@Value("${custom.cas.casServerUrlPrefix:}")private String casServerUrlPrefix;/*** 自定義服務標識,格式為{protocol}:{hostName}:{port}*/@Value("${custom.cas.serverName:}")private String serverName;/*** 監聽登出事件,清除session與token之間的映射關系及CAS會話記錄*/@Beanpublic ServletListenerRegistrationBean<EventListener> casSingleSignOutListener() {ServletListenerRegistrationBean<EventListener> singleSignOutListener = new ServletListenerRegistrationBean<>();singleSignOutListener.setListener(new SingleSignOutHttpSessionListener());return singleSignOutListener;}@Bean@Order(0)public FilterRegistrationBean<SingleSignOutFilter> casSingleSignOutFilter() {FilterRegistrationBean<SingleSignOutFilter> registration = new FilterRegistrationBean<>();registration.setFilter(new SingleSignOutFilter());registration.setName("CAS Single Sign Out Filter");Map<String, String> initParams = new HashMap<>();initParams.put("casServerUrlPrefix", casServerUrlPrefix); // CAS服務端地址,會拼接為登錄地址initParams.put("serverName", serverName); // 服務地址registration.setInitParameters(initParams);registration.addUrlPatterns("/*");return registration;}/*** 攔截所有請求,將未攜帶票據與會話中無票據的請求都重定向到CAS登錄地址*/@Bean@Order(1)public FilterRegistrationBean<AuthenticationFilter> casAuthenticationFilter() {FilterRegistrationBean<AuthenticationFilter> registration = new FilterRegistrationBean<>();registration.setFilter(new AuthenticationFilter());registration.setName("CAS Authentication Filter");Map<String, String> initParams = new HashMap<>();initParams.put("casServerUrlPrefix", casServerUrlPrefix); // CAS服務端地址,會拼接為登錄地址initParams.put("serverName", serverName); // 服務地址// 自定義忽略認證的路徑或表達式,這里用來免登錄訪問【退出登錄提示】頁面initParams.put("ignorePattern", "/logoutPage");registration.setInitParameters(initParams);registration.addUrlPatterns("/*");return registration;}/*** 攔截所有請求,使用獲取的票據向CAS服務端發起校驗票據請求*/@Bean@Order(2)public FilterRegistrationBean<Cas30ProxyReceivingTicketValidationFilter> cas30TicketValidationFilter() {FilterRegistrationBean<Cas30ProxyReceivingTicketValidationFilter> registration = new FilterRegistrationBean<>();registration.setFilter(new Cas30ProxyReceivingTicketValidationFilter());registration.setName("CAS30 Ticket Validation Filter");Map<String, String> initParams = new HashMap<>();initParams.put("casServerUrlPrefix", casServerUrlPrefix); // CAS服務端地址,會拼接為服務校驗地址initParams.put("serverName", serverName);registration.setInitParameters(initParams);registration.addUrlPatterns("/*");return registration;}/*** 包裝HttpServletRequest,使CAS登錄成功的用戶名等信息存入請求中<br>* <br>* 登錄成功后以下兩個方法將不再返回null: <br>* * <pre>* HttpServletRequest#getUserPrincipal()* HttpServletRequest#getRemoteUser()* </pre>*/@Bean@Order(3)public FilterRegistrationBean<HttpServletRequestWrapperFilter> httpServletRequestWrapperFilter() {FilterRegistrationBean<HttpServletRequestWrapperFilter> registration = new FilterRegistrationBean<>();registration.setFilter(new HttpServletRequestWrapperFilter());registration.setName("HttpServletRequest Wrapper Filter");registration.addUrlPatterns("/*");return registration;}}新增了 casSingleSignOutListener() (配置單點登出監聽器)、casSingleSignOutFilter()(單點登出過濾器)以及 將登出提示頁從認證過濾器處放行。
// 自定義忽略認證的路徑或表達式,這里用來免登錄訪問【退出登錄提示】頁面 initParams.put("ignorePattern", "/logoutPage");需注意單點登出過濾器的排序要早于認證過濾器、校驗票據過濾器。
CasManualConfigApp.java
package com.hellxz.cas;import java.io.IOException;import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession;import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;@RestController @SpringBootApplication public class CasManualConfigApp {/*** 自定義全局單點登出地址,由cas服務端地址/logout?service=當前serviceName/logoutPage組成<br>* 當cas全局登出(帶TGC訪問cas的/logout接口)成功后,會重定向service參數地址<br>* * <pre>* 需注意:service參數必須含登錄時注冊給CAS的serviceName,否則只廢棄CAS會話而不會重定向* </pre>*/@Value("${custom.cas.casSingleLogoutUrl:}")private String casSingleLogoutUrl;public static void main(String[] args) {SpringApplication.run(CasManualConfigApp.class, args);}@GetMapping("/test")public String test(HttpServletRequest request) {return "服務B測試通過";}/*** 首頁,需要登錄*/@GetMapping("/index")public String index(HttpServletRequest request) {//@formatter:offreturn "<h1>登錄成功</h1><br><br>"+ "<a href=\"/logout\">退出登錄</a><br><br>"+ "<a href=\"" + casSingleLogoutUrl + "\">全局退出登錄</a>";//@formatter:on}/*** 登出提示頁,免登錄*/@GetMapping("/logoutPage")public String logoutPage(HttpServletResponse response) {//@formatter:offreturn "<h1>您已退出登錄成功。</h1><br><br>"+ "<a href=\"/index\">去登錄</a><br><br>"+ "<a href=\"" + casSingleLogoutUrl + "\">全局退出登錄</a>";//@formatter:on}/*** 退出登錄,跳轉登出提示頁*/@GetMapping("/logout")public void logout(HttpServletRequest request, HttpServletResponse response) throws IOException {HttpSession session = request.getSession(false);if (session != null) {// 過期會話session.invalidate();}// 跳轉登出提示頁response.sendRedirect("/logoutPage");} }與 自動配置實現基本一致,添加幾個接口供測試
application.properties
驗證實現
以本地IP為 10.2.6.63,自動配置端口8081,手動配置端口 8082,CAS服務端192.168.56.104:8088/cas 為例
1、單點登錄
啟動自動配置服務,訪問本地端口號8081,我這里是 http://10.2.6.63:8081/index ,訪問首頁立即跳轉CAS登錄頁面
輸入用戶名與密碼,casuser/Mellon,登錄。如下圖單點登錄成功
2、客戶端登出
先驗證退出客戶端登出,點 退出登錄。如圖進入登出成功提示頁面
檢察下 /logout 接口的 cookie值,此處是 95E21E8D67C363A7432C342EDACB4DE8
再點擊 去登錄,這是訪問 /index,可以看到又登錄成功了,而且這次沒有手輸賬號密碼,查看了cookie中的會話id為 6157E88046280B3E90A502016F98549A,與退出之前不是同一會話
3、單點登出
接下來驗證CAS單點登出,點擊 全局退出登錄。如下圖,可見訪問CAS服務端 /logout接口,并傳遞了回調地址為登出提示頁面,最終回到了提示頁面。
我們再試驗一下 去登錄,驗證是否需要手輸賬號密碼登錄。
如上圖,的確需要手輸賬號才能登錄,說明單點登出功能正常。
4、CAS客戶端單點登出日志
由于自動配置項目我沒配置單點登出 trace 等級日志,我們用 手動配置服務登錄再全局退出下,看看日志。
啟動手動配置服務,訪問 http://10.2.6.63:8082/index,登錄后再全局退出。日志如下:
2022-01-18 23:22:38.237 TRACE 24016 --- [nio-8082-exec-1] o.j.c.c.session.SingleSignOutHandler : Ignoring URI for logout: /index 2022-01-18 23:22:40.401 TRACE 24016 --- [nio-8082-exec-4] o.j.c.c.session.SingleSignOutHandler : Received a token request 2022-01-18 23:22:40.406 DEBUG 24016 --- [nio-8082-exec-4] o.j.c.c.session.SingleSignOutHandler : Recording session for token ST-61-aJ6BwiVkekqtkqlIXmmyWAYq6sMlocalhost 2022-01-18 23:22:40.535 TRACE 24016 --- [nio-8082-exec-3] o.j.c.c.session.SingleSignOutHandler : Ignoring URI for logout: /index 2022-01-18 23:22:43.698 TRACE 24016 --- [nio-8082-exec-5] o.j.c.c.session.SingleSignOutHandler : Received a logout request 2022-01-18 23:22:43.699 TRACE 24016 --- [nio-8082-exec-5] o.j.c.c.session.SingleSignOutHandler : Logout request: <samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="LR-37-W7V-UYdTLhGkwl7P2w2somtR" Version="2.0" IssueInstant="2022-01-18T10:22:43Z"><saml:NameID xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">@NOT_USED@</saml:NameID><samlp:SessionIndex>ST-61-aJ6BwiVkekqtkqlIXmmyWAYq6sMlocalhost</samlp:SessionIndex></samlp:LogoutRequest> 2022-01-18 23:22:43.702 DEBUG 24016 --- [nio-8082-exec-5] o.j.c.c.session.SingleSignOutHandler : Invalidating session [DB43DC21DCC1663D64968E8DBD48B247] for token [ST-61-aJ6BwiVkekqtkqlIXmmyWAYq6sMlocalhost] 2022-01-18 23:22:43.712 TRACE 24016 --- [nio-8082-exec-2] o.j.c.c.session.SingleSignOutHandler : Ignoring URI for logout: /logoutPage可以看到:
- 登錄成功的日志 Recording session for token ST-61-aJ6BwiVkekqtkqlIXmmyWAYq6sMlocalhost
- 單點登出的日志: Received a logout request 和 Logout request: <samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="LR-37-W7V-UYdTLhGkwl7P2w2somtR" Version="2.0" IssueInstant="2022-01-18T10:22:43Z"><saml:NameID xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">@NOT_USED@</saml:NameID><samlp:SessionIndex>ST-61-aJ6BwiVkekqtkqlIXmmyWAYq6sMlocalhost</samlp:SessionIndex></samlp:LogoutRequest>
- 過期當前客戶端會話的 Invalidating session [DB43DC21DCC1663D64968E8DBD48B247] for token [ST-61-aJ6BwiVkekqtkqlIXmmyWAYq6sMlocalhost]
至此驗證客戶端登出及單點登出功能一切正常。
自動配置和手動配置這兩個工程的效果是一樣的,筆者已經親身測試OK,就不在此重復表述了。
總結
本次編寫的 demo 恰如其分地驗證了CAS客戶端登出與單點登出的流程,即客戶端登出(過期自己)及單點登出(過期自己以及所有相關客戶端)。
參考:
- https://github.com/cas-projects/cas-sample-java-webapp/blob/master/src/main/webapp/WEB-INF/web.xml
- https://github.com/apereo/java-cas-client
本文同步于本人博客園(hellxz.cnblogs.com) 與 CSDN(https://blog.csdn.net/u012586326),禁止轉載。
總結
以上是生活随笔為你收集整理的CAS学习笔记五:SpringBoot自动/手动配置方式集成CAS单点登出的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python爬虫面试题-扣丁学堂解析Py
- 下一篇: 使用纯JS还原小时候游戏厅里的水果机