Apereo CAS Reactive Spring Client Logout Problem

I have mentioned that reactive spring client cannot be integrated using the cas-client library before. Because cas-client doesn’t support reactive, so i have explained the way to integrate using OpenID Connect. (https://dgempiuc.medium.com/spring-cloud-gateway-and-apereo-cas-integration-fbb6d5c8440c)

"@class" : "org.apereo.cas.support.oauth.services.OAuthRegisteredService",
"description": "Spring Cloud Gateway client",
"logoutUrl" : "http://gateway-url/oidc/logout",

How CAS logout works?

When we want to logout from applications, we send this request directly to the CAS, not to the application. In this way, CAS both destroys the TGT and sends a logout request to the applications logged in with this TGT. CAS supports 2 types of logouts: Single Logout and Single Sign-out.

In the Single Logout process, a logout request is sent to all applications that have logged in using the TGT that comes with the logout request, while in the Single Sign-out process, a logout request is sent only to the application from which the logout request came. The default logout type in CAS is Single Logout.

From CAS documentation

Remember that the callback submitted to each CAS-protected application is simply a notification; nothing more. It is the responsibility of the application to intercept that notification and properly destroy the user authentication session, either manually, via a specific endpoint or more commonly via a CAS client library that supports SLO.

Login Process in cas-client

During the final phase, the filter provided by the cas-client library comes into play and adds the ST — Session pair to the map in the HashMapBackedSessionMappingStorage class. (key=ST, value=session)

Logout Process in cas-client

When the application notifies CAS that it wants to logout, CAS sends the following request to the application (by DefaultSingleLogoutMessageCreator class). Check CAS logout specification https://apereo.github.io/cas/6.2.x/protocol/CAS-Protocol-Specification.html#head_appdx_c) for SAML logout request.


When the application receives a logout request, ST is removed from the request with the filter provided by the cas-client, the ST corresponding session is found from the map in the HashMapBackedSessionMappingStorage used during login, and that session is invalidated. The sample application log is as follows;

DEBUG --- HashMapBackedSessionMappingStorage : Attempting to remove Session=[28E77A7C5A42E54BB8E314B10A0115D0]DEBUG --- HashMapBackedSessionMappingStorage : Found mapping for session.  Session Removed.DEBUG --- SingleSignOutHandler     : Invalidating session [28E77A7C5A42E54BB8E314B10A0115D0] for token [ST-5-Q39Lj5mj8f6PddA1Ezhi4Xk6uD4-denizg-l]

Spring Cloud Gateway Logout Problem

But, here we run into the problem of logout. Since we can’t use the cas-client library and ST does not reach the application in OpenID Connect communication, we can’t do ST-session mapping. Therefore, we can’t know and invalidate the session corresponding to the ST in the request during logout.

So, what should we do?

Let’s put this into code and change the CAS side first.

public class DefaultSingleLogoutServiceMessageHandler extends BaseSingleLogoutServiceMessageHandler {


public boolean sendSingleLogoutMessage(SingleLogoutRequest request, SingleLogoutMessage lm) {
val logoutService = request.getService();

val registeredService = request.getRegisteredService();
if(checkServiceIsAuthenticatedByOIDC(registeredService)) {
val url = request.getLogoutUrl().toString();
val msg = new LogoutUser(getUsernameInRequest(request));
result = sendMessageToOIDCLogoutEndpoint(url, msg);
} else {
val msg = getLogoutHttpMessageToSend(request, lm);
result = super.sendMessageToEndpoint(msg, request, lm);


private boolean checkServiceIsAuthenticatedByOIDC(RegisteredService registeredService) {
return registeredService instanceof OAuthRegisteredService;

private boolean sendMessageToOIDCLogoutEndpoint(String logoutUrl, LogoutUser user) {
try {
restTemplate.postForEntity(logoutUrl, ..);
log.debug("OIDC logout request is sent to [{}]", user);
return true;
} catch (final Exception e) {
log.error("Unable to send logout message", e);
return false;


With this class, if it is a service of type OAuthRegisteredService, a custom request is sent to it. If it is not OAuth client, the default behavior is not broken and a SAML logout request is sent.

Let’s change Spring Cloud Gateway side.

public class LogoutController {
private final SessionService sessionService;

public Mono<Void> logout(@RequestBody LogoutUser logoutUser) {
String username = logoutUser.getUsername();
return invalidateSession(username);

private Mono<Void> invalidateSession(String username) {
Mono<Void> result = sessionService.destroy(username);
log.info("Session for {} is terminated", username);
return result;




Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store