Apereo CAS :自定义单点退出逻辑,增加自定义参数
Apereo CAS 与第三方单点退出成功后,默认返回的信息比较少或者可用的信息不多,使得第三方在编写接入的逻辑代码时,会比较不便。因此,我们需要改造 Apereo CAS 登录方法,追加 扩展信息
- 1、修改 Apereo CAS 的登录方法,追加 扩展信息
- 2、修改 Apereo CAS 的验票方法
/p3/validate
,实时获取Redis缓存中的动态信息
1、引入 Maven 依赖
Apereo CAS 当前主要是采用 Overlay
的方式进行扩展。要想找到单点退出
相关的对象,需要将相关的依赖项添加到项目的 Maven pom.xml
文件中:
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-core-logout-api</artifactId>
<version>${cas.version}</version>
</dependency>
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-core-logout</artifactId>
<version>${cas.version}</version>
</dependency>
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-core-api-logout</artifactId>
<version>${cas.version}</version>
</dependency>
2、分析源码
单点登出
根据官网文档 https://apereo.github.io/cas/7.0.x/installation/Logout-Single-Signout.html SLO 退出分为2种,Back Channel 和Front Channel,我这里使用的是Back Channel,其原理也很简单就是在每个service下配置一个回调地址,当触发退出时,如果这个服务验证过st,那么系统就会认为其已经保存了登录信息,那么在触发单点登出时,调用配置的退出地址。
配置service
我这里没有使用静态json配置service的方法,在resource/services 下添加你要接入cas的配置文件,文件名为
localhost-10000003.json,其中这里的localhost必须和下面配置文件的name对上,相应的10000003也必须和下面配置文件中的id对上,evaluationOrder为配置优先级,当一个接入的url同事满足多个配置文件时,值越小,优先级越高,还有一个配置单点登出的重要参数logoutUrl,这个就是触发登出时系统要去调用的子应用的回调地址,但实践当中我们的回调地址往往都需要带上一些凭证信息如用户id,所以用户id是动态生成的,CAS默认提供的逻辑只是静态的去调用配置的回调接口,我们要的是自定义登出逻辑,在配置的回调地址中加上参数,这里是加上用户id
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "http://127.0.0.1.*",
"name" : "localhost",
"id" : 10000003,
"description" : "localhost",
"evaluationOrder" : 1,
"theme":"theme_default",
"logoutUrl":"http://127.0.0.1:11202/logout"
}
定制单点登出回调逻辑
官网并没有对如何实现单点登出给出更多的文档,因为cas是通过webflow来实现的,通过跟踪具体的webflow配置信息就可以看出他的逻辑
DefaultLogoutWebflowConfigurer
这个类是单点退出的webflow配置入口,在这个类中,可以找到以下代码,其中terminateSessionAction就是处理单点退出所在的类
ActionState actionState = this.createActionState(flow, “terminateSession”, “terminateSessionAction”);
1
我们进入TerminateSessionAction 这个类 在terminate方法下有一行,根据方法名我们可以知道这里执行的是销毁TGT凭证的地方
List<LogoutRequest> logoutRequests = this.centralAuthenticationService.destroyTicketGrantingTicket(tgtId);
我们找到CentralAuthenticationService 这个接口,发现其默认只有一个实现类DefaultCentralAuthenticationService,在其的destroyTicketGrantingTicket方法中我们可以看到,如下代码,通过查看源码,发现this.logoutManager.performLogout(ticket)中执行的就是单点退出的逻辑。
public List<LogoutRequest> destroyTicketGrantingTicket(String ticketGrantingTicketId) {
try {
LOGGER.debug("Removing ticket [{}] from registry...", ticketGrantingTicketId);
TicketGrantingTicket ticket = (TicketGrantingTicket)this.getTicket(ticketGrantingTicketId, TicketGrantingTicket.class);
LOGGER.debug("Ticket found. Processing logout requests and then deleting the ticket...");
AuthenticationCredentialsThreadLocalBinder.bindCurrent(ticket.getAuthentication());
List<LogoutRequest> logoutRequests = this.logoutManager.performLogout(ticket);
this.deleteTicket(ticketGrantingTicketId);
this.doPublishEvent(new CasTicketGrantingTicketDestroyedEvent(this, ticket));
return logoutRequests;
} catch (InvalidTicketException var4) {
LOGGER.debug("TicketGrantingTicket [{}] cannot be found in the ticket registry.", ticketGrantingTicketId);
return new ArrayList(0);
}
}
我们进入其唯一实现类DefaultLogoutManager
,找到 performLogout
方法,下面我做注释,注释的地方就是执行单点登出的地方
public List<LogoutRequest> performLogout(TicketGrantingTicket ticket) {
LOGGER.info("Performing logout operations for [{}]", ticket.getId());
if (this.singleLogoutCallbacksDisabled) {
LOGGER.info("Single logout callbacks are disabled");
return new ArrayList(0);
} else {
//执行单点退出逻辑
List<LogoutRequest> logoutRequests = this.performLogoutForTicket(ticket);
this.logoutExecutionPlan.getLogoutHandlers().forEach((h) -> {
LOGGER.debug("Invoking logout handler [{}] to process ticket [{}]", h.getClass().getSimpleName(), ticket.getId());
h.handle(ticket);
});
LOGGER.info("[{}] logout requests were processed", logoutRequests.size());
return logoutRequests;
}
}
现在我们再进入performLogoutForTicket
方法,下面注释出已经标出了执行单点退出的地址
private List<LogoutRequest> performLogoutForTicket(TicketGrantingTicket ticketToBeLoggedOut) {
Stream<Map<String, Service>> streamServices = Stream.concat(Stream.of(ticketToBeLoggedOut.getServices()), Stream.of(ticketToBeLoggedOut.getProxyGrantingTickets()));
return (List)streamServices.map(Map::entrySet).flatMap(Collection::stream).filter((entry) -> {
return entry.getValue() instanceof WebApplicationService;
}).map((entry) -> {
WebApplicationService service = (WebApplicationService)entry.getValue();
LOGGER.debug("Handling single logout callback for [{}]", service);
//具体执行单点退出的地方
return this.singleLogoutServiceMessageHandler.handle(service, (String)entry.getKey());
}).flatMap(Collection::stream).filter(Objects::nonNull).collect(Collectors.toList());
}
增加用户参数
单点登出实践 重写 SingleLogoutServiceMessageHandler
public class LogoutHandler implements SingleLogoutServiceMessageHandler {
private HttpClient httpClient;
private ServicesManager servicesManager;
private TicketRegistry ticketRegistry;
public LogoutHandler(HttpClient httpClient, ServicesManager servicesManager, TicketRegistry ticketRegistry) {
this.httpClient = httpClient;
this.servicesManager = servicesManager;
this.ticketRegistry = ticketRegistry;
}
@Override
public Collection<LogoutRequest> handle(WebApplicationService singleLogoutService, String ticketId) {
//根据ticketId获取到ST
ServiceTicket serviceTicket =(ServiceTicket)ticketRegistry.getTicket(ticketId);
//根据ST获取到TGT
TicketGrantingTicket ticketGrantingTicket=serviceTicket.getTicketGrantingTicket();
//用TGT获取用户id
String userId=ticketGrantingTicket.getAuthentication().getPrincipal().getId();
Collection<LogoutRequest> logoutRequests=new ArrayList<>();
//获取当前有多少服务使用TGT去生成ST,因为单点的统一凭证就是TGT,一次登录使用的都是同一个TGT,所以可以使用TGT发现有多少个自服务登录过系统
for(Map.Entry<String, Service> entry: ticketGrantingTicket.getServices().entrySet()){
//查询对应系统配置的信息
RegisteredService registeredService = servicesManager.findServiceBy(entry.getValue());
//获取到前面配置的service的回调地址并且加上userId作为参数
String url=registeredService.getLogoutUrl()+"?userId="+userId;
try {
//发送消息
HttpMessage message =httpClient.sendMessageToEndPoint(new URL(url));
DefaultLogoutRequest defaultLogoutRequest=new DefaultLogoutRequest(ticketId,singleLogoutService,new URL(url));
logoutRequests.add(defaultLogoutRequest);
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
return logoutRequests;
}
}
在Srping的LogoutManager中注入的LogoutHandler
@Autowired
private LogoutMessageCreator logoutBuilder;
@Autowired
@Qualifier("noRedirectHttpClient")
private HttpClient httpClient;
@Autowired
@Qualifier("servicesManager")
private ServicesManager servicesManager;
@Autowired
@Qualifier("ticketRegistry")
private ObjectProvider<TicketRegistry> ticketRegistry;
@Autowired
@Bean
public LogoutManager logoutManager(@Qualifier("logoutExecutionPlan") final LogoutExecutionPlan logoutExecutionPlan) {
return new DefaultLogoutManager(logoutBuilder, new LogoutHandler(httpClient,servicesManager,ticketRegistry.getIfAvailable()),
false , logoutExecutionPlan);
}
参考资料:
最后编辑:Jeebiz 更新时间:2024-05-06 16:13