You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2020/08/05 06:11:37 UTC

[syncope] branch master updated: [SYNCOPE-1582] Adding CAS client support (#209)

This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/master by this push:
     new aaaced8  [SYNCOPE-1582] Adding CAS client support (#209)
aaaced8 is described below

commit aaaced89f94f30c3280d4cc3d57a880a52a94571
Author: Francesco Chicchiriccò <il...@users.noreply.github.com>
AuthorDate: Wed Aug 5 08:11:26 2020 +0200

    [SYNCOPE-1582] Adding CAS client support (#209)
---
 .../syncope/core/logic/wa/WAClientAppLogic.java    |  26 +++-
 .../org/apache/syncope/fit/sra/AbstractITCase.java |  10 +-
 .../org/apache/syncope/fit/sra/CASSRAITCase.java   | 154 +++++++++++++++++++++
 .../org/apache/syncope/fit/sra/OIDCSRAITCase.java  |   4 +-
 .../org/apache/syncope/fit/sra/SAML2SRAITCase.java |   5 +-
 .../src/test/resources/application-cas.properties  |  21 +++
 pom.xml                                            |  13 ++
 sra/pom.xml                                        |   7 +-
 .../org/apache/syncope/sra/SecurityConfig.java     |  18 ++-
 .../cas/CASAuthenticationRequestWebFilter.java     | 105 ++++++++++++++
 .../sra/security/cas/CASAuthenticationToken.java   |  46 ++++++
 .../security/cas/CASAuthenticationWebFilter.java   | 105 ++++++++++++++
 .../CASSecurityConfigUtils.java}                   |  50 +++----
 .../CASServerLogoutHandler.java}                   |  32 +++--
 .../cas/CASServerLogoutSuccessHandler.java         |  33 +++++
 .../apache/syncope/sra/security/cas/CASUtils.java  | 111 +++++++++++++++
 .../security/oauth2/OAuth2SecurityConfigUtils.java |   3 +-
 .../OAuth2SessionRemovalServerLogoutHandler.java}  |  17 +--
 ...pContext.java => ServerWebExchangeContext.java} |  20 +--
 ...rHttpSessionStore.java => WebSessionStore.java} |  20 +--
 .../security/saml2/SAML2AnonymousWebFilter.java    |  20 +--
 .../sra/security/saml2/SAML2RequestGenerator.java  |  16 +--
 .../security/saml2/SAML2SecurityConfigUtils.java   |  11 +-
 .../security/saml2/SAML2ServerLogoutHandler.java   |  16 ++-
 .../SAML2WebSsoAuthenticationRequestWebFilter.java |   8 +-
 .../saml2/SAML2WebSsoAuthenticationWebFilter.java  |  11 +-
 .../apache/syncope/sra/session/SessionUtils.java   |  39 ++++++
 .../resources/debug/application-debug.properties   |  26 ++--
 28 files changed, 821 insertions(+), 126 deletions(-)

diff --git a/core/am/logic/src/main/java/org/apache/syncope/core/logic/wa/WAClientAppLogic.java b/core/am/logic/src/main/java/org/apache/syncope/core/logic/wa/WAClientAppLogic.java
index a1e6765..2d05503 100644
--- a/core/am/logic/src/main/java/org/apache/syncope/core/logic/wa/WAClientAppLogic.java
+++ b/core/am/logic/src/main/java/org/apache/syncope/core/logic/wa/WAClientAppLogic.java
@@ -29,8 +29,10 @@ import org.apache.syncope.common.lib.wa.WAClientApp;
 import org.apache.syncope.common.lib.types.ClientAppType;
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
+import org.apache.syncope.core.persistence.api.dao.auth.CASSPDAO;
 import org.apache.syncope.core.persistence.api.dao.auth.OIDCRPDAO;
 import org.apache.syncope.core.persistence.api.dao.auth.SAML2SPDAO;
+import org.apache.syncope.core.persistence.api.entity.auth.CASSP;
 import org.apache.syncope.core.persistence.api.entity.auth.OIDCRP;
 import org.apache.syncope.core.persistence.api.entity.auth.SAML2SP;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -51,6 +53,9 @@ public class WAClientAppLogic {
     @Autowired
     private OIDCRPDAO oidcrpDAO;
 
+    @Autowired
+    private CASSPDAO casspDAO;
+
     @PreAuthorize("hasRole('" + IdRepoEntitlement.ANONYMOUS + "')")
     @Transactional(readOnly = true)
     public List<WAClientApp> list() {
@@ -64,9 +69,14 @@ public class WAClientAppLogic {
                     break;
 
                 case SAML2SP:
-                default:
                     clientApps.addAll(saml2spDAO.findAll().stream().
                             map(binder::getWAClientApp).collect(Collectors.toList()));
+                    break;
+
+                case CASSP:
+                default:
+                    clientApps.addAll(casspDAO.findAll().stream().
+                            map(binder::getWAClientApp).collect(Collectors.toList()));
             }
         });
 
@@ -91,6 +101,13 @@ public class WAClientAppLogic {
                 }
                 break;
 
+            case CASSP:
+                CASSP cassp = casspDAO.findByClientAppId(clientAppId);
+                if (cassp != null) {
+                    clientApp = binder.getWAClientApp(cassp);
+                }
+                break;
+
             default:
         }
 
@@ -134,6 +151,13 @@ public class WAClientAppLogic {
                 }
                 break;
 
+            case CASSP:
+                CASSP cassp = casspDAO.findByName(name);
+                if (cassp != null) {
+                    clientApp = binder.getWAClientApp(cassp);
+                }
+                break;
+
             default:
         }
 
diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/AbstractITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/AbstractITCase.java
index db6e6a0..9ff4b97 100644
--- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/AbstractITCase.java
+++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/AbstractITCase.java
@@ -44,6 +44,7 @@ import java.util.concurrent.TimeoutException;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.cxf.jaxrs.client.WebClient;
 import org.apache.http.Consts;
 import org.apache.http.HttpStatus;
@@ -96,6 +97,9 @@ public abstract class AbstractITCase {
 
     protected static final String SRA_ADDRESS = "http://localhost:" + PORT;
 
+    protected static final String QUERY_STRING =
+            "key1=value1&key2=value2&key2=value3&key3=an%20url%20encoded%20value%3A%20this%21";
+
     protected static final String LOGGED_OUT_HEADER = "X-LOGGED-OUT";
 
     protected static SyncopeClientFactoryBean clientFactory;
@@ -332,12 +336,16 @@ public abstract class AbstractITCase {
         assertEquals("value2", key2.get(0).asText());
         assertEquals("value3", key2.get(1).asText());
 
+        assertEquals("an url encoded value: this!", args.get("key3").asText());
+
         ObjectNode headers = (ObjectNode) json.get("headers");
         assertEquals(MediaType.TEXT_HTML, headers.get(HttpHeaders.ACCEPT).asText());
         assertEquals(EN_LANGUAGE, headers.get(HttpHeaders.ACCEPT_LANGUAGE).asText());
         assertEquals("localhost:" + PORT, headers.get("X-Forwarded-Host").asText());
 
-        assertEquals(originalRequestURI, json.get("url").asText());
+        assertEquals(
+                StringUtils.substringBefore(originalRequestURI, "?"),
+                StringUtils.substringBefore(json.get("url").asText(), "?"));
 
         return headers;
     }
diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/CASSRAITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/CASSRAITCase.java
new file mode 100644
index 0000000..2b0e59a
--- /dev/null
+++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/CASSRAITCase.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.fit.sra;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.fail;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.apache.http.Consts;
+import org.apache.http.HttpStatus;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.impl.client.BasicCookieStore;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+import org.apache.syncope.common.lib.to.client.CASSPTO;
+import org.apache.syncope.common.lib.types.ClientAppType;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+public class CASSRAITCase extends AbstractITCase {
+
+    @BeforeAll
+    public static void startSRA() throws IOException, InterruptedException, TimeoutException {
+        assumeTrue(CASSRAITCase.class.equals(MethodHandles.lookup().lookupClass()));
+
+        doStartSRA("cas");
+    }
+
+    @BeforeAll
+    public static void clientAppSetup() {
+        String appName = CASSRAITCase.class.getName();
+        CASSPTO clientApp = clientAppService.list(ClientAppType.CASSP).stream().
+                filter(app -> appName.equals(app.getName())).
+                map(CASSPTO.class::cast).
+                findFirst().
+                orElseGet(() -> {
+                    CASSPTO app = new CASSPTO();
+                    app.setName(appName);
+                    app.setClientAppId(4L);
+                    app.setServiceId("http://localhost:8080/.*");
+
+                    Response response = clientAppService.create(ClientAppType.CASSP, app);
+                    if (response.getStatusInfo().getStatusCode() != Response.Status.CREATED.getStatusCode()) {
+                        fail("Could not create CAS Client App");
+                    }
+
+                    return clientAppService.read(
+                            ClientAppType.CASSP, response.getHeaderString(RESTHeaders.RESOURCE_KEY));
+                });
+
+        clientApp.setAuthPolicy(getAuthPolicy().getKey());
+
+        clientAppService.update(ClientAppType.CASSP, clientApp);
+        clientAppService.pushToWA();
+    }
+
+    @Test
+    public void web() throws IOException {
+        CloseableHttpClient httpclient = HttpClients.createDefault();
+        HttpClientContext context = HttpClientContext.create();
+        context.setCookieStore(new BasicCookieStore());
+
+        // 1. public
+        HttpGet get = new HttpGet(SRA_ADDRESS + "/public/get?" + QUERY_STRING);
+        get.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_HTML);
+        get.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
+        CloseableHttpResponse response = httpclient.execute(get, context);
+
+        ObjectNode headers = checkGetResponse(response, get.getURI().toASCIIString().replace("/public", ""));
+        assertFalse(headers.has(HttpHeaders.COOKIE));
+
+        // 2. protected
+        get = new HttpGet(SRA_ADDRESS + "/protected/get?" + QUERY_STRING);
+        String originalRequestURI = get.getURI().toASCIIString();
+        get.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_HTML);
+        get.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
+        response = httpclient.execute(get, context);
+        assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
+
+        // 2a. authenticate
+        String responseBody = EntityUtils.toString(response.getEntity());
+        response = authenticateToCas(responseBody, httpclient, context);
+
+        // 2b. WA attribute consent screen
+        if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
+            responseBody = EntityUtils.toString(response.getEntity());
+            String execution = extractCASExecution(responseBody);
+
+            List<NameValuePair> form = new ArrayList<>();
+            form.add(new BasicNameValuePair("_eventId", "confirm"));
+            form.add(new BasicNameValuePair("execution", execution));
+            form.add(new BasicNameValuePair("option", "1"));
+            form.add(new BasicNameValuePair("reminder", "30"));
+            form.add(new BasicNameValuePair("reminderTimeUnit", "days"));
+
+            HttpPost post = new HttpPost(WA_ADDRESS + "/login");
+            post.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_HTML);
+            post.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
+            post.setEntity(new UrlEncodedFormEntity(form, Consts.UTF_8));
+            response = httpclient.execute(post, context);
+        }
+        assertEquals(HttpStatus.SC_MOVED_TEMPORARILY, response.getStatusLine().getStatusCode());
+
+        // 2c. finally get requested content
+        get = new HttpGet(response.getFirstHeader(HttpHeaders.LOCATION).getValue());
+        get.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_HTML);
+        get.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
+        response = httpclient.execute(get, context);
+
+        headers = checkGetResponse(response, originalRequestURI.replace("/protected", ""));
+        assertFalse(headers.get(HttpHeaders.COOKIE).asText().isBlank());
+
+        // 3. logout
+        get = new HttpGet(SRA_ADDRESS + "/protected/logout");
+        get.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
+        response = httpclient.execute(get, context);
+
+        checkLogout(response);
+    }
+}
diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java
index 191cd32..d0b98f1 100644
--- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java
+++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OIDCSRAITCase.java
@@ -144,7 +144,7 @@ public class OIDCSRAITCase extends AbstractITCase {
         context.setCookieStore(new BasicCookieStore());
 
         // 1. public
-        HttpGet get = new HttpGet(SRA_ADDRESS + "/public/get?key1=value1&key2=value2&key2=value3");
+        HttpGet get = new HttpGet(SRA_ADDRESS + "/public/get?" + QUERY_STRING);
         get.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_HTML);
         get.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
         CloseableHttpResponse response = httpclient.execute(get, context);
@@ -153,7 +153,7 @@ public class OIDCSRAITCase extends AbstractITCase {
         assertFalse(headers.has(HttpHeaders.COOKIE));
 
         // 2. protected
-        get = new HttpGet(SRA_ADDRESS + "/protected/get?key1=value1&key2=value2&key2=value3");
+        get = new HttpGet(SRA_ADDRESS + "/protected/get?" + QUERY_STRING);
         String originalRequestURI = get.getURI().toASCIIString();
         get.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_HTML);
         get.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/SAML2SRAITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/SAML2SRAITCase.java
index 762f2b4..e4e11f2 100644
--- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/SAML2SRAITCase.java
+++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/SAML2SRAITCase.java
@@ -35,7 +35,6 @@ import java.util.concurrent.TimeoutException;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
-import org.apache.commons.lang3.tuple.Pair;
 import org.apache.commons.lang3.tuple.Triple;
 import org.apache.commons.text.StringEscapeUtils;
 import org.apache.http.Consts;
@@ -156,7 +155,7 @@ public class SAML2SRAITCase extends AbstractITCase {
         context.setCookieStore(new BasicCookieStore());
 
         // 1. public
-        HttpGet get = new HttpGet(SRA_ADDRESS + "/public/get?key1=value1&key2=value2&key2=value3");
+        HttpGet get = new HttpGet(SRA_ADDRESS + "/public/get?" + QUERY_STRING);
         get.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_HTML);
         get.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
         CloseableHttpResponse response = httpclient.execute(get, context);
@@ -165,7 +164,7 @@ public class SAML2SRAITCase extends AbstractITCase {
         assertFalse(headers.has(HttpHeaders.COOKIE));
 
         // 2. protected
-        get = new HttpGet(SRA_ADDRESS + "/protected/get?key1=value1&key2=value2&key2=value3");
+        get = new HttpGet(SRA_ADDRESS + "/protected/get?" + QUERY_STRING);
         String originalRequestURI = get.getURI().toASCIIString();
         get.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_HTML);
         get.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
diff --git a/fit/wa-reference/src/test/resources/application-cas.properties b/fit/wa-reference/src/test/resources/application-cas.properties
new file mode 100644
index 0000000..acc6237
--- /dev/null
+++ b/fit/wa-reference/src/test/resources/application-cas.properties
@@ -0,0 +1,21 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+am.type=CAS
+am.cas.server.name=http://localhost:80
+am.cas.url.prefix=http://localhost:9080/syncope-wa/
+
+global.postLogout=http://localhost:8080/logout
diff --git a/pom.xml b/pom.xml
index 6d819b4..27c2985 100644
--- a/pom.xml
+++ b/pom.xml
@@ -450,6 +450,7 @@ under the License.
     <pac4j.version>4.0.3</pac4j.version>
 
     <cas.version>6.3.0-SNAPSHOT</cas.version>
+    <cas-client.version>3.6.1</cas-client.version>
 
     <h2.version>1.4.200</h2.version>
 
@@ -1374,6 +1375,12 @@ under the License.
         <groupId>org.pac4j</groupId>
         <artifactId>pac4j-saml</artifactId>
         <version>${pac4j.version}</version>
+        <exclusions>
+          <exclusion>
+            <groupId>org.apache.velocity</groupId>
+            <artifactId>velocity</artifactId>            
+          </exclusion>
+        </exclusions>
       </dependency>
       <dependency>
         <groupId>org.pac4j</groupId>
@@ -1643,6 +1650,12 @@ under the License.
         <artifactId>cas-server-webapp-config</artifactId>
         <version>${cas.version}</version>
       </dependency>
+
+      <dependency>
+        <groupId>org.jasig.cas.client</groupId>
+        <artifactId>cas-client-core</artifactId>
+        <version>${cas-client.version}</version>
+      </dependency>
       <!-- /CAS -->
 
       <!-- Wicket -->
diff --git a/sra/pom.xml b/sra/pom.xml
index 51a114e..183b170 100644
--- a/sra/pom.xml
+++ b/sra/pom.xml
@@ -82,7 +82,12 @@ under the License.
       <artifactId>pac4j-saml</artifactId>
     </dependency>
 
-    <dependency> 
+    <dependency>
+      <groupId>org.jasig.cas.client</groupId>
+      <artifactId>cas-client-core</artifactId>
+    </dependency>
+
+    <dependency>
       <groupId>org.springframework.session</groupId>
       <artifactId>spring-session-core</artifactId> 
     </dependency>
diff --git a/sra/src/main/java/org/apache/syncope/sra/SecurityConfig.java b/sra/src/main/java/org/apache/syncope/sra/SecurityConfig.java
index 89646f3..9d9fda1 100644
--- a/sra/src/main/java/org/apache/syncope/sra/SecurityConfig.java
+++ b/sra/src/main/java/org/apache/syncope/sra/SecurityConfig.java
@@ -27,10 +27,12 @@ import org.apache.syncope.sra.security.CsrfRouteMatcher;
 import org.apache.syncope.sra.security.LogoutRouteMatcher;
 import org.apache.syncope.sra.security.oauth2.OAuth2SecurityConfigUtils;
 import org.apache.syncope.sra.security.PublicRouteMatcher;
+import org.apache.syncope.sra.security.cas.CASSecurityConfigUtils;
 import org.apache.syncope.sra.security.saml2.SAML2BindingType;
 import org.apache.syncope.sra.security.saml2.SAML2MetadataEndpoint;
 import org.apache.syncope.sra.security.saml2.SAML2SecurityConfigUtils;
 import org.apache.syncope.sra.security.saml2.SAML2WebSsoAuthenticationWebFilter;
+import org.jasig.cas.client.Protocol;
 import org.pac4j.core.http.callback.NoParameterCallbackUrlResolver;
 import org.pac4j.saml.client.SAML2Client;
 import org.pac4j.saml.config.SAML2Configuration;
@@ -266,10 +268,24 @@ public class SecurityConfig {
             case SAML2:
                 SAML2Client saml2Client = saml2Client();
                 SAML2SecurityConfigUtils.forLogin(http, saml2Client, publicRouteMatcher);
-                SAML2SecurityConfigUtils.forLogout(builder, saml2Client, logoutRouteMatcher, ctx);
+                SAML2SecurityConfigUtils.forLogout(builder, saml2Client, cacheManager, logoutRouteMatcher, ctx);
                 break;
 
             case CAS:
+                CASSecurityConfigUtils.forLogin(
+                        http,
+                        env.getProperty("am.cas.server.name"),
+                        Protocol.CAS3,
+                        env.getProperty("am.cas.url.prefix"),
+                        publicRouteMatcher);
+                CASSecurityConfigUtils.forLogout(
+                        builder,
+                        cacheManager,
+                        env.getProperty("am.cas.url.prefix"),
+                        logoutRouteMatcher,
+                        ctx);
+                break;
+
             default:
         }
 
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/cas/CASAuthenticationRequestWebFilter.java b/sra/src/main/java/org/apache/syncope/sra/security/cas/CASAuthenticationRequestWebFilter.java
new file mode 100644
index 0000000..7d741fd
--- /dev/null
+++ b/sra/src/main/java/org/apache/syncope/sra/security/cas/CASAuthenticationRequestWebFilter.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.sra.security.cas;
+
+import java.net.URI;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.sra.security.PublicRouteMatcher;
+import org.apache.syncope.sra.security.web.server.DoNothingIfCommittedServerRedirectStrategy;
+import org.apache.syncope.sra.session.SessionUtils;
+import org.jasig.cas.client.Protocol;
+import org.jasig.cas.client.util.CommonUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.web.server.ServerRedirectStrategy;
+import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
+import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.WebFilter;
+import org.springframework.web.server.WebFilterChain;
+import reactor.core.publisher.Mono;
+
+public class CASAuthenticationRequestWebFilter implements WebFilter {
+
+    private static final Logger LOG = LoggerFactory.getLogger(CASAuthenticationRequestWebFilter.class);
+
+    private final ServerWebExchangeMatcher matcher;
+
+    /**
+     * The name of the server. Should be in the following format: {protocol}:{hostName}:{port}.
+     * Standard ports can be excluded.
+     */
+    private final String serverName;
+
+    private final Protocol protocol;
+
+    /**
+     * The URL to the CAS Server login.
+     */
+    private final String casServerLoginUrl;
+
+    private ServerRedirectStrategy authenticationRedirectStrategy = new DoNothingIfCommittedServerRedirectStrategy();
+
+    public CASAuthenticationRequestWebFilter(
+            final PublicRouteMatcher publicRouteMatcher,
+            final String serverName,
+            final Protocol protocol,
+            final String casServerUrlPrefix) {
+
+        this.matcher = ServerWebExchangeMatchers.matchers(
+                publicRouteMatcher,
+                CASUtils.ticketAvailable(protocol),
+                SessionUtils.authInSession());
+        this.serverName = serverName;
+        this.protocol = protocol;
+        this.casServerLoginUrl = StringUtils.appendIfMissing(casServerUrlPrefix, "/") + "login";
+    }
+
+    public void setAuthenticationRedirectStrategy(final ServerRedirectStrategy authenticationRedirectStrategy) {
+        this.authenticationRedirectStrategy = authenticationRedirectStrategy;
+    }
+
+    @Override
+    public Mono<Void> filter(final ServerWebExchange exchange, final WebFilterChain chain) {
+        return matcher.matches(exchange).
+                filter(matchResult -> !matchResult.isMatch()).
+                switchIfEmpty(chain.filter(exchange).then(Mono.empty())).
+                flatMap(r -> exchange.getSession()).
+                flatMap(session -> {
+                    session.getAttributes().
+                            put(SessionUtils.INITIAL_REQUEST_URI, exchange.getRequest().getURI());
+
+                    LOG.debug("no ticket and no assertion found");
+
+                    String serviceUrl = CASUtils.constructServiceUrl(exchange, this.serverName, this.protocol);
+                    LOG.debug("Constructed service url: {}", serviceUrl);
+
+                    String urlToRedirectTo = CommonUtils.constructRedirectUrl(
+                            this.casServerLoginUrl,
+                            this.protocol.getServiceParameterName(),
+                            serviceUrl,
+                            false,
+                            false,
+                            null);
+                    LOG.debug("redirecting to \"{}\"", urlToRedirectTo);
+
+                    return authenticationRedirectStrategy.sendRedirect(exchange, URI.create(urlToRedirectTo));
+                });
+    }
+}
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/cas/CASAuthenticationToken.java b/sra/src/main/java/org/apache/syncope/sra/security/cas/CASAuthenticationToken.java
new file mode 100644
index 0000000..bf5d23c
--- /dev/null
+++ b/sra/src/main/java/org/apache/syncope/sra/security/cas/CASAuthenticationToken.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.sra.security.cas;
+
+import org.apache.commons.lang3.StringUtils;
+import org.jasig.cas.client.validation.Assertion;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+
+public class CASAuthenticationToken extends AbstractAuthenticationToken {
+
+    private static final long serialVersionUID = 6333776590644298469L;
+
+    private final Assertion assertion;
+
+    public CASAuthenticationToken(final Assertion assertion) {
+        super(null);
+        this.assertion = assertion;
+        this.setAuthenticated(true);
+    }
+
+    @Override
+    public Object getCredentials() {
+        return StringUtils.EMPTY;
+    }
+
+    @Override
+    public Assertion getPrincipal() {
+        return assertion;
+    }
+}
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/cas/CASAuthenticationWebFilter.java b/sra/src/main/java/org/apache/syncope/sra/security/cas/CASAuthenticationWebFilter.java
new file mode 100644
index 0000000..224c1b2
--- /dev/null
+++ b/sra/src/main/java/org/apache/syncope/sra/security/cas/CASAuthenticationWebFilter.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.sra.security.cas;
+
+import java.net.URI;
+import org.apache.syncope.sra.security.web.server.DoNothingIfCommittedServerRedirectStrategy;
+import org.apache.syncope.sra.session.SessionUtils;
+import org.jasig.cas.client.Protocol;
+import org.jasig.cas.client.validation.Assertion;
+import org.jasig.cas.client.validation.TicketValidationException;
+import org.jasig.cas.client.validation.TicketValidator;
+import org.jasig.cas.client.validation.json.Cas30JsonServiceTicketValidator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.ReactiveAuthenticationManager;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.server.ServerRedirectStrategy;
+import org.springframework.security.web.server.WebFilterExchange;
+import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
+import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
+import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
+import org.springframework.security.web.server.util.matcher.AndServerWebExchangeMatcher;
+import org.springframework.security.web.server.util.matcher.NegatedServerWebExchangeMatcher;
+import reactor.core.publisher.Mono;
+
+public class CASAuthenticationWebFilter extends AuthenticationWebFilter {
+
+    private static final Logger LOG = LoggerFactory.getLogger(CASAuthenticationWebFilter.class);
+
+    private final String serverName;
+
+    private final Protocol protocol;
+
+    private final TicketValidator ticketValidator;
+
+    public CASAuthenticationWebFilter(
+            final ReactiveAuthenticationManager authenticationManager,
+            final String serverName,
+            final Protocol protocol,
+            final String casServerUrlPrefix) {
+
+        super(authenticationManager);
+
+        this.serverName = serverName;
+        this.protocol = protocol;
+        this.ticketValidator = new Cas30JsonServiceTicketValidator(casServerUrlPrefix);
+
+        setRequiresAuthenticationMatcher(new AndServerWebExchangeMatcher(
+                CASUtils.ticketAvailable(protocol),
+                new NegatedServerWebExchangeMatcher(SessionUtils.authInSession())));
+
+        setServerAuthenticationConverter(validateAssertion());
+
+        setAuthenticationSuccessHandler(redirectToInitialRequestURI());
+    }
+
+    private ServerAuthenticationConverter validateAssertion() {
+        return exchange -> CASUtils.retrieveTicketFromRequest(exchange, this.protocol).
+                flatMap(ticket -> {
+                    try {
+                        Assertion assertion = this.ticketValidator.validate(
+                                ticket,
+                                CASUtils.constructServiceUrl(exchange, this.serverName, this.protocol));
+                        return Mono.just(new CASAuthenticationToken(assertion));
+                    } catch (TicketValidationException e) {
+                        LOG.error("Could not validate {}", ticket, e);
+                        throw new BadCredentialsException("Could not validate " + ticket);
+                    }
+                });
+    }
+
+    private ServerAuthenticationSuccessHandler redirectToInitialRequestURI() {
+        return new ServerAuthenticationSuccessHandler() {
+
+            private final ServerRedirectStrategy redirectStrategy = new DoNothingIfCommittedServerRedirectStrategy();
+
+            @Override
+            public Mono<Void> onAuthenticationSuccess(
+                    final WebFilterExchange webFilterExchange, final Authentication authentication) {
+
+                return webFilterExchange.getExchange().getSession().
+                        flatMap(session -> this.redirectStrategy.sendRedirect(
+                        webFilterExchange.getExchange(),
+                        session.<URI>getRequiredAttribute(SessionUtils.INITIAL_REQUEST_URI)));
+            }
+        };
+    }
+}
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2SecurityConfigUtils.java b/sra/src/main/java/org/apache/syncope/sra/security/cas/CASSecurityConfigUtils.java
similarity index 66%
copy from sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2SecurityConfigUtils.java
copy to sra/src/main/java/org/apache/syncope/sra/security/cas/CASSecurityConfigUtils.java
index 4b71109..1c159ba 100644
--- a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2SecurityConfigUtils.java
+++ b/sra/src/main/java/org/apache/syncope/sra/security/cas/CASSecurityConfigUtils.java
@@ -16,14 +16,15 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.sra.security.saml2;
+package org.apache.syncope.sra.security.cas;
 
 import org.apache.syncope.sra.ApplicationContextUtils;
 import org.apache.syncope.sra.security.LogoutRouteMatcher;
 import org.apache.syncope.sra.security.PublicRouteMatcher;
-import org.pac4j.saml.client.SAML2Client;
+import org.jasig.cas.client.Protocol;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.cache.CacheManager;
 import org.springframework.context.ConfigurableApplicationContext;
 import org.springframework.security.authentication.ReactiveAuthenticationManager;
 import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
@@ -32,65 +33,66 @@ import org.springframework.security.core.Authentication;
 import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
 import org.springframework.security.web.server.authentication.logout.LogoutWebFilter;
 import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
-import org.springframework.web.server.WebFilter;
 import reactor.core.publisher.Mono;
 
-public final class SAML2SecurityConfigUtils {
+public final class CASSecurityConfigUtils {
 
-    private static final Logger LOG = LoggerFactory.getLogger(SAML2SecurityConfigUtils.class);
+    private static final Logger LOG = LoggerFactory.getLogger(CASSecurityConfigUtils.class);
 
     private static ReactiveAuthenticationManager authenticationManager() {
-        return authentication -> Mono.just(authentication).
-                filter(Authentication::isAuthenticated);
+        return authentication -> Mono.just(authentication).filter(Authentication::isAuthenticated);
     }
 
     public static void forLogin(
             final ServerHttpSecurity http,
-            final SAML2Client saml2Client,
+            final String serverName,
+            final Protocol protocol,
+            final String casServerUrlPrefix,
             final PublicRouteMatcher publicRouteMatcher) {
 
         ReactiveAuthenticationManager authenticationManager = authenticationManager();
 
-        SAML2WebSsoAuthenticationRequestWebFilter authRequestFilter =
-                new SAML2WebSsoAuthenticationRequestWebFilter(saml2Client);
+        CASAuthenticationRequestWebFilter authRequestFilter = new CASAuthenticationRequestWebFilter(
+                publicRouteMatcher,
+                serverName,
+                protocol,
+                casServerUrlPrefix);
         http.addFilterAt(authRequestFilter, SecurityWebFiltersOrder.HTTP_BASIC);
 
-        AuthenticationWebFilter authenticationFilter =
-                new SAML2WebSsoAuthenticationWebFilter(authenticationManager, saml2Client);
+        AuthenticationWebFilter authenticationFilter = new CASAuthenticationWebFilter(
+                authenticationManager,
+                serverName, protocol,
+                casServerUrlPrefix);
         authenticationFilter.setAuthenticationFailureHandler((exchange, ex) -> Mono.error(ex));
         authenticationFilter.setSecurityContextRepository(new WebSessionServerSecurityContextRepository());
         http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION);
-
-        WebFilter anonymousRedirectFilter = new SAML2AnonymousWebFilter(publicRouteMatcher);
-        http.addFilterAt(anonymousRedirectFilter, SecurityWebFiltersOrder.AUTHENTICATION);
     }
 
     public static void forLogout(
             final ServerHttpSecurity.AuthorizeExchangeSpec builder,
-            final SAML2Client saml2Client,
+            final CacheManager cacheManager,
+            final String casServerUrlPrefix,
             final LogoutRouteMatcher logoutRouteMatcher,
             final ConfigurableApplicationContext ctx) {
 
         LogoutWebFilter logoutWebFilter = new LogoutWebFilter();
         logoutWebFilter.setRequiresLogoutMatcher(logoutRouteMatcher);
 
-        SAML2ServerLogoutHandler logoutHandler = new SAML2ServerLogoutHandler(saml2Client);
-        logoutWebFilter.setLogoutHandler(logoutHandler);
+        logoutWebFilter.setLogoutHandler(new CASServerLogoutHandler(cacheManager, casServerUrlPrefix));
 
         try {
-            SAML2ServerLogoutSuccessHandler handler = ApplicationContextUtils.getOrCreateBean(ctx,
-                    SAML2ServerLogoutSuccessHandler.class.getName(),
-                    SAML2ServerLogoutSuccessHandler.class);
+            CASServerLogoutSuccessHandler handler = ApplicationContextUtils.getOrCreateBean(ctx,
+                    CASServerLogoutSuccessHandler.class.getName(),
+                    CASServerLogoutSuccessHandler.class);
             logoutWebFilter.setLogoutSuccessHandler(handler);
         } catch (ClassNotFoundException e) {
-            LOG.error("While creating instance of {}",
-                    SAML2ServerLogoutSuccessHandler.class.getName(), e);
+            LOG.error("While creating instance of {}", CASServerLogoutSuccessHandler.class.getName(), e);
         }
 
         builder.and().addFilterAt(logoutWebFilter, SecurityWebFiltersOrder.LOGOUT);
     }
 
-    private SAML2SecurityConfigUtils() {
+    private CASSecurityConfigUtils() {
         // private constructor for static utility class
     }
 }
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/SessionRemovalServerLogoutHandler.java b/sra/src/main/java/org/apache/syncope/sra/security/cas/CASServerLogoutHandler.java
similarity index 53%
copy from sra/src/main/java/org/apache/syncope/sra/security/SessionRemovalServerLogoutHandler.java
copy to sra/src/main/java/org/apache/syncope/sra/security/cas/CASServerLogoutHandler.java
index 0c25586..3c62df0 100644
--- a/sra/src/main/java/org/apache/syncope/sra/security/SessionRemovalServerLogoutHandler.java
+++ b/sra/src/main/java/org/apache/syncope/sra/security/cas/CASServerLogoutHandler.java
@@ -16,33 +16,43 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.sra.security;
+package org.apache.syncope.sra.security.cas;
 
+import java.net.URI;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.sra.SessionConfig;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.apache.syncope.sra.security.web.server.DoNothingIfCommittedServerRedirectStrategy;
 import org.springframework.cache.CacheManager;
 import org.springframework.security.core.Authentication;
+import org.springframework.security.web.server.ServerRedirectStrategy;
 import org.springframework.security.web.server.WebFilterExchange;
 import org.springframework.security.web.server.authentication.logout.ServerLogoutHandler;
 import reactor.core.publisher.Mono;
 
-public class SessionRemovalServerLogoutHandler implements ServerLogoutHandler {
+public class CASServerLogoutHandler implements ServerLogoutHandler {
 
-    private static final Logger EVENTS = LoggerFactory.getLogger("events");
+    private final ServerRedirectStrategy redirectStrategy = new DoNothingIfCommittedServerRedirectStrategy();
 
     private final CacheManager cacheManager;
 
-    public SessionRemovalServerLogoutHandler(final CacheManager cacheManager) {
+    /**
+     * The URL to the CAS Server logout.
+     */
+    private final String casServerLogoutUrl;
+
+    public CASServerLogoutHandler(final CacheManager cacheManager, final String casServerUrlPrefix) {
         this.cacheManager = cacheManager;
+        this.casServerLogoutUrl = StringUtils.appendIfMissing(casServerUrlPrefix, "/") + "logout";
     }
 
     @Override
     public Mono<Void> logout(final WebFilterExchange exchange, final Authentication authentication) {
-        return exchange.getExchange().getSession().doOnNext(session -> {
-            session.invalidate();
-            EVENTS.debug("Invalidate session {}", (authentication == null) ? null : authentication.getPrincipal());
-            cacheManager.getCache(SessionConfig.DEFAULT_CACHE).evictIfPresent(session.getId());
-        }).flatMap(session -> Mono.empty());
+        return exchange.getExchange().getSession().
+                flatMap(session -> {
+                    cacheManager.getCache(SessionConfig.DEFAULT_CACHE).evictIfPresent(session.getId());
+
+                    return session.invalidate().then(
+                            redirectStrategy.sendRedirect(exchange.getExchange(), URI.create(this.casServerLogoutUrl)));
+                }).onErrorResume(Mono::error);
     }
 }
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/cas/CASServerLogoutSuccessHandler.java b/sra/src/main/java/org/apache/syncope/sra/security/cas/CASServerLogoutSuccessHandler.java
new file mode 100644
index 0000000..9d5c4b6
--- /dev/null
+++ b/sra/src/main/java/org/apache/syncope/sra/security/cas/CASServerLogoutSuccessHandler.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.sra.security.cas;
+
+import org.apache.syncope.sra.security.AbstractServerLogoutSuccessHandler;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.server.WebFilterExchange;
+import reactor.core.publisher.Mono;
+
+public class CASServerLogoutSuccessHandler extends AbstractServerLogoutSuccessHandler {
+
+    @Override
+    public Mono<Void> onLogoutSuccess(final WebFilterExchange exchange, final Authentication authentication) {
+        return Mono.just(authentication).
+                flatMap(auth -> redirectStrategy.sendRedirect(exchange.getExchange(), getPostLogout(exchange)));
+    }
+}
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/cas/CASUtils.java b/sra/src/main/java/org/apache/syncope/sra/security/cas/CASUtils.java
new file mode 100644
index 0000000..1f1931f
--- /dev/null
+++ b/sra/src/main/java/org/apache/syncope/sra/security/cas/CASUtils.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.sra.security.cas;
+
+import java.util.List;
+import org.apache.commons.lang3.StringUtils;
+import org.jasig.cas.client.Protocol;
+import org.jasig.cas.client.util.URIBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.util.UriComponents;
+import org.springframework.web.util.UriComponentsBuilder;
+import reactor.core.publisher.Mono;
+
+public final class CASUtils {
+
+    private static final Logger LOG = LoggerFactory.getLogger(CASUtils.class);
+
+    public static Mono<String> safeGetParameter(final ServerWebExchange exchange, final String parameter) {
+        if (exchange.getRequest().getMethod() == HttpMethod.POST) {
+            LOG.debug(
+                    "safeGetParameter called on a POST ServerHttpRequest for Restricted Parameters. "
+                    + "Cannot complete check safely. "
+                    + "Reverting to standard behavior for this Parameter");
+            return exchange.getFormData().
+                    flatMap(form -> Mono.justOrEmpty(form.getFirst(parameter)));
+        }
+        return Mono.justOrEmpty(exchange.getRequest().getQueryParams().getFirst(parameter));
+    }
+
+    public static Mono<String> retrieveTicketFromRequest(final ServerWebExchange exchange, final Protocol protocol) {
+        return safeGetParameter(exchange, protocol.getArtifactParameterName());
+    }
+
+    public static ServerWebExchangeMatcher ticketAvailable(final Protocol protocol) {
+        return exchange -> CASUtils.retrieveTicketFromRequest(exchange, protocol).
+                flatMap(ticket -> ServerWebExchangeMatcher.MatchResult.match()).
+                switchIfEmpty(ServerWebExchangeMatcher.MatchResult.notMatch());
+    }
+
+    public static String constructServiceUrl(
+            final ServerWebExchange exchange,
+            final String serverName,
+            final Protocol protocol) {
+
+        UriComponents requestURI = UriComponentsBuilder.fromHttpRequest(exchange.getRequest()).build();
+
+        URIBuilder originalRequestUrl = new URIBuilder(
+                StringUtils.substringBefore(requestURI.toUriString(), "?"), true);
+        originalRequestUrl.setParameters(requestURI.getQuery());
+
+        URIBuilder builder;
+        if (!serverName.startsWith("https://") && !serverName.startsWith("http://")) {
+            String scheme = exchange.getRequest().getSslInfo() == null ? "http://" : "https://";
+            builder = new URIBuilder(scheme + serverName, true);
+        } else {
+            builder = new URIBuilder(serverName, true);
+        }
+
+        builder.setPort(requestURI.getPort());
+
+        builder.setEncodedPath(builder.getEncodedPath() + requestURI.getPath());
+
+        List<String> serviceParameterNames = List.of(protocol.getServiceParameterName().split(","));
+        if (!serviceParameterNames.isEmpty() && !originalRequestUrl.getQueryParams().isEmpty()) {
+            originalRequestUrl.getQueryParams().stream().
+                    filter(pair -> !pair.getName().equals(protocol.getArtifactParameterName())
+                    && !serviceParameterNames.contains(pair.getName())).
+                    forEach(pair -> {
+                        String name = pair.getName();
+                        if (name.contains("&") || name.contains("=")) {
+                            URIBuilder encodedParamBuilder = new URIBuilder();
+                            encodedParamBuilder.setParameters(name);
+                            encodedParamBuilder.getQueryParams().stream().
+                                    filter(pair2 -> !pair2.getName().equals(protocol.getArtifactParameterName())
+                                    && !serviceParameterNames.contains(pair2.getName())).
+                                    forEach(pair2 -> builder.addParameter(pair2.getName(), pair2.getValue()));
+                        } else {
+                            builder.addParameter(name, pair.getValue());
+                        }
+                    });
+        }
+
+        String result = builder.toString();
+        LOG.debug("serviceUrl generated: {}", result);
+        return result;
+    }
+
+    private CASUtils() {
+        // private constructor for static utility class
+    }
+}
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/oauth2/OAuth2SecurityConfigUtils.java b/sra/src/main/java/org/apache/syncope/sra/security/oauth2/OAuth2SecurityConfigUtils.java
index cf6bbb4..6a92551 100644
--- a/sra/src/main/java/org/apache/syncope/sra/security/oauth2/OAuth2SecurityConfigUtils.java
+++ b/sra/src/main/java/org/apache/syncope/sra/security/oauth2/OAuth2SecurityConfigUtils.java
@@ -22,7 +22,6 @@ import java.util.Set;
 import org.apache.syncope.sra.ApplicationContextUtils;
 import org.apache.syncope.sra.SecurityConfig.AMType;
 import org.apache.syncope.sra.security.LogoutRouteMatcher;
-import org.apache.syncope.sra.security.SessionRemovalServerLogoutHandler;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.cache.CacheManager;
@@ -122,7 +121,7 @@ public final class OAuth2SecurityConfigUtils {
 
         LogoutWebFilter logoutWebFilter = new LogoutWebFilter();
         logoutWebFilter.setRequiresLogoutMatcher(logoutRouteMatcher);
-        logoutWebFilter.setLogoutHandler(new SessionRemovalServerLogoutHandler(cacheManager));
+        logoutWebFilter.setLogoutHandler(new OAuth2SessionRemovalServerLogoutHandler(cacheManager));
 
         if (AMType.OIDC == amType) {
             try {
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/SessionRemovalServerLogoutHandler.java b/sra/src/main/java/org/apache/syncope/sra/security/oauth2/OAuth2SessionRemovalServerLogoutHandler.java
similarity index 70%
rename from sra/src/main/java/org/apache/syncope/sra/security/SessionRemovalServerLogoutHandler.java
rename to sra/src/main/java/org/apache/syncope/sra/security/oauth2/OAuth2SessionRemovalServerLogoutHandler.java
index 0c25586..9cf3d0e 100644
--- a/sra/src/main/java/org/apache/syncope/sra/security/SessionRemovalServerLogoutHandler.java
+++ b/sra/src/main/java/org/apache/syncope/sra/security/oauth2/OAuth2SessionRemovalServerLogoutHandler.java
@@ -16,33 +16,28 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.sra.security;
+package org.apache.syncope.sra.security.oauth2;
 
 import org.apache.syncope.sra.SessionConfig;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.springframework.cache.CacheManager;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.web.server.WebFilterExchange;
 import org.springframework.security.web.server.authentication.logout.ServerLogoutHandler;
 import reactor.core.publisher.Mono;
 
-public class SessionRemovalServerLogoutHandler implements ServerLogoutHandler {
-
-    private static final Logger EVENTS = LoggerFactory.getLogger("events");
+public class OAuth2SessionRemovalServerLogoutHandler implements ServerLogoutHandler {
 
     private final CacheManager cacheManager;
 
-    public SessionRemovalServerLogoutHandler(final CacheManager cacheManager) {
+    public OAuth2SessionRemovalServerLogoutHandler(final CacheManager cacheManager) {
         this.cacheManager = cacheManager;
     }
 
     @Override
     public Mono<Void> logout(final WebFilterExchange exchange, final Authentication authentication) {
-        return exchange.getExchange().getSession().doOnNext(session -> {
-            session.invalidate();
-            EVENTS.debug("Invalidate session {}", (authentication == null) ? null : authentication.getPrincipal());
+        return exchange.getExchange().getSession().flatMap(session -> {
             cacheManager.getCache(SessionConfig.DEFAULT_CACHE).evictIfPresent(session.getId());
-        }).flatMap(session -> Mono.empty());
+            return session.invalidate();
+        });
     }
 }
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerHttpContext.java b/sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerWebExchangeContext.java
similarity index 91%
rename from sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerHttpContext.java
rename to sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerWebExchangeContext.java
index 1e0b3ab..bf22e69 100644
--- a/sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerHttpContext.java
+++ b/sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerWebExchangeContext.java
@@ -36,11 +36,11 @@ import org.springframework.web.server.ServerWebExchange;
 import org.springframework.web.server.WebSession;
 import org.springframework.web.util.UriComponentsBuilder;
 
-public class ServerHttpContext implements WebContext {
+public class ServerWebExchangeContext implements WebContext {
 
     private final ServerWebExchange exchange;
 
-    private ServerHttpSessionStore sessionStore;
+    private WebSessionStore sessionStore;
 
     private MultiValueMap<String, String> form;
 
@@ -52,8 +52,8 @@ public class ServerHttpContext implements WebContext {
      * @param exchange the current exchange
      * @param webSession the current web session
      */
-    public ServerHttpContext(final ServerWebExchange exchange, final WebSession webSession) {
-        this(exchange, new ServerHttpSessionStore(webSession));
+    public ServerWebExchangeContext(final ServerWebExchange exchange, final WebSession webSession) {
+        this(exchange, new WebSessionStore(webSession));
     }
 
     /**
@@ -62,9 +62,9 @@ public class ServerHttpContext implements WebContext {
      * @param exchange the current exchange
      * @param sessionStore the session store to use
      */
-    public ServerHttpContext(
+    public ServerWebExchangeContext(
             final ServerWebExchange exchange,
-            final ServerHttpSessionStore sessionStore) {
+            final WebSessionStore sessionStore) {
 
         CommonHelper.assertNotNull("exchange", exchange);
         CommonHelper.assertNotNull("sessionStore", sessionStore);
@@ -72,12 +72,12 @@ public class ServerHttpContext implements WebContext {
         this.sessionStore = sessionStore;
     }
 
-    public ServerHttpSessionStore getNativeSessionStore() {
+    public WebSessionStore getNativeSessionStore() {
         return this.sessionStore;
     }
 
     @Override
-    public SessionStore<ServerHttpContext> getSessionStore() {
+    public SessionStore<ServerWebExchangeContext> getSessionStore() {
         return this.sessionStore;
     }
 
@@ -103,7 +103,7 @@ public class ServerHttpContext implements WebContext {
         return Optional.empty();
     }
 
-    public ServerHttpContext setForm(final MultiValueMap<String, String> form) {
+    public ServerWebExchangeContext setForm(final MultiValueMap<String, String> form) {
         this.form = form;
         return this;
     }
@@ -214,7 +214,7 @@ public class ServerHttpContext implements WebContext {
         return exchange.getRequest().getPath().value();
     }
 
-    public ServerHttpContext setBody(final String body) {
+    public ServerWebExchangeContext setBody(final String body) {
         this.body = body;
         return this;
     }
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerHttpSessionStore.java b/sra/src/main/java/org/apache/syncope/sra/security/pac4j/WebSessionStore.java
similarity index 67%
rename from sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerHttpSessionStore.java
rename to sra/src/main/java/org/apache/syncope/sra/security/pac4j/WebSessionStore.java
index 622a92c..c74336c 100644
--- a/sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerHttpSessionStore.java
+++ b/sra/src/main/java/org/apache/syncope/sra/security/pac4j/WebSessionStore.java
@@ -22,47 +22,47 @@ import java.util.Optional;
 import org.pac4j.core.context.session.SessionStore;
 import org.springframework.web.server.WebSession;
 
-public class ServerHttpSessionStore implements SessionStore<ServerHttpContext> {
+public class WebSessionStore implements SessionStore<ServerWebExchangeContext> {
 
     private final WebSession webSession;
 
-    public ServerHttpSessionStore(final WebSession webSession) {
+    public WebSessionStore(final WebSession webSession) {
         this.webSession = webSession;
     }
 
     @Override
-    public String getOrCreateSessionId(final ServerHttpContext context) {
+    public String getOrCreateSessionId(final ServerWebExchangeContext context) {
         return this.webSession.getId();
     }
 
     @Override
-    public Optional<Object> get(final ServerHttpContext context, final String key) {
+    public Optional<Object> get(final ServerWebExchangeContext context, final String key) {
         return Optional.ofNullable(this.webSession.getAttribute(key));
     }
 
     @Override
-    public void set(final ServerHttpContext context, final String key, final Object value) {
+    public void set(final ServerWebExchangeContext context, final String key, final Object value) {
     }
 
     @Override
-    public boolean destroySession(final ServerHttpContext context) {
+    public boolean destroySession(final ServerWebExchangeContext context) {
         return false;
     }
 
     @Override
-    public Optional<WebSession> getTrackableSession(final ServerHttpContext context) {
+    public Optional<WebSession> getTrackableSession(final ServerWebExchangeContext context) {
         return Optional.ofNullable(this.webSession);
     }
 
     @Override
-    public Optional<SessionStore<ServerHttpContext>> buildFromTrackableSession(
-            final ServerHttpContext context, final Object trackableSession) {
+    public Optional<SessionStore<ServerWebExchangeContext>> buildFromTrackableSession(
+            final ServerWebExchangeContext context, final Object trackableSession) {
 
         return Optional.empty();
     }
 
     @Override
-    public boolean renewSession(final ServerHttpContext context) {
+    public boolean renewSession(final ServerWebExchangeContext context) {
         return false;
     }
 }
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2AnonymousWebFilter.java b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2AnonymousWebFilter.java
index 29d0fa5..6cf7be5 100644
--- a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2AnonymousWebFilter.java
+++ b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2AnonymousWebFilter.java
@@ -20,8 +20,10 @@ package org.apache.syncope.sra.security.saml2;
 
 import java.net.URI;
 import org.apache.syncope.sra.security.PublicRouteMatcher;
+import org.apache.syncope.sra.session.SessionUtils;
 import org.springframework.http.HttpStatus;
-import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
+import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
+import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
 import org.springframework.web.server.ServerWebExchange;
 import org.springframework.web.server.WebFilter;
 import org.springframework.web.server.WebFilterChain;
@@ -29,24 +31,22 @@ import reactor.core.publisher.Mono;
 
 public class SAML2AnonymousWebFilter implements WebFilter {
 
-    public static final String INITIAL_REQUEST_URI = "INITIAL_REQUEST_URI";
-
-    private final PublicRouteMatcher publicRouteMatcher;
+    private final ServerWebExchangeMatcher matcher;
 
     public SAML2AnonymousWebFilter(final PublicRouteMatcher publicRouteMatcher) {
-        this.publicRouteMatcher = publicRouteMatcher;
+        this.matcher = ServerWebExchangeMatchers.matchers(
+                publicRouteMatcher,
+                SessionUtils.authInSession());
     }
 
     @Override
     public Mono<Void> filter(final ServerWebExchange exchange, final WebFilterChain chain) {
-        return publicRouteMatcher.matches(exchange).
+        return matcher.matches(exchange).
                 filter(matchResult -> !matchResult.isMatch()).
-                flatMap(r -> exchange.getSession()).flatMap(r -> exchange.getSession()).
-                filter(s -> !s.getAttributes().containsKey(
-                WebSessionServerSecurityContextRepository.DEFAULT_SPRING_SECURITY_CONTEXT_ATTR_NAME)).
                 switchIfEmpty(chain.filter(exchange).then(Mono.empty())).
+                flatMap(r -> exchange.getSession()).
                 flatMap(session -> {
-                    session.getAttributes().put(INITIAL_REQUEST_URI, exchange.getRequest().getURI());
+                    session.getAttributes().put(SessionUtils.INITIAL_REQUEST_URI, exchange.getRequest().getURI());
 
                     exchange.getResponse().setStatusCode(HttpStatus.SEE_OTHER);
                     exchange.getResponse().getHeaders().
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2RequestGenerator.java b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2RequestGenerator.java
index fd2a924..b595d57 100644
--- a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2RequestGenerator.java
+++ b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2RequestGenerator.java
@@ -18,7 +18,7 @@
  */
 package org.apache.syncope.sra.security.saml2;
 
-import org.apache.syncope.sra.security.pac4j.ServerHttpContext;
+import org.apache.syncope.sra.security.pac4j.ServerWebExchangeContext;
 import java.net.URI;
 import org.pac4j.core.exception.http.RedirectionAction;
 import org.pac4j.core.exception.http.WithContentAction;
@@ -38,13 +38,13 @@ abstract class SAML2RequestGenerator {
 
     protected Mono<Void> handle(
             final RedirectionAction action,
-            final ServerHttpContext shc) {
+            final ServerWebExchangeContext swec) {
 
         if (action instanceof WithLocationAction) {
             WithLocationAction withLocationAction = (WithLocationAction) action;
-            shc.getNative().getResponse().setStatusCode(HttpStatus.FOUND);
-            shc.getNative().getResponse().getHeaders().setLocation(URI.create(withLocationAction.getLocation()));
-            return shc.getNative().getResponse().setComplete();
+            swec.getNative().getResponse().setStatusCode(HttpStatus.FOUND);
+            swec.getNative().getResponse().getHeaders().setLocation(URI.create(withLocationAction.getLocation()));
+            return swec.getNative().getResponse().setComplete();
         } else if (action instanceof WithContentAction) {
             WithContentAction withContentAction = (WithContentAction) action;
             String content = withContentAction.getContent();
@@ -54,9 +54,9 @@ abstract class SAML2RequestGenerator {
             }
 
             return Mono.defer(() -> {
-                shc.getNative().getResponse().getHeaders().setContentType(MediaType.TEXT_HTML);
-                return shc.getNative().getResponse().
-                        writeWith(Mono.just(shc.getNative().getResponse().bufferFactory().wrap(content.getBytes())));
+                swec.getNative().getResponse().getHeaders().setContentType(MediaType.TEXT_HTML);
+                return swec.getNative().getResponse().
+                        writeWith(Mono.just(swec.getNative().getResponse().bufferFactory().wrap(content.getBytes())));
             });
         } else {
             throw new IllegalArgumentException("Unsupported Action: " + action.getClass().getName());
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2SecurityConfigUtils.java b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2SecurityConfigUtils.java
index 4b71109..63001da 100644
--- a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2SecurityConfigUtils.java
+++ b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2SecurityConfigUtils.java
@@ -24,6 +24,7 @@ import org.apache.syncope.sra.security.PublicRouteMatcher;
 import org.pac4j.saml.client.SAML2Client;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.cache.CacheManager;
 import org.springframework.context.ConfigurableApplicationContext;
 import org.springframework.security.authentication.ReactiveAuthenticationManager;
 import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
@@ -40,8 +41,7 @@ public final class SAML2SecurityConfigUtils {
     private static final Logger LOG = LoggerFactory.getLogger(SAML2SecurityConfigUtils.class);
 
     private static ReactiveAuthenticationManager authenticationManager() {
-        return authentication -> Mono.just(authentication).
-                filter(Authentication::isAuthenticated);
+        return authentication -> Mono.just(authentication).filter(Authentication::isAuthenticated);
     }
 
     public static void forLogin(
@@ -68,14 +68,14 @@ public final class SAML2SecurityConfigUtils {
     public static void forLogout(
             final ServerHttpSecurity.AuthorizeExchangeSpec builder,
             final SAML2Client saml2Client,
+            final CacheManager cacheManager,
             final LogoutRouteMatcher logoutRouteMatcher,
             final ConfigurableApplicationContext ctx) {
 
         LogoutWebFilter logoutWebFilter = new LogoutWebFilter();
         logoutWebFilter.setRequiresLogoutMatcher(logoutRouteMatcher);
 
-        SAML2ServerLogoutHandler logoutHandler = new SAML2ServerLogoutHandler(saml2Client);
-        logoutWebFilter.setLogoutHandler(logoutHandler);
+        logoutWebFilter.setLogoutHandler(new SAML2ServerLogoutHandler(saml2Client, cacheManager));
 
         try {
             SAML2ServerLogoutSuccessHandler handler = ApplicationContextUtils.getOrCreateBean(ctx,
@@ -83,8 +83,7 @@ public final class SAML2SecurityConfigUtils {
                     SAML2ServerLogoutSuccessHandler.class);
             logoutWebFilter.setLogoutSuccessHandler(handler);
         } catch (ClassNotFoundException e) {
-            LOG.error("While creating instance of {}",
-                    SAML2ServerLogoutSuccessHandler.class.getName(), e);
+            LOG.error("While creating instance of {}", SAML2ServerLogoutSuccessHandler.class.getName(), e);
         }
 
         builder.and().addFilterAt(logoutWebFilter, SecurityWebFiltersOrder.LOGOUT);
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2ServerLogoutHandler.java b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2ServerLogoutHandler.java
index 0bcba31..0959065 100644
--- a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2ServerLogoutHandler.java
+++ b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2ServerLogoutHandler.java
@@ -18,11 +18,13 @@
  */
 package org.apache.syncope.sra.security.saml2;
 
-import org.apache.syncope.sra.security.pac4j.ServerHttpContext;
+import org.apache.syncope.sra.SessionConfig;
+import org.apache.syncope.sra.security.pac4j.ServerWebExchangeContext;
 import org.pac4j.saml.client.SAML2Client;
 import org.pac4j.saml.credentials.SAML2Credentials;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.cache.CacheManager;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.web.server.WebFilterExchange;
 import org.springframework.security.web.server.authentication.logout.ServerLogoutHandler;
@@ -32,8 +34,11 @@ public class SAML2ServerLogoutHandler extends SAML2RequestGenerator implements S
 
     private static final Logger LOG = LoggerFactory.getLogger(SAML2ServerLogoutHandler.class);
 
-    public SAML2ServerLogoutHandler(final SAML2Client saml2Client) {
+    private final CacheManager cacheManager;
+
+    public SAML2ServerLogoutHandler(final SAML2Client saml2Client, final CacheManager cacheManager) {
         super(saml2Client);
+        this.cacheManager = cacheManager;
     }
 
     @Override
@@ -45,11 +50,12 @@ public class SAML2ServerLogoutHandler extends SAML2RequestGenerator implements S
                     LOG.debug("Creating SAML2 SP Logout Request for IDP[{}] and Profile[{}]",
                             saml2Client.getIdentityProviderResolvedEntityId(), credentials.getUserProfile());
 
-                    ServerHttpContext shc = new ServerHttpContext(exchange.getExchange(), session);
+                    ServerWebExchangeContext swec = new ServerWebExchangeContext(exchange.getExchange(), session);
 
+                    cacheManager.getCache(SessionConfig.DEFAULT_CACHE).evictIfPresent(session.getId());
                     return session.invalidate().then(
-                            saml2Client.getLogoutAction(shc, credentials.getUserProfile(), null).
-                                    map(action -> handle(action, shc)).
+                            saml2Client.getLogoutAction(swec, credentials.getUserProfile(), null).
+                                    map(action -> handle(action, swec)).
                                     orElseThrow(() -> new IllegalStateException("No action generated")));
                 }).onErrorResume(Mono::error);
     }
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2WebSsoAuthenticationRequestWebFilter.java b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2WebSsoAuthenticationRequestWebFilter.java
index e57faad..ab1819c 100644
--- a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2WebSsoAuthenticationRequestWebFilter.java
+++ b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2WebSsoAuthenticationRequestWebFilter.java
@@ -18,7 +18,7 @@
  */
 package org.apache.syncope.sra.security.saml2;
 
-import org.apache.syncope.sra.security.pac4j.ServerHttpContext;
+import org.apache.syncope.sra.security.pac4j.ServerWebExchangeContext;
 import org.pac4j.saml.client.SAML2Client;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -57,10 +57,10 @@ public class SAML2WebSsoAuthenticationRequestWebFilter extends SAML2RequestGener
                     LOG.debug("Creating SAML2 SP Authentication Request for IDP[{}]",
                             saml2Client.getIdentityProviderResolvedEntityId());
 
-                    ServerHttpContext shc = new ServerHttpContext(exchange, session);
+                    ServerWebExchangeContext swec = new ServerWebExchangeContext(exchange, session);
 
-                    return saml2Client.getRedirectionAction(shc).
-                            map(action -> handle(action, shc)).
+                    return saml2Client.getRedirectionAction(swec).
+                            map(action -> handle(action, swec)).
                             orElseThrow(() -> new IllegalStateException("No action generated"));
                 }).onErrorResume(Mono::error);
     }
diff --git a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2WebSsoAuthenticationWebFilter.java b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2WebSsoAuthenticationWebFilter.java
index 79ef94b..0cbd9f7 100644
--- a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2WebSsoAuthenticationWebFilter.java
+++ b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2WebSsoAuthenticationWebFilter.java
@@ -18,9 +18,10 @@
  */
 package org.apache.syncope.sra.security.saml2;
 
-import org.apache.syncope.sra.security.pac4j.ServerHttpContext;
 import java.net.URI;
+import org.apache.syncope.sra.security.pac4j.ServerWebExchangeContext;
 import org.apache.syncope.sra.security.web.server.DoNothingIfCommittedServerRedirectStrategy;
+import org.apache.syncope.sra.session.SessionUtils;
 import org.pac4j.core.util.Pac4jConstants;
 import org.pac4j.saml.client.SAML2Client;
 import org.pac4j.saml.credentials.SAML2Credentials;
@@ -87,12 +88,12 @@ public class SAML2WebSsoAuthenticationWebFilter extends AuthenticationWebFilter
                 flatMap(form -> this.matcher.matches(exchange).
                 flatMap(matchResult -> exchange.getSession()).
                 flatMap(session -> {
-                    ServerHttpContext shc = new ServerHttpContext(exchange, session).setForm(form);
+                    ServerWebExchangeContext swec = new ServerWebExchangeContext(exchange, session).setForm(form);
 
-                    SAML2Credentials credentials = saml2Client.getCredentialsExtractor().extract(shc).
+                    SAML2Credentials credentials = saml2Client.getCredentialsExtractor().extract(swec).
                             orElseThrow(() -> new IllegalStateException("No AuthnResponse found"));
 
-                    saml2Client.getAuthenticator().validate(credentials, shc);
+                    saml2Client.getAuthenticator().validate(credentials, swec);
 
                     return Mono.just(new SAML2AuthenticationToken(credentials));
                 }));
@@ -110,7 +111,7 @@ public class SAML2WebSsoAuthenticationWebFilter extends AuthenticationWebFilter
                 return webFilterExchange.getExchange().getSession().
                         flatMap(session -> this.redirectStrategy.sendRedirect(
                         webFilterExchange.getExchange(),
-                        (URI) session.getRequiredAttribute(SAML2AnonymousWebFilter.INITIAL_REQUEST_URI)));
+                        session.<URI>getRequiredAttribute(SessionUtils.INITIAL_REQUEST_URI)));
             }
         };
     }
diff --git a/sra/src/main/java/org/apache/syncope/sra/session/SessionUtils.java b/sra/src/main/java/org/apache/syncope/sra/session/SessionUtils.java
new file mode 100644
index 0000000..0f0b490
--- /dev/null
+++ b/sra/src/main/java/org/apache/syncope/sra/session/SessionUtils.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.sra.session;
+
+import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
+import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
+
+public final class SessionUtils {
+
+    public static final String INITIAL_REQUEST_URI = "INITIAL_REQUEST_URI";
+
+    public static ServerWebExchangeMatcher authInSession() {
+        return exchange -> exchange.getSession().
+                filter(session -> session.getAttributes().containsKey(
+                WebSessionServerSecurityContextRepository.DEFAULT_SPRING_SECURITY_CONTEXT_ATTR_NAME)).
+                flatMap(session -> ServerWebExchangeMatcher.MatchResult.match()).
+                switchIfEmpty(ServerWebExchangeMatcher.MatchResult.notMatch());
+    }
+
+    private SessionUtils() {
+        // private constructor for static utility class
+    }
+}
diff --git a/sra/src/test/resources/debug/application-debug.properties b/sra/src/test/resources/debug/application-debug.properties
index 5af528f..b74d3a4 100644
--- a/sra/src/test/resources/debug/application-debug.properties
+++ b/sra/src/test/resources/debug/application-debug.properties
@@ -30,16 +30,20 @@
 #am.oauth2.client.id=oauth2TestClientId
 #am.oauth2.client.secret=oauth2TestClientSecret
 
-am.type=SAML2
-am.saml2.sp.authnrequest.binding=POST
-am.saml2.sp.logout.request.binding=POST
-am.saml2.sp.logout.response.binding=POST
-am.saml2.sp.entityId=http://localhost:8080
-am.saml2.sp.skew=300
-am.saml2.idp=http://localhost:9080/syncope-wa/idp/metadata
-am.saml2.keystore=classpath:/saml.keystore.jks
-am.saml2.keystore.type=jks
-am.saml2.keystore.storepass=changeit
-am.saml2.keystore.keypass=changeit
+#am.type=SAML2
+#am.saml2.sp.authnrequest.binding=POST
+#am.saml2.sp.logout.request.binding=POST
+#am.saml2.sp.logout.response.binding=POST
+#am.saml2.sp.entityId=http://localhost:8080
+#am.saml2.sp.skew=300
+#am.saml2.idp=http://localhost:9080/syncope-wa/idp/metadata
+#am.saml2.keystore=classpath:/saml.keystore.jks
+#am.saml2.keystore.type=jks
+#am.saml2.keystore.storepass=changeit
+#am.saml2.keystore.keypass=changeit
+
+am.type=CAS
+am.cas.server.name=http://localhost:80
+am.cas.url.prefix=http://localhost:9080/syncope-wa/
 
 global.postLogout=http://localhost:8080/logout