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:off
return "<h1>登录成功</h1><br><br>"
+ "<a href=\"/logout\">退出登录</a><br><br>"
+ "<a href=\"" + casSingleLogoutUrl + "\">全局退出登录</a>";
//@formatter:on
}
/**
* 登出提示页,免登录
*/
@GetMapping("/logoutPage")
public String logoutPage(HttpServletResponse response) {
//@formatter:off
return "<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会话记录
*/
@Bean
public 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:off
return "<h1>登录成功</h1><br><br>"
+ "<a href=\"/logout\">退出登录</a><br><br>"
+ "<a href=\"" + casSingleLogoutUrl + "\">全局退出登录</a>";
//@formatter:on
}
/**
* 登出提示页,免登录
*/
@GetMapping("/logoutPage")
public String logoutPage(HttpServletResponse response) {
//@formatter:off
return "<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单点登出的更多相关文章
- struts2视频学习笔记 22-23(基于XML配置方式实现对action的所有方法及部分方法进行校验)
课时22 基于XML配置方式实现对action的所有方法进行校验 使用基于XML配置方式实现输入校验时,Action也需要继承ActionSupport,并且提供校验文件,校验文件和action类 ...
- CAS学习笔记三:SpringBoot自动配置与手动配置过滤器方式集成CAS客户端
本文目标 基于SpringBoot + Maven 分别使用自动配置与手动配置过滤器方式集成CAS客户端. 需要提前搭建 CAS 服务端,参考 https://www.cnblogs.com/hell ...
- 源码学习系列之SpringBoot自动配置(篇一)
源码学习系列之SpringBoot自动配置源码学习(篇一) ok,本博客尝试跟一下Springboot的自动配置源码,做一下笔记记录,自动配置是Springboot的一个很关键的特性,也容易被忽略的属 ...
- go微服务框架kratos学习笔记五(kratos 配置中心 paladin config sdk [断剑重铸之日,骑士归来之时])
目录 go微服务框架kratos学习笔记五(kratos 配置中心 paladin config sdk [断剑重铸之日,骑士归来之时]) 静态配置 flag注入 在线热加载配置 远程配置中心 go微 ...
- 源码学习系列之SpringBoot自动配置(篇二)
源码学习系列之SpringBoot自动配置(篇二)之HttpEncodingAutoConfiguration 源码分析 继上一篇博客源码学习系列之SpringBoot自动配置(篇一)之后,本博客继续 ...
- 【opencv学习笔记五】一个简单程序:图像读取与显示
今天我们来学习一个最简单的程序,即从文件读取图像并且创建窗口显示该图像. 目录 [imread]图像读取 [namedWindow]创建window窗口 [imshow]图像显示 [imwrite]图 ...
- python3.4学习笔记(五) IDLE显示行号问题,插件安装和其他开发工具介绍
python3.4学习笔记(五) IDLE显示行号问题,插件安装和其他开发工具介绍 IDLE默认不能显示行号,使用ALT+G 跳到对应行号,在右下角有显示光标所在行.列.pycharm免费社区版.Su ...
- Linux学习笔记(五) 账号管理
1.用户与组账号 用户账号:包括实际人员和逻辑性对象(例如应用程序执行特定工作的账号) 每一个用户账号包含一个唯一的用户 ID 和组 ID 标准用户是系统安装过程中自动创建的用户账号,其中除 root ...
- C#可扩展编程之MEF学习笔记(五):MEF高级进阶
好久没有写博客了,今天抽空继续写MEF系列的文章.有园友提出这种系列的文章要做个目录,看起来方便,所以就抽空做了一个,放到每篇文章的最后. 前面四篇讲了MEF的基础知识,学完了前四篇,MEF中比较常用 ...
随机推荐
- Java中List排序的3种方法
在某些特殊的场景下,我们需要在 Java 程序中对 List 集合进行排序操作.比如从第三方接口中获取所有用户的列表,但列表默认是以用户编号从小到大进行排序的,而我们的系统需要按照用户的年龄从大到小进 ...
- CF1494A ABC String 题解
Content 给定 \(T\) 个仅包含大写字母 A,B,C 的字符串 \(s\).问你是否能够通过将每个 A,B,C 换成 (,) 中的一个(同一个字母必须要换成同一个字符),使得最后得到的括号序 ...
- git 添加.gitignore文件不生效
git rm -r --cached . #新增的忽略文件没有生效,是因为git是有缓存的,而之前的文件在缓存中,并不会清除掉,还会继续提交,所以更新.gitignore文件,要清除缓存文件 git ...
- 【LeetCode】541. Reverse String II 解题报告(Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 Java解法 Python解法 日期 题目地址:ht ...
- idea使用教程-常用设置
[1]进入设置: [2]设置主题: [3]编辑区的字体变大或者变小: [4]鼠标悬浮在代码上有提示: [5]自动导包和优化多余的包: 手动导包:快捷键:alt+enter 自动导包和优化多余的包: [ ...
- Sentry 开发者贡献指南 - SDK 开发(事件负载)
内容整理自官方开发文档 系列 Docker Compose 部署与故障排除详解 1 分钟快速使用 Docker 上手最新版 Sentry-CLI - 创建版本 快速使用 Docker 上手 Sentr ...
- Spring中的@Bean注解
@Bean 基础概念 @Bean:Spring的@Bean注解用于告诉方法,产生一个Bean对象,然后这个Bean对象交给Spring管理.产生这个Bean对象的方法Spring只会调用一次,随后这个 ...
- MySQL 批量插入,如何不插入重复数据
1.insert ignore into 当插入数据时,如出现错误时,如重复数据,将不返回错误,只以警告形式返回.所以使用ignore请确保语句本身没有问题,否则也会被忽略掉=======>IN ...
- 'real'词频分析
写下来想法来自于无聊时写的代码.https://cryptopals.com/sets/1/challenges/3 The hex encoded string: 1b37373331363f781 ...
- Product Integration
目录 生存模型 连续情形 离散情形 统一 Richard D. Gill, Product integration 一般的积分是指黎曼积分, 其计算是把区域无限细分求和并取极限, 有另外一种积分是把区 ...