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 80cf524  [SYNCOPE-1579] Adding SAML 2.0 support via pac4j-saml (#208)
80cf524 is described below

commit 80cf524e9db6008c047c1b5668d6e5cc0cd58bbb
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Mon Jul 27 15:26:29 2020 +0200

    [SYNCOPE-1579] Adding SAML 2.0 support via pac4j-saml (#208)
---
 .../main/resources/archetype-resources/wa/pom.xml  |   4 -
 .../api/service/wa/WASAML2IdPMetadataService.java  |   4 +-
 .../core/persistence/jpa/dao/JPAGroupDAO.java      |  11 +-
 .../provisioning/java/MappingManagerImplTest.java  |   2 +-
 docker/wa/pom.xml                                  |   2 -
 docker/wa/src/main/resources/wa.properties         |   6 +-
 ext/saml2sp/pom.xml                                |  68 ++++-
 fit/wa-reference/pom.xml                           |   9 +-
 .../src/main/resources/idp-metadata.xml            | 144 -----------
 fit/wa-reference/src/main/resources/wa.properties  |   3 +-
 .../org/apache/syncope/fit/sra/AbstractITCase.java | 182 +++++++------
 .../apache/syncope/fit/sra/OAUTH2SRAITCase.java    |   4 -
 .../org/apache/syncope/fit/sra/OIDCSRAITCase.java  | 116 ++++-----
 .../org/apache/syncope/fit/sra/SAML2SRAITCase.java | 282 +++++++++++++++++++++
 .../test/resources/application-saml2.properties    |  26 +-
 pom.xml                                            | 112 +++-----
 sra/pom.xml                                        |  22 +-
 .../org/apache/syncope/sra/SecurityConfig.java     |  73 +++++-
 .../AbstractServerLogoutSuccessHandler.java        |  77 ++++++
 .../{ => oauth2}/OAuth2SecurityConfigUtils.java    |  13 +-
 ...cClientInitiatedServerLogoutSuccessHandler.java |  74 +-----
 .../sra/security/pac4j/ServerHttpContext.java      | 226 +++++++++++++++++
 .../sra/security/pac4j/ServerHttpSessionStore.java |  68 +++++
 .../security/saml2/SAML2AnonymousWebFilter.java    |  57 +++++
 .../security/saml2/SAML2AuthenticationToken.java   |  49 ++++
 .../sra/security/saml2/SAML2BindingType.java       |  36 +++
 .../sra/security/saml2/SAML2MetadataEndpoint.java  |  54 ++++
 .../sra/security/saml2/SAML2RequestGenerator.java  |  65 +++++
 .../security/saml2/SAML2SecurityConfigUtils.java   |  96 +++++++
 .../security/saml2/SAML2ServerLogoutHandler.java   |  56 ++++
 .../saml2/SAML2ServerLogoutSuccessHandler.java     |  33 +++
 .../SAML2WebSsoAuthenticationRequestWebFilter.java |  67 +++++
 .../saml2/SAML2WebSsoAuthenticationWebFilter.java  | 117 +++++++++
 ...DoNothingIfCommittedServerRedirectStrategy.java |  34 +++
 .../resources/debug/application-debug.properties   |  32 ++-
 sra/src/test/resources/debug/saml.keystore.jks     | Bin 0 -> 9263 bytes
 wa/pom.xml                                         |   9 -
 .../metadata/RestfulSamlIdPMetadataGenerator.java  |   9 +-
 .../metadata/RestfulSamlIdPMetadataLocator.java    |  43 ++--
 wa/starter/src/main/resources/wa.properties        |   6 +-
 wa/starter/src/test/resources/debug/wa.properties  |   3 +-
 41 files changed, 1779 insertions(+), 515 deletions(-)

diff --git a/archetype/src/main/resources/archetype-resources/wa/pom.xml 
b/archetype/src/main/resources/archetype-resources/wa/pom.xml
index 044806d..a1c919d 100644
--- a/archetype/src/main/resources/archetype-resources/wa/pom.xml
+++ b/archetype/src/main/resources/archetype-resources/wa/pom.xml
@@ -32,10 +32,6 @@ under the License.
   <artifactId>${artifactId}</artifactId>
   <packaging>war</packaging>
 
-  <properties>
-    <opensaml.version>3.4.5</opensaml.version>
-  </properties>
-
   <dependencies>
     <dependency>
       <groupId>${groupId}</groupId>
diff --git 
a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/wa/WASAML2IdPMetadataService.java
 
b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/wa/WASAML2IdPMetadataService.java
index 219abb0..f5b73d4 100644
--- 
a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/wa/WASAML2IdPMetadataService.java
+++ 
b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/wa/WASAML2IdPMetadataService.java
@@ -51,6 +51,8 @@ import 
org.apache.syncope.common.rest.api.service.JAXRSService;
 @Path("wa/saml2idp/metadata")
 public interface WASAML2IdPMetadataService extends JAXRSService {
 
+    String DEFAULT_OWNER = "Syncope";
+
     /**
      * Returns a document outlining keys and metadata of Syncope as SAML 2.0 
IdP.
      *
@@ -60,7 +62,7 @@ public interface WASAML2IdPMetadataService extends 
JAXRSService {
      */
     @GET
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
-    SAML2IdPMetadataTO getByOwner(@QueryParam("appliesTo") 
@DefaultValue("Syncope") String appliesTo);
+    SAML2IdPMetadataTO getByOwner(@QueryParam("appliesTo") 
@DefaultValue(DEFAULT_OWNER) String appliesTo);
 
     /**
      * Returns the SAML 2.0 IdP metadata matching the given key.
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAGroupDAO.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAGroupDAO.java
index a8ea10f..19d0901 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAGroupDAO.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAGroupDAO.java
@@ -20,7 +20,6 @@ package org.apache.syncope.core.persistence.jpa.dao;
 
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
@@ -255,11 +254,10 @@ public class JPAGroupDAO extends AbstractAnyDAO<Group> 
implements GroupDAO {
         clearUDynMembers(merged);
         if (merged.getUDynMembership() != null) {
             SearchCond cond = 
buildDynMembershipCond(merged.getUDynMembership().getFIQLCond(), 
merged.getRealm());
-            int count = searchDAO.count(
-                    
Collections.<String>singleton(merged.getRealm().getFullPath()), cond, 
AnyTypeKind.USER);
+            int count = 
searchDAO.count(Set.of(merged.getRealm().getFullPath()), cond, 
AnyTypeKind.USER);
             for (int page = 1; page <= (count / AnyDAO.DEFAULT_PAGE_SIZE) + 1; 
page++) {
                 List<User> matching = searchDAO.search(
-                        
Collections.<String>singleton(merged.getRealm().getFullPath()),
+                        Set.of(merged.getRealm().getFullPath()),
                         cond,
                         page,
                         AnyDAO.DEFAULT_PAGE_SIZE,
@@ -279,11 +277,10 @@ public class JPAGroupDAO extends AbstractAnyDAO<Group> 
implements GroupDAO {
         clearADynMembers(merged);
         merged.getADynMemberships().forEach(memb -> {
             SearchCond cond = buildDynMembershipCond(memb.getFIQLCond(), 
merged.getRealm());
-            int count = searchDAO.count(
-                    
Collections.<String>singleton(merged.getRealm().getFullPath()), cond, 
AnyTypeKind.ANY_OBJECT);
+            int count = 
searchDAO.count(Set.of(merged.getRealm().getFullPath()), cond, 
AnyTypeKind.ANY_OBJECT);
             for (int page = 1; page <= (count / AnyDAO.DEFAULT_PAGE_SIZE) + 1; 
page++) {
                 List<AnyObject> matching = searchDAO.search(
-                        
Collections.<String>singleton(merged.getRealm().getFullPath()),
+                        Set.of(merged.getRealm().getFullPath()),
                         cond,
                         page,
                         AnyDAO.DEFAULT_PAGE_SIZE,
diff --git 
a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/MappingManagerImplTest.java
 
b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/MappingManagerImplTest.java
index b8137f4..81bd345 100644
--- 
a/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/MappingManagerImplTest.java
+++ 
b/core/provisioning-java/src/test/java/org/apache/syncope/core/provisioning/java/MappingManagerImplTest.java
@@ -264,7 +264,7 @@ public class MappingManagerImplTest extends AbstractTest {
         // 2. verify that dynamic membership is in place
         assertTrue(userDAO.findAllGroupKeys(user).contains(group.getKey()));
 
-        // 3.
+        // 3. check propagation attrs
         ExternalResource csv = resourceDAO.find("resource-csv");
         Provision provision = csv.getProvision(AnyTypeKind.USER.name()).get();
         assertNotNull(provision);
diff --git a/docker/wa/pom.xml b/docker/wa/pom.xml
index 428ae14..d7531e2 100644
--- a/docker/wa/pom.xml
+++ b/docker/wa/pom.xml
@@ -34,8 +34,6 @@ under the License.
   <packaging>war</packaging>
 
   <properties>
-    <opensaml.version>4.0.0</opensaml.version>
-
     <rootpom.basedir>${basedir}/../..</rootpom.basedir>
   </properties>
   
diff --git a/docker/wa/src/main/resources/wa.properties 
b/docker/wa/src/main/resources/wa.properties
index eb69a87..01534fd 100644
--- a/docker/wa/src/main/resources/wa.properties
+++ b/docker/wa/src/main/resources/wa.properties
@@ -27,9 +27,13 @@ cas.server.name=http://localhost:8080
 cas.server.prefix=${cas.server.name}/syncope-wa
 cas.server.scope=syncope.org
 
+cas.tgc.secure=false
 cas.logout.follow-service-redirects=true
 
-cas.authn.saml-idp.entity-id=https://syncope.apache.org/saml
+cas.authn.saml-idp.entity-id=http://localhost:8080/saml
+cas.authn.saml-idp.metadata.metadata-backup-location=file:${conf.directory}/saml
+
+cas.authn.oidc.issuer=http://localhost:8080/syncope-wa/oidc/
 
 # Disable access to the login endpoint
 # if no target application is specified.
diff --git a/ext/saml2sp/pom.xml b/ext/saml2sp/pom.xml
index d7a23b7..68cacbd 100644
--- a/ext/saml2sp/pom.xml
+++ b/ext/saml2sp/pom.xml
@@ -34,9 +34,75 @@ under the License.
   <packaging>pom</packaging>
   
   <properties>
+    <opensaml.version>3.3.1</opensaml.version>
     <rootpom.basedir>${basedir}/../..</rootpom.basedir>
   </properties>
-  
+
+  <dependencyManagement>
+    <dependencies>
+      <!-- OpenSAML -->
+      <dependency>
+        <groupId>org.opensaml</groupId>
+        <artifactId>opensaml-saml-api</artifactId>
+        <version>${opensaml.version}</version>
+        <exclusions>
+          <exclusion>
+            <groupId>org.opensaml</groupId>
+            <artifactId>opensaml-storage-api</artifactId>
+          </exclusion>
+          <exclusion>
+            <groupId>org.opensaml</groupId>
+            <artifactId>opensaml-messaging-api</artifactId>
+          </exclusion>
+          <exclusion>
+            <groupId>org.apache.velocity</groupId>
+            <artifactId>velocity</artifactId>
+          </exclusion>
+          <exclusion>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+          </exclusion>
+          <exclusion>
+            <groupId>com.google.code.findbugs</groupId>
+            <artifactId>jsr305</artifactId>
+          </exclusion>
+        </exclusions>
+      </dependency>
+      <dependency>
+        <groupId>org.opensaml</groupId>
+        <artifactId>opensaml-saml-impl</artifactId>
+        <version>${opensaml.version}</version>
+        <exclusions>
+          <exclusion>
+            <groupId>org.opensaml</groupId>
+            <artifactId>opensaml-soap-impl</artifactId>
+          </exclusion>
+          <exclusion>
+            <groupId>org.opensaml</groupId>
+            <artifactId>opensaml-storage-api</artifactId>
+          </exclusion>
+          <exclusion>
+            <groupId>org.opensaml</groupId>
+            <artifactId>opensaml-messaging-api</artifactId>
+          </exclusion>
+          <exclusion>
+            <groupId>org.apache.velocity</groupId>
+            <artifactId>velocity</artifactId>
+          </exclusion>
+          <exclusion>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+          </exclusion>
+          <exclusion>
+            <groupId>com.google.code.findbugs</groupId>
+            <artifactId>jsr305</artifactId>
+          </exclusion>
+        </exclusions>
+      </dependency>
+      <!-- /OpenSAML -->
+    </dependencies>
+  </dependencyManagement>
+
   <modules>
     <module>common-lib</module>
     <module>persistence-api</module>
diff --git a/fit/wa-reference/pom.xml b/fit/wa-reference/pom.xml
index b609cbd..9bf1300 100644
--- a/fit/wa-reference/pom.xml
+++ b/fit/wa-reference/pom.xml
@@ -34,8 +34,6 @@ under the License.
   <packaging>war</packaging>
   
   <properties>
-    <opensaml.version>4.0.0</opensaml.version>
-
     <ianal.skip>true</ianal.skip>
 
     <rootpom.basedir>${basedir}/../..</rootpom.basedir>
@@ -243,6 +241,13 @@ under the License.
         <directory>src/test/resources</directory>
         <filtering>true</filtering>
       </testResource>
+      <testResource>
+        <directory>${basedir}/../../sra/src/test/resources/debug</directory>
+        <filtering>false</filtering>
+        <includes>
+          <include>saml.keystore.jks</include>
+        </includes>
+      </testResource>
     </testResources>
   </build>
   
diff --git a/fit/wa-reference/src/main/resources/idp-metadata.xml 
b/fit/wa-reference/src/main/resources/idp-metadata.xml
deleted file mode 100644
index 2e0162b..0000000
--- a/fit/wa-reference/src/main/resources/idp-metadata.xml
+++ /dev/null
@@ -1,144 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  ~ 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.
-  -->
-<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" 
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"; 
xmlns:shibmd="urn:mace:shibboleth:metadata:1.0" 
xmlns:xml="http://www.w3.org/XML/1998/namespace"; 
xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui" 
entityID="https://syncope.apache.org/idp";>
-    <IDPSSODescriptor 
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol 
urn:oasis:names:tc:SAML:1.1:protocol urn:mace:shibboleth:1.0">
-        <Extensions>
-            <shibmd:Scope regexp="false">example.net</shibmd:Scope>
-        </Extensions>
-        <KeyDescriptor use="signing">
-            <ds:KeyInfo>
-                <ds:X509Data>
-                    
<ds:X509Certificate>MIIDLTCCAhWgAwIBAgIUVqwgQMQunB5UtoiiOqP1oQeg7lcwDQYJKoZIhvcNAQEL
-                        
BQAwHjEcMBoGA1UEAwwTbW1vYXl5ZWQudW5pY29uLm5ldDAeFw0xOTExMDExNDQ3
-                        
NDhaFw0zOTExMDExNDQ3NDhaMB4xHDAaBgNVBAMME21tb2F5eWVkLnVuaWNvbi5u
-                        
ZXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLXKHf93KZztJfCpNg
-                        
R/ip6EdOp3Z52HmwT32QlOzeby+2prqbOanQcs5oEeXoz6cdzjwOO6isnqZ3ES7p
-                        
BuVyuUoYVZyuXY6dsk6ANxeOXBRzGBS3ZemzYRQVmvQudNHUqdXpJelkFZvz3Au2
-                        
I594V2PZjywtuGIUb+T7j+8hh6Srf8c/W/KmC3KLFfU2yDQrcjuhGv+0Py5ZUpXs
-                        
EANs/d/AYV+LbMp3UtvWSOy8xGb+xxjS2KhTd53Oc6xsCgTPgTM5Y3DVA0ERNH+n
-                        
ppngRi/t3NggIN0EKYAS6ZqJi1GBEVHFOoacebLSy/UQA8tYI170/gf03/OYwO2S
-                        
9GATAgMBAAGjYzBhMB0GA1UdDgQWBBQxJh8NNf+qGJNZPlOItCWFQFY/wDBABgNV
-                        
HREEOTA3ghNtbW9heXllZC51bmljb24ubmV0hiBtbW9heXllZC51bmljb24ubmV0
-                        
L2lkcC9tZXRhZGF0YTANBgkqhkiG9w0BAQsFAAOCAQEAMMOb+f4Log69KUeAEvgh
-                        
sWTjiZujvl44nY4roXofAoXYc3vos/p5JVwEtrxgTLdyTsz65kZtaRISRrUJ3k0n
-                        
K22L2eXGa85qPhdKivRyNip5AMVi0zSXC6uhG50571Gy5UK/Rh3gvg7VM8GUFDHL
-                        
+Zay9ffV9lf0UVmFObA+PAe+HNY/dYRLIP9/pFW0+c1MmFtwCTrO4xbecfzA+Yde
-                        
9dbaBjS4veOSvFKiaCOvsiIVEUt1J7NrqM5sgYvOR5Q5zv0G72pmzS8cuGe2UP7e
-                        
i24oGm471cMDTLyFLYMCL8veHydcgfIV9z5g0PksV0kQL91r4XVkIp3iFZJ+TUBF
-                        zg==</ds:X509Certificate>
-                </ds:X509Data>
-            </ds:KeyInfo>
-        </KeyDescriptor>
-        <KeyDescriptor use="encryption">
-            <ds:KeyInfo>
-                <ds:X509Data>
-                    
<ds:X509Certificate>MIIDLTCCAhWgAwIBAgIUKymtgciRE6pWwDfrsI58qL9pQMgwDQYJKoZIhvcNAQEL
-                        
BQAwHjEcMBoGA1UEAwwTbW1vYXl5ZWQudW5pY29uLm5ldDAeFw0xOTExMDExNDQ3
-                        
NDhaFw0zOTExMDExNDQ3NDhaMB4xHDAaBgNVBAMME21tb2F5eWVkLnVuaWNvbi5u
-                        
ZXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCKrT805sny3vlaYjTn
-                        
+m6m3VbUoStvnacgwHH+orNFhHoV1HP2ndoH5BsEDB9tQYuyRbGUm/nYVOSHayzB
-                        
G3bzMGdU7woO6rsCqpHuxUyEvojd/y/N/r9jMzeBOCu0KDBTrn3BJhnGSwSTfhOS
-                        
3r20JFmDuTkHmabRs7ro0BvDaQ29jh38ro1iwB4E/4mqb1zYP13NI3ooErN/o6pl
-                        
XKpnFY37bDDOyOuocjN9tfPNIANNFKah0HjWOP0Nso0D1g6jHOSzmOw/Yxg61vBk
-                        
qOD4aKhLYPAxsXRl80nDrwTnm3/9xLQj9D3uLAtDLnn9pSqn3jCLxsxsHfKL/zkB
-                        
IKEBAgMBAAGjYzBhMB0GA1UdDgQWBBSrPjAgCJIHYmsofDcDIPzEhnYxmTBABgNV
-                        
HREEOTA3ghNtbW9heXllZC51bmljb24ubmV0hiBtbW9heXllZC51bmljb24ubmV0
-                        
L2lkcC9tZXRhZGF0YTANBgkqhkiG9w0BAQsFAAOCAQEAI8MlofbE0tbq8ez2d0Lq
-                        
Syhp4Q/shMEwjqcDarOwR+ACB9McOannUpAG7TCDp8Ch5E/V1B0Uo/5DF2tAzB1y
-                        
7sgAmy2mY9/mFhMYpOqTCagufwewaMkn9n7ETzC/6vQEjYrjiNyNR0F3UQQz2bhe
-                        
ROM3YuKctuOnMthc+ZE7vn+AXCGumRHBhyCaYdzfeUh7id+yrd9B51+o3iF4eu6w
-                        
zJi5z7FMCS6I4PSc/uWYDw1ahzoPONjazWSEWGUibZaJYM3pJHkuwqyWKOFGVknH
-                        
J1Qv4WCfSPb6eva94TZX0lkLM01C7NZObnfxY3fvJGcyFl8wlRTUYvuqM8md5CEp
-                        LA==</ds:X509Certificate>
-                </ds:X509Data>
-            </ds:KeyInfo>
-        </KeyDescriptor>
-
-        <!--
-        <ArtifactResolutionService 
Binding="urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding"
-                                   
Location="https://syncope.apache.org/cas/idp/profile/SAML1/SOAP/ArtifactResolution";
 index="1"/>
-        -->
-
-        <SingleLogoutService 
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" 
Location="https://syncope.apache.org/cas/idp/profile/SAML2/POST/SLO"/>
-        <SingleLogoutService 
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" 
Location="https://syncope.apache.org/cas/idp/profile/SAML2/Redirect/SLO"; />
-
-        <NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat>
-        
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
-
-        <SingleSignOnService 
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" 
Location="https://syncope.apache.org/cas/idp/profile/SAML2/POST/SSO"/>
-        <SingleSignOnService 
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign" 
Location="https://syncope.apache.org/cas/idp/profile/SAML2/POST-SimpleSign/SSO"/>
-        <SingleSignOnService 
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" 
Location="https://syncope.apache.org/cas/idp/profile/SAML2/Redirect/SSO"/>
-        <SingleSignOnService 
Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" 
Location="https://syncope.apache.org/cas/idp/profile/SAML2/SOAP/ECP"/>
-    </IDPSSODescriptor>
-
-    <!--
-    <AttributeAuthorityDescriptor 
protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol 
urn:oasis:names:tc:SAML:2.0:protocol">
-        <Extensions>
-            <shibmd:Scope regexp="false">example.net</shibmd:Scope>
-        </Extensions>
-        <KeyDescriptor use="signing">
-            <ds:KeyInfo>
-                <ds:X509Data>
-                    
<ds:X509Certificate>MIIDLTCCAhWgAwIBAgIUVqwgQMQunB5UtoiiOqP1oQeg7lcwDQYJKoZIhvcNAQEL
-BQAwHjEcMBoGA1UEAwwTbW1vYXl5ZWQudW5pY29uLm5ldDAeFw0xOTExMDExNDQ3
-NDhaFw0zOTExMDExNDQ3NDhaMB4xHDAaBgNVBAMME21tb2F5eWVkLnVuaWNvbi5u
-ZXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLXKHf93KZztJfCpNg
-R/ip6EdOp3Z52HmwT32QlOzeby+2prqbOanQcs5oEeXoz6cdzjwOO6isnqZ3ES7p
-BuVyuUoYVZyuXY6dsk6ANxeOXBRzGBS3ZemzYRQVmvQudNHUqdXpJelkFZvz3Au2
-I594V2PZjywtuGIUb+T7j+8hh6Srf8c/W/KmC3KLFfU2yDQrcjuhGv+0Py5ZUpXs
-EANs/d/AYV+LbMp3UtvWSOy8xGb+xxjS2KhTd53Oc6xsCgTPgTM5Y3DVA0ERNH+n
-ppngRi/t3NggIN0EKYAS6ZqJi1GBEVHFOoacebLSy/UQA8tYI170/gf03/OYwO2S
-9GATAgMBAAGjYzBhMB0GA1UdDgQWBBQxJh8NNf+qGJNZPlOItCWFQFY/wDBABgNV
-HREEOTA3ghNtbW9heXllZC51bmljb24ubmV0hiBtbW9heXllZC51bmljb24ubmV0
-L2lkcC9tZXRhZGF0YTANBgkqhkiG9w0BAQsFAAOCAQEAMMOb+f4Log69KUeAEvgh
-sWTjiZujvl44nY4roXofAoXYc3vos/p5JVwEtrxgTLdyTsz65kZtaRISRrUJ3k0n
-K22L2eXGa85qPhdKivRyNip5AMVi0zSXC6uhG50571Gy5UK/Rh3gvg7VM8GUFDHL
-+Zay9ffV9lf0UVmFObA+PAe+HNY/dYRLIP9/pFW0+c1MmFtwCTrO4xbecfzA+Yde
-9dbaBjS4veOSvFKiaCOvsiIVEUt1J7NrqM5sgYvOR5Q5zv0G72pmzS8cuGe2UP7e
-i24oGm471cMDTLyFLYMCL8veHydcgfIV9z5g0PksV0kQL91r4XVkIp3iFZJ+TUBF
-zg==</ds:X509Certificate>
-                </ds:X509Data>
-            </ds:KeyInfo>
-        </KeyDescriptor>
-        <AttributeService 
Binding="urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding" 
Location="https://syncope.apache.org/cas/idp/profile/SAML1/SOAP/AttributeQuery"/>
-        <AttributeService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" 
Location="https://syncope.apache.org/cas/idp/profile/SAML2/SOAP/AttributeQuery"/>
-    </AttributeAuthorityDescriptor>
-    -->
-
-    <!--
-    <Organization>
-        <OrganizationName xml:lang="en">Institution Name</OrganizationName>
-        <OrganizationDisplayName xml:lang="en">Institution 
DisplayName</OrganizationDisplayName>
-        <OrganizationURL xml:lang="en">URL</OrganizationURL>
-    </Organization>
-    <ContactPerson contactType="administrative">
-        <GivenName>John Smith</GivenName>
-        <EmailAddress>[email protected]</EmailAddress>
-    </ContactPerson>
-    <ContactPerson contactType="technical">
-        <GivenName>John Smith</GivenName>
-        <EmailAddress>[email protected]</EmailAddress>
-    </ContactPerson>
-    <ContactPerson contactType="support">
-        <GivenName>IT Services Support</GivenName>
-        <EmailAddress>[email protected]</EmailAddress>
-    </ContactPerson>
-    -->
-</EntityDescriptor>
diff --git a/fit/wa-reference/src/main/resources/wa.properties 
b/fit/wa-reference/src/main/resources/wa.properties
index fc6e2ca..8c94d91 100644
--- a/fit/wa-reference/src/main/resources/wa.properties
+++ b/fit/wa-reference/src/main/resources/wa.properties
@@ -31,7 +31,8 @@ cas.authn.syncope.url=http://localhost:9080/syncope/rest/
 cas.tgc.secure=false
 cas.logout.follow-service-redirects=true
 
-cas.authn.saml-idp.entity-id=https://syncope.apache.org/saml
+cas.authn.saml-idp.entity-id=http://localhost:9080/saml
+cas.authn.saml-idp.metadata.metadata-backup-location=file:${conf.directory}/saml
 
 cas.authn.oidc.issuer=http://localhost:9080/syncope-wa/oidc/
 
cas.authn.oidc.id-token-signing-alg-values-supported=RS256,RS384,RS512,PS256,PS384,PS512,ES256,ES384,ES512,HS256,HS384,HS512
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 3a76fee..db6e6a0 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
@@ -19,33 +19,47 @@
 package org.apache.syncope.fit.sra;
 
 import static org.awaitility.Awaitility.await;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
+import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.ConnectException;
 import java.net.InetSocketAddress;
 import java.net.Socket;
 import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 import java.util.concurrent.TimeUnit;
 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.cxf.jaxrs.client.WebClient;
+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.HttpPost;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
 import org.apache.syncope.client.lib.SyncopeClient;
 import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
-import org.apache.syncope.common.lib.auth.SyncopeAuthModuleConf;
 import org.apache.syncope.common.lib.policy.AuthPolicyTO;
 import org.apache.syncope.common.lib.policy.DefaultAuthPolicyConf;
-import org.apache.syncope.common.lib.to.AuthModuleTO;
 import org.apache.syncope.common.lib.to.SRARouteTO;
-import org.apache.syncope.common.lib.to.client.OIDCRPTO;
-import org.apache.syncope.common.lib.types.ClientAppType;
-import org.apache.syncope.common.lib.types.OIDCSubjectType;
 import org.apache.syncope.common.lib.types.PolicyType;
 import org.apache.syncope.common.lib.types.SRARouteFilter;
 import org.apache.syncope.common.lib.types.SRARouteFilterFactory;
@@ -53,7 +67,6 @@ import org.apache.syncope.common.lib.types.SRARoutePredicate;
 import org.apache.syncope.common.lib.types.SRARoutePredicateFactory;
 import org.apache.syncope.common.lib.types.SRARouteType;
 import org.apache.syncope.common.rest.api.RESTHeaders;
-import org.apache.syncope.common.rest.api.service.AuthModuleService;
 import org.apache.syncope.common.rest.api.service.ClientAppService;
 import org.apache.syncope.common.rest.api.service.PolicyService;
 import org.apache.syncope.common.rest.api.service.SRARouteService;
@@ -89,8 +102,6 @@ public abstract class AbstractITCase {
 
     protected static SyncopeClient adminClient;
 
-    protected static AuthModuleService authModuleService;
-
     protected static PolicyService policyService;
 
     protected static ClientAppService clientAppService;
@@ -104,11 +115,9 @@ public abstract class AbstractITCase {
         clientFactory = new 
SyncopeClientFactoryBean().setAddress(CORE_ADDRESS);
         adminClient = clientFactory.create(ADMIN_UNAME, ADMIN_PWD);
 
-        authModuleService = adminClient.getService(AuthModuleService.class);
         policyService = adminClient.getService(PolicyService.class);
         clientAppService = adminClient.getService(ClientAppService.class);
         sraRouteService = adminClient.getService(SRARouteService.class);
-
     }
 
     @BeforeAll
@@ -193,70 +202,6 @@ public abstract class AbstractITCase {
         }
     }
 
-    protected static void oidcClientAppSetup(
-            final String appName,
-            final String sraRegistrationId,
-            final Long clientAppId,
-            final String clientId,
-            final String clientSecret) {
-
-        AuthModuleTO syncopeAuthModule = authModuleService.list().stream().
-                filter(module -> module.getConf() instanceof 
SyncopeAuthModuleConf).
-                findFirst().orElseThrow(() -> new 
IllegalArgumentException("Could not find Syncope Auth Module"));
-
-        AuthPolicyTO syncopeAuthPolicy = 
policyService.list(PolicyType.AUTH).stream().
-                map(AuthPolicyTO.class::cast).
-                filter(policy -> policy.getConf() instanceof 
DefaultAuthPolicyConf
-                && ((DefaultAuthPolicyConf) 
policy.getConf()).getAuthModules().contains(syncopeAuthModule.getKey())).
-                findFirst().
-                orElseGet(() -> {
-                    DefaultAuthPolicyConf policyConf = new 
DefaultAuthPolicyConf();
-                    
policyConf.getAuthModules().add(syncopeAuthModule.getKey());
-
-                    AuthPolicyTO policy = new AuthPolicyTO();
-                    policy.setDescription("Syncope authentication");
-                    policy.setConf(policyConf);
-
-                    Response response = policyService.create(PolicyType.AUTH, 
policy);
-                    if (response.getStatusInfo().getStatusCode() != 
Response.Status.CREATED.getStatusCode()) {
-                        fail("Could not create Syncope Auth Policy");
-                    }
-
-                    return policyService.read(PolicyType.AUTH, 
response.getHeaderString(RESTHeaders.RESOURCE_KEY));
-                });
-
-        OIDCRPTO clientApp = 
clientAppService.list(ClientAppType.OIDCRP).stream().
-                filter(app -> appName.equals(app.getName())).
-                map(OIDCRPTO.class::cast).
-                findFirst().
-                orElseGet(() -> {
-                    OIDCRPTO app = new OIDCRPTO();
-                    app.setName(appName);
-                    app.setClientAppId(clientAppId);
-                    app.setClientId(clientId);
-                    app.setClientSecret(clientSecret);
-
-                    Response response = 
clientAppService.create(ClientAppType.OIDCRP, app);
-                    if (response.getStatusInfo().getStatusCode() != 
Response.Status.CREATED.getStatusCode()) {
-                        fail("Could not create OIDC Client App");
-                    }
-
-                    return clientAppService.read(
-                            ClientAppType.OIDCRP, 
response.getHeaderString(RESTHeaders.RESOURCE_KEY));
-                });
-
-        clientApp.setClientId(clientId);
-        clientApp.setClientSecret(clientSecret);
-        clientApp.setSubjectType(OIDCSubjectType.PUBLIC);
-        clientApp.getRedirectUris().add(SRA_ADDRESS + "/login/oauth2/code/" + 
sraRegistrationId);
-        clientApp.setAuthPolicy(syncopeAuthPolicy.getKey());
-        clientApp.setSignIdToken(true);
-        clientApp.setLogoutUri(SRA_ADDRESS + "/logout");
-
-        clientAppService.update(ClientAppType.OIDCRP, clientApp);
-        clientAppService.pushToWA();
-    }
-
     protected static void doStartSRA(final String activeProfile)
             throws IOException, InterruptedException, TimeoutException {
 
@@ -304,11 +249,36 @@ public abstract class AbstractITCase {
             }
             return connected;
         });
-        assertTrue(WebClient.create(SRA_ADDRESS).get().getStatus() < 400);
+        assertDoesNotThrow(() -> 
WebClient.create(SRA_ADDRESS).get().getStatus());
 
         sraRouteService.pushToSRA();
     }
 
+    protected static AuthPolicyTO getAuthPolicy() {
+        String authModule = "DefaultSyncopeAuthModule";
+
+        return policyService.list(PolicyType.AUTH).stream().
+                map(AuthPolicyTO.class::cast).
+                filter(policy -> policy.getConf() instanceof 
DefaultAuthPolicyConf
+                && ((DefaultAuthPolicyConf) 
policy.getConf()).getAuthModules().contains(authModule)).
+                findFirst().
+                orElseGet(() -> {
+                    DefaultAuthPolicyConf policyConf = new 
DefaultAuthPolicyConf();
+                    policyConf.getAuthModules().add(authModule);
+
+                    AuthPolicyTO policy = new AuthPolicyTO();
+                    policy.setDescription("Syncope authentication");
+                    policy.setConf(policyConf);
+
+                    Response response = policyService.create(PolicyType.AUTH, 
policy);
+                    if (response.getStatusInfo().getStatusCode() != 
Response.Status.CREATED.getStatusCode()) {
+                        fail("Could not create Syncope Auth Policy");
+                    }
+
+                    return policyService.read(PolicyType.AUTH, 
response.getHeaderString(RESTHeaders.RESOURCE_KEY));
+                });
+    }
+
     @AfterAll
     public static void stopSRA() throws InterruptedException {
         if (SRA != null) {
@@ -316,4 +286,64 @@ public abstract class AbstractITCase {
             SRA.waitFor();
         }
     }
+
+    protected static String extractCASExecution(final String responseBody) {
+        int begin = responseBody.indexOf("name=\"execution\" value=\"");
+        assertNotEquals(-1, begin);
+        int end = responseBody.indexOf("\"/><input type=\"hidden\" 
name=\"_eventId\"");
+        assertNotEquals(-1, end);
+
+        String execution = responseBody.substring(begin + 24, end);
+        assertNotNull(execution);
+        return execution;
+    }
+
+    protected static CloseableHttpResponse authenticateToCas(
+            final String responseBody, final CloseableHttpClient httpclient, 
final HttpClientContext context)
+            throws IOException {
+
+        List<NameValuePair> form = new ArrayList<>();
+        form.add(new BasicNameValuePair("_eventId", "submit"));
+        form.add(new BasicNameValuePair("execution", 
extractCASExecution(responseBody)));
+        form.add(new BasicNameValuePair("username", "bellini"));
+        form.add(new BasicNameValuePair("password", "password"));
+        form.add(new BasicNameValuePair("geolocation", ""));
+
+        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));
+        return httpclient.execute(post, context);
+    }
+
+    protected static ObjectNode checkGetResponse(
+            final CloseableHttpResponse response, final String 
originalRequestURI) throws IOException {
+
+        assertEquals(HttpStatus.SC_OK, 
response.getStatusLine().getStatusCode());
+
+        assertEquals(MediaType.APPLICATION_JSON, 
response.getFirstHeader(HttpHeaders.CONTENT_TYPE).getValue());
+
+        JsonNode json = 
OBJECT_MAPPER.readTree(EntityUtils.toString(response.getEntity()));
+
+        ObjectNode args = (ObjectNode) json.get("args");
+        assertEquals("value1", args.get("key1").asText());
+
+        ArrayNode key2 = (ArrayNode) args.get("key2");
+        assertEquals("value2", key2.get(0).asText());
+        assertEquals("value3", key2.get(1).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());
+
+        return headers;
+    }
+
+    protected void checkLogout(final CloseableHttpResponse response) throws 
IOException {
+        assertEquals(HttpStatus.SC_NO_CONTENT, 
response.getStatusLine().getStatusCode());
+        assertEquals("true", 
response.getFirstHeader(LOGGED_OUT_HEADER).getValue());
+    }
 }
diff --git 
a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OAUTH2SRAITCase.java
 
b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OAUTH2SRAITCase.java
index a1288a4..430c1ac 100644
--- 
a/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OAUTH2SRAITCase.java
+++ 
b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/OAUTH2SRAITCase.java
@@ -18,10 +18,6 @@
  */
 package org.apache.syncope.fit.sra;
 
-import static org.apache.syncope.fit.sra.AbstractITCase.doStartSRA;
-import static org.apache.syncope.fit.sra.AbstractITCase.oidcClientAppSetup;
-import static org.apache.syncope.fit.sra.OIDCSRAITCase.CLIENT_ID;
-import static org.apache.syncope.fit.sra.OIDCSRAITCase.CLIENT_SECRET;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
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 079a13b..191cd32 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
@@ -27,7 +27,6 @@ import static org.junit.jupiter.api.Assertions.fail;
 import static org.junit.jupiter.api.Assumptions.assumeTrue;
 
 import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.jwt.SignedJWT;
@@ -58,6 +57,10 @@ 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.OIDCRPTO;
+import org.apache.syncope.common.lib.types.ClientAppType;
+import org.apache.syncope.common.lib.types.OIDCSubjectType;
+import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 
@@ -76,6 +79,45 @@ public class OIDCSRAITCase extends AbstractITCase {
         doStartSRA("oidc");
     }
 
+    protected static void oidcClientAppSetup(
+            final String appName,
+            final String sraRegistrationId,
+            final Long clientAppId,
+            final String clientId,
+            final String clientSecret) {
+
+        OIDCRPTO clientApp = 
clientAppService.list(ClientAppType.OIDCRP).stream().
+                filter(app -> appName.equals(app.getName())).
+                map(OIDCRPTO.class::cast).
+                findFirst().
+                orElseGet(() -> {
+                    OIDCRPTO app = new OIDCRPTO();
+                    app.setName(appName);
+                    app.setClientAppId(clientAppId);
+                    app.setClientId(clientId);
+                    app.setClientSecret(clientSecret);
+
+                    Response response = 
clientAppService.create(ClientAppType.OIDCRP, app);
+                    if (response.getStatusInfo().getStatusCode() != 
Response.Status.CREATED.getStatusCode()) {
+                        fail("Could not create OIDC Client App");
+                    }
+
+                    return clientAppService.read(
+                            ClientAppType.OIDCRP, 
response.getHeaderString(RESTHeaders.RESOURCE_KEY));
+                });
+
+        clientApp.setClientId(clientId);
+        clientApp.setClientSecret(clientSecret);
+        clientApp.setSubjectType(OIDCSubjectType.PUBLIC);
+        clientApp.getRedirectUris().add(SRA_ADDRESS + "/login/oauth2/code/" + 
sraRegistrationId);
+        clientApp.setAuthPolicy(getAuthPolicy().getKey());
+        clientApp.setSignIdToken(true);
+        clientApp.setLogoutUri(SRA_ADDRESS + "/logout");
+
+        clientAppService.update(ClientAppType.OIDCRP, clientApp);
+        clientAppService.pushToWA();
+    }
+
     @BeforeAll
     public static void clientAppSetup() {
         
assumeTrue(OIDCSRAITCase.class.equals(MethodHandles.lookup().lookupClass()));
@@ -95,37 +137,6 @@ public class OIDCSRAITCase extends AbstractITCase {
         oidcClientAppSetup(OIDCSRAITCase.class.getName(), "OIDC", 1L, 
CLIENT_ID, CLIENT_SECRET);
     }
 
-    private ObjectNode checkResponse(final CloseableHttpResponse response, 
final String originalRequestURI)
-            throws IOException {
-
-        assertEquals(HttpStatus.SC_OK, 
response.getStatusLine().getStatusCode());
-
-        assertEquals(MediaType.APPLICATION_JSON, 
response.getFirstHeader(HttpHeaders.CONTENT_TYPE).getValue());
-
-        JsonNode json = 
OBJECT_MAPPER.readTree(EntityUtils.toString(response.getEntity()));
-
-        ObjectNode args = (ObjectNode) json.get("args");
-        assertEquals("value1", args.get("key1").asText());
-
-        ArrayNode key2 = (ArrayNode) args.get("key2");
-        assertEquals("value2", key2.get(0).asText());
-        assertEquals("value3", key2.get(1).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());
-
-        return headers;
-    }
-
-    protected void checkLogout(final CloseableHttpResponse response) {
-        assertEquals(HttpStatus.SC_NO_CONTENT, 
response.getStatusLine().getStatusCode());
-        assertEquals("true", 
response.getFirstHeader(LOGGED_OUT_HEADER).getValue());
-    }
-
     @Test
     public void web() throws IOException {
         CloseableHttpClient httpclient = HttpClients.createDefault();
@@ -138,7 +149,7 @@ public class OIDCSRAITCase extends AbstractITCase {
         get.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
         CloseableHttpResponse response = httpclient.execute(get, context);
 
-        ObjectNode headers = checkResponse(response, 
get.getURI().toASCIIString().replace("/public", ""));
+        ObjectNode headers = checkGetResponse(response, 
get.getURI().toASCIIString().replace("/public", ""));
         assertFalse(headers.has(HttpHeaders.COOKIE));
 
         // 2. protected
@@ -151,46 +162,21 @@ public class OIDCSRAITCase extends AbstractITCase {
 
         // 2a. redirected to WA login screen
         String responseBody = EntityUtils.toString(response.getEntity());
-        int begin = responseBody.indexOf("name=\"execution\" value=\"");
-        assertNotEquals(-1, begin);
-        int end = responseBody.indexOf("\"/><input type=\"hidden\" 
name=\"_eventId\"");
-        assertNotEquals(-1, end);
-
-        String execution = responseBody.substring(begin + 24, end);
-        assertNotNull(execution);
-
-        List<NameValuePair> form = new ArrayList<>();
-        form.add(new BasicNameValuePair("_eventId", "submit"));
-        form.add(new BasicNameValuePair("execution", execution));
-        form.add(new BasicNameValuePair("username", "bellini"));
-        form.add(new BasicNameValuePair("password", "password"));
-        form.add(new BasicNameValuePair("geolocation", ""));
-
-        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);
+        response = authenticateToCas(responseBody, httpclient, context);
 
         // 2b. WA attribute consent screen
         if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
             responseBody = EntityUtils.toString(response.getEntity());
-            begin = responseBody.indexOf("name=\"execution\" value=\"");
-            assertNotEquals(-1, begin);
-            end = responseBody.indexOf("\"/><input type=\"hidden\" 
name=\"_eventId\"");
-            assertNotEquals(-1, end);
-
-            execution = responseBody.substring(begin + 24, end);
-            assertNotNull(execution);
+            String execution = extractCASExecution(responseBody);
 
-            form = new ArrayList<>();
+            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"));
 
-            post = new HttpPost(WA_ADDRESS + "/login");
+            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));
@@ -207,11 +193,11 @@ public class OIDCSRAITCase extends AbstractITCase {
 
         responseBody = EntityUtils.toString(response.getEntity());
 
-        begin = responseBody.indexOf("name=\"allow\"");
+        int begin = responseBody.indexOf("name=\"allow\"");
         assertNotEquals(-1, begin);
         begin = responseBody.indexOf("href=\"", begin);
         assertNotEquals(-1, begin);
-        end = responseBody.indexOf("\">", begin);
+        int end = responseBody.indexOf("\">", begin);
         assertNotEquals(-1, end);
 
         String allow = responseBody.substring(begin + 6, end).replace("&amp;", 
"&");
@@ -223,7 +209,7 @@ public class OIDCSRAITCase extends AbstractITCase {
         get.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
         response = httpclient.execute(get, context);
 
-        headers = checkResponse(response, 
originalRequestURI.replace("/protected", ""));
+        headers = checkGetResponse(response, 
originalRequestURI.replace("/protected", ""));
         
assertTrue(headers.get(HttpHeaders.COOKIE).asText().contains("pac4jCsrfToken"));
 
         // 3. logout
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
new file mode 100644
index 0000000..762f2b4
--- /dev/null
+++ 
b/fit/wa-reference/src/test/java/org/apache/syncope/fit/sra/SAML2SRAITCase.java
@@ -0,0 +1,282 @@
+/*
+ * 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.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+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.commons.lang3.tuple.Pair;
+import org.apache.commons.lang3.tuple.Triple;
+import org.apache.commons.text.StringEscapeUtils;
+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.SAML2SPTO;
+import org.apache.syncope.common.lib.types.ClientAppType;
+import org.apache.syncope.common.lib.types.SAML2SPNameId;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+public class SAML2SRAITCase extends AbstractITCase {
+
+    @BeforeAll
+    public static void startSRA() throws IOException, InterruptedException, 
TimeoutException {
+        
assumeTrue(SAML2SRAITCase.class.equals(MethodHandles.lookup().lookupClass()));
+
+        doStartSRA("saml2");
+    }
+
+    @BeforeAll
+    public static void clientAppSetup() {
+        String appName = SAML2SRAITCase.class.getName();
+        SAML2SPTO clientApp = 
clientAppService.list(ClientAppType.SAML2SP).stream().
+                filter(app -> appName.equals(app.getName())).
+                map(SAML2SPTO.class::cast).
+                findFirst().
+                orElseGet(() -> {
+                    SAML2SPTO app = new SAML2SPTO();
+                    app.setName(appName);
+                    app.setClientAppId(3L);
+                    app.setEntityId("http://localhost:8080";);
+                    
app.setMetadataLocation("http://localhost:8080/saml2/metadata";);
+
+                    Response response = 
clientAppService.create(ClientAppType.SAML2SP, app);
+                    if (response.getStatusInfo().getStatusCode() != 
Response.Status.CREATED.getStatusCode()) {
+                        fail("Could not create SAML2 Client App");
+                    }
+
+                    return clientAppService.read(
+                            ClientAppType.SAML2SP, 
response.getHeaderString(RESTHeaders.RESOURCE_KEY));
+                });
+
+        clientApp.setSignAssertions(true);
+        clientApp.setSignResponses(true);
+        clientApp.setRequiredNameIdFormat(SAML2SPNameId.PERSISTENT);
+        clientApp.setAuthPolicy(getAuthPolicy().getKey());
+
+        clientAppService.update(ClientAppType.SAML2SP, clientApp);
+        clientAppService.pushToWA();
+    }
+
+    private Triple<String, String, String> parseSAMLRequestForm(final String 
responseBody) {
+        int begin = responseBody.indexOf("name=\"RelayState\" value=\"");
+        assertNotEquals(-1, begin);
+        int end = responseBody.indexOf("\"/>", begin);
+        assertNotEquals(-1, end);
+        String relayState = responseBody.substring(begin + 25, end);
+        assertNotNull(relayState);
+
+        begin = responseBody.indexOf("name=\"SAMLRequest\" value=\"");
+        assertNotEquals(-1, begin);
+        end = responseBody.indexOf("\"/>", begin);
+        assertNotEquals(-1, end);
+        String samlRequest = responseBody.substring(begin + 26, end);
+        assertNotNull(samlRequest);
+
+        begin = responseBody.indexOf("<form action=\"");
+        assertNotEquals(-1, begin);
+        end = responseBody.indexOf("\" method=\"post\">");
+        assertNotEquals(-1, end);
+        String action = 
StringEscapeUtils.unescapeXml(responseBody.substring(begin + 14, end));
+        assertNotNull(action);
+
+        return Triple.of(action, relayState, samlRequest);
+    }
+
+    private Triple<String, String, String> parseSAMLResponseForm(final String 
responseBody) {
+        int begin = responseBody.indexOf("name=\"RelayState\" value=\"");
+        assertNotEquals(-1, begin);
+        int end = responseBody.indexOf("\"/>");
+        assertNotEquals(-1, end);
+        String relayState = responseBody.substring(begin + 26, end);
+        assertNotNull(relayState);
+
+        begin = responseBody.indexOf("name=\"SAMLResponse\" value=\"");
+        assertNotEquals(-1, begin);
+        end = responseBody.indexOf("\"/>", begin);
+        assertNotEquals(-1, end);
+        String samlResponse = responseBody.substring(begin + 27, end);
+        assertNotNull(samlResponse);
+
+        begin = responseBody.indexOf("<form action=\"");
+        assertNotEquals(-1, begin);
+        end = responseBody.indexOf("\" method=\"post\">");
+        assertNotEquals(-1, end);
+        String action = 
StringEscapeUtils.unescapeXml(responseBody.substring(begin + 14, end));
+        assertNotNull(action);
+
+        return Triple.of(action, relayState, samlResponse);
+    }
+
+    @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?key1=value1&key2=value2&key2=value3");
+        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?key1=value1&key2=value2&key2=value3");
+        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. post SAML request
+        String responseBody = EntityUtils.toString(response.getEntity());
+        Triple<String, String, String> parsed = 
parseSAMLRequestForm(responseBody);
+
+        HttpPost post = new HttpPost(parsed.getLeft());
+        post.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_HTML);
+        post.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
+        post.setEntity(new UrlEncodedFormEntity(
+                List.of(new BasicNameValuePair("RelayState", 
parsed.getMiddle()),
+                        new BasicNameValuePair("SAMLRequest", 
parsed.getRight())), Consts.UTF_8));
+        response = httpclient.execute(post, context);
+        assertEquals(HttpStatus.SC_MOVED_TEMPORARILY, 
response.getStatusLine().getStatusCode());
+
+        // 2b. authenticate
+        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);
+        assertEquals(HttpStatus.SC_OK, 
response.getStatusLine().getStatusCode());
+
+        responseBody = EntityUtils.toString(response.getEntity());
+        response = authenticateToCas(responseBody, httpclient, context);
+
+        // 2c. 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"));
+
+            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());
+
+        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);
+        assertEquals(HttpStatus.SC_OK, 
response.getStatusLine().getStatusCode());
+
+        // 2d. post SAML response
+        responseBody = EntityUtils.toString(response.getEntity());
+        parsed = parseSAMLResponseForm(responseBody);
+
+        post = new HttpPost(parsed.getLeft());
+        post.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_HTML);
+        post.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
+        post.setEntity(new UrlEncodedFormEntity(
+                List.of(new BasicNameValuePair("RelayState", 
parsed.getMiddle()),
+                        new BasicNameValuePair("SAMLResponse", 
parsed.getRight())), Consts.UTF_8));
+        response = httpclient.execute(post, context);
+        assertEquals(HttpStatus.SC_MOVED_TEMPORARILY, 
response.getStatusLine().getStatusCode());
+
+        // 2e. 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);
+
+        // 3a. post SAML request
+        responseBody = EntityUtils.toString(response.getEntity());
+        parsed = parseSAMLRequestForm(responseBody);
+
+        post = new HttpPost(parsed.getLeft());
+        post.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_HTML);
+        post.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE);
+        post.setEntity(new UrlEncodedFormEntity(
+                List.of(new BasicNameValuePair("RelayState", 
parsed.getMiddle()),
+                        new BasicNameValuePair("SAMLRequest", 
parsed.getRight())), Consts.UTF_8));
+        response = httpclient.execute(post, context);
+        assertEquals(HttpStatus.SC_MOVED_TEMPORARILY, 
response.getStatusLine().getStatusCode());
+
+        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);
+
+        // 3b. post SAML response
+        // this is missing as currently WA does not responde with form for 
SP's SingleLogoutService
+        checkLogout(response);
+    }
+
+    @Override
+    protected void checkLogout(final CloseableHttpResponse response) throws 
IOException {
+        assertEquals(HttpStatus.SC_OK, 
response.getStatusLine().getStatusCode());
+
+        String responseBody = EntityUtils.toString(response.getEntity());
+        assertTrue(responseBody.contains("Logout successful"));
+        assertTrue(responseBody.contains("have successfully logged out of the 
Central Authentication Service"));
+    }
+}
diff --git a/sra/src/test/resources/debug/application-debug.properties 
b/fit/wa-reference/src/test/resources/application-saml2.properties
similarity index 57%
copy from sra/src/test/resources/debug/application-debug.properties
copy to fit/wa-reference/src/test/resources/application-saml2.properties
index 5958ee8..fa9a8e4 100644
--- a/sra/src/test/resources/debug/application-debug.properties
+++ b/fit/wa-reference/src/test/resources/application-saml2.properties
@@ -14,20 +14,16 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-#am.type=OIDC
-#am.oidc.configuration=http://localhost:9080/syncope-wa/oidc/
-#am.oidc.client.id=oidcTestClientId
-#am.oidc.client.secret=oidcTestClientSecret
-
-am.type=OAUTH2
-am.oauth2.tokenUri=http://localhost:9080/syncope-wa/oauth2.0/accessToken
-am.oauth2.authorizationUri=http://localhost:9080/syncope-wa/oauth2.0/authorize
-am.oauth2.userInfoUri=http://localhost:9080/syncope-wa/oauth2.0/profile
-am.oauth2.userNameAttributeName=id
-am.oauth2.scopes=
-am.oauth2.jwkSetUri=
-am.oauth2.issuer=http://localhost:9080/syncope-wa
-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
 
 global.postLogout=http://localhost:8080/logout
diff --git a/pom.xml b/pom.xml
index b5959f9..26de67f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -431,7 +431,6 @@ under the License.
     <camel.version>3.4.2</camel.version>
 
     <slf4j.version>2.0.0-alpha1</slf4j.version>
-    <opensaml.version>3.3.1</opensaml.version>
 
     <elasticsearch.version>7.8.0</elasticsearch.version>
 
@@ -446,6 +445,10 @@ under the License.
     <commons-text.version>1.9</commons-text.version>
     <commons-logging.version>1.1.3</commons-logging.version>
     
+    <modernizer-maven.version>2.1.0</modernizer-maven.version>
+
+    <pac4j.version>4.0.3</pac4j.version>
+
     <cas.version>6.3.0-SNAPSHOT</cas.version>
 
     <h2.version>1.4.200</h2.version>
@@ -771,67 +774,6 @@ under the License.
       </dependency>
       <!-- /Camel -->
 
-      <!-- OpenSAML -->
-      <dependency>
-        <groupId>org.opensaml</groupId>
-        <artifactId>opensaml-saml-api</artifactId>
-        <version>${opensaml.version}</version>
-        <exclusions>
-          <exclusion>
-            <groupId>org.opensaml</groupId>
-            <artifactId>opensaml-storage-api</artifactId>
-          </exclusion>
-          <exclusion>
-            <groupId>org.opensaml</groupId>
-            <artifactId>opensaml-messaging-api</artifactId>
-          </exclusion>
-          <exclusion>
-            <groupId>org.apache.velocity</groupId>
-            <artifactId>velocity</artifactId>
-          </exclusion>
-          <exclusion>
-            <groupId>org.apache.httpcomponents</groupId>
-            <artifactId>httpclient</artifactId>
-          </exclusion>
-          <exclusion>
-            <groupId>com.google.code.findbugs</groupId>
-            <artifactId>jsr305</artifactId>
-          </exclusion>
-        </exclusions>
-      </dependency>
-      <dependency>
-        <groupId>org.opensaml</groupId>
-        <artifactId>opensaml-saml-impl</artifactId>
-        <version>${opensaml.version}</version>
-        <exclusions>
-          <exclusion>
-            <groupId>org.opensaml</groupId>
-            <artifactId>opensaml-soap-impl</artifactId>
-          </exclusion>
-          <exclusion>
-            <groupId>org.opensaml</groupId>
-            <artifactId>opensaml-storage-api</artifactId>
-          </exclusion>
-          <exclusion>
-            <groupId>org.opensaml</groupId>
-            <artifactId>opensaml-messaging-api</artifactId>
-          </exclusion>
-          <exclusion>
-            <groupId>org.apache.velocity</groupId>
-            <artifactId>velocity</artifactId>
-          </exclusion>
-          <exclusion>
-            <groupId>org.apache.httpcomponents</groupId>
-            <artifactId>httpclient</artifactId>
-          </exclusion>
-          <exclusion>
-            <groupId>com.google.code.findbugs</groupId>
-            <artifactId>jsr305</artifactId>
-          </exclusion>
-        </exclusions>
-      </dependency>
-      <!-- /OpenSAML -->
-
       <dependency>
         <groupId>org.elasticsearch.client</groupId>
         <artifactId>elasticsearch-rest-high-level-client</artifactId>
@@ -1189,6 +1131,12 @@ under the License.
       </dependency>
 
       <dependency>
+        <groupId>org.gaul</groupId>
+        <artifactId>modernizer-maven-annotations</artifactId>
+        <version>${modernizer-maven.version}</version>
+      </dependency>
+
+      <dependency>
         <groupId>org.apache.tika</groupId>
         <artifactId>tika-core</artifactId>
         <version>1.24.1</version>
@@ -1421,6 +1369,19 @@ under the License.
       </dependency>
       <!-- /Flowable -->
 
+      <!-- PAC4J -->
+      <dependency>
+        <groupId>org.pac4j</groupId>
+        <artifactId>pac4j-saml</artifactId>
+        <version>${pac4j.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.pac4j</groupId>
+        <artifactId>pac4j-oidc</artifactId>
+        <version>${pac4j.version}</version>
+      </dependency>
+      <!-- PAC4J -->
+
       <!-- CAS -->
       <dependency>
         <groupId>org.apereo.cas</groupId>
@@ -2080,6 +2041,13 @@ under the License.
         <enabled>true</enabled>
       </snapshots>
     </repository>
+    <repository>
+      <id>shibboleth</id>
+      <url>https://build.shibboleth.net/nexus/content/groups/public</url>
+      <releases>
+        <enabled>true</enabled>
+      </releases>
+    </repository>
   </repositories>
 
   <pluginRepositories>
@@ -2427,7 +2395,7 @@ under the License.
       <plugin>
         <groupId>org.gaul</groupId>
         <artifactId>modernizer-maven-plugin</artifactId>
-        <version>2.1.0</version>
+        <version>${modernizer-maven.version}</version>
         <configuration>
           <javaVersion>${targetJdk}</javaVersion>
           <ignorePackages>
@@ -2691,15 +2659,15 @@ under the License.
             <link>http://www.slf4j.org/api/</link>
             <link>http://connid.tirasa.net/apidocs/1.5/</link>
             <link>http://cxf.apache.org/javadoc/latest/</link>
-            <link>http://fasterxml.github.io/jackson-core/javadoc/2.10/</link>
-            
<link>http://fasterxml.github.io/jackson-databind/javadoc/2.10/</link>
-            
<link>http://fasterxml.github.io/jackson-annotations/javadoc/2.10/</link>
-            
<link>http://fasterxml.github.io/jackson-dataformat-xml/javadoc/2.10/</link>
-            
<link>http://fasterxml.github.io/jackson-dataformats-text/javadoc/yaml/2.10/</link>
-            
<link>http://fasterxml.github.io/jackson-dataformats-text/javadoc/csv/2.10/</link>
+            <link>http://fasterxml.github.io/jackson-core/javadoc/2.11/</link>
+            
<link>http://fasterxml.github.io/jackson-databind/javadoc/2.11/</link>
+            
<link>http://fasterxml.github.io/jackson-annotations/javadoc/2.11/</link>
+            
<link>http://fasterxml.github.io/jackson-dataformat-xml/javadoc/2.11/</link>
+            
<link>http://fasterxml.github.io/jackson-dataformats-text/javadoc/yaml/2.11/</link>
+            
<link>http://fasterxml.github.io/jackson-dataformats-text/javadoc/csv/2.11/</link>
             
<link>https://www.javadoc.io/doc/org.apache.camel/camel-core/latest/</link>
             
<link>https://www.javadoc.io/doc/org.apache.camel/camel-spring/latest/</link>
-            <link>https://ci.apache.org/projects/wicket/apidocs/8.x/</link>
+            <link>https://ci.apache.org/projects/wicket/apidocs/9.x/</link>
             
<link>https://commons.apache.org/proper/commons-lang/javadocs/api-release/</link>
             
<link>https://commons.apache.org/proper/commons-jexl/apidocs/</link>
             <link>https://tika.apache.org/1.24/api/</link>
@@ -2707,8 +2675,8 @@ under the License.
             
<link>https://docs.spring.io/spring/docs/current/javadoc-api/</link>
             
<link>https://docs.spring.io/spring-security/site/docs/current/api/</link>
             <link>http://www.flowable.org/docs/javadocs/</link>
-            
<link>https://build.shibboleth.net/nexus/content/sites/site/java-opensaml/3.3.1/apidocs/</link>
-            <link>http://docs.swagger.io/swagger-core/v2.1.1/apidocs/</link>
+            
<link>https://build.shibboleth.net/nexus/content/sites/site/java-opensaml/4.0.1/apidocs/</link>
+            <link>http://docs.swagger.io/swagger-core/v2.1.3/apidocs/</link>
           </links>
         </configuration>
         <reportSets>
diff --git a/sra/pom.xml b/sra/pom.xml
index ec6e970..51a114e 100644
--- a/sra/pom.xml
+++ b/sra/pom.xml
@@ -77,8 +77,13 @@ under the License.
       <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
     </dependency>
 
+    <dependency>
+      <groupId>org.pac4j</groupId>
+      <artifactId>pac4j-saml</artifactId>
+    </dependency>
+
     <dependency> 
-      <groupId>org.springframework.session</groupId> 
+      <groupId>org.springframework.session</groupId>
       <artifactId>spring-session-core</artifactId> 
     </dependency>
 
@@ -110,6 +115,11 @@ under the License.
     </dependency>
 
     <dependency>
+      <groupId>org.gaul</groupId>
+      <artifactId>modernizer-maven-annotations</artifactId>
+    </dependency>
+
+    <dependency>
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-api</artifactId>
     </dependency>
@@ -276,6 +286,16 @@ under the License.
           <resource>
             <directory>${basedir}/src/test/resources/debug</directory>
             <filtering>true</filtering>
+            <excludes>
+              <exclude>saml.keystore.jks</exclude>
+            </excludes>
+          </resource>
+          <resource>
+            <directory>${basedir}/src/test/resources/debug</directory>
+            <filtering>false</filtering>
+            <includes>
+              <include>saml.keystore.jks</include>
+            </includes>
           </resource>
         </resources>
       </build>
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 6347edc..89646f3 100644
--- a/sra/src/main/java/org/apache/syncope/sra/SecurityConfig.java
+++ b/sra/src/main/java/org/apache/syncope/sra/SecurityConfig.java
@@ -19,15 +19,21 @@
 package org.apache.syncope.sra;
 
 import java.text.ParseException;
-import java.util.Collections;
 import java.util.Map;
 import java.util.Objects;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
 import org.apache.syncope.sra.security.CsrfRouteMatcher;
 import org.apache.syncope.sra.security.LogoutRouteMatcher;
-import org.apache.syncope.sra.security.OAuth2SecurityConfigUtils;
+import org.apache.syncope.sra.security.oauth2.OAuth2SecurityConfigUtils;
 import org.apache.syncope.sra.security.PublicRouteMatcher;
+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.pac4j.core.http.callback.NoParameterCallbackUrlResolver;
+import org.pac4j.saml.client.SAML2Client;
+import org.pac4j.saml.config.SAML2Configuration;
 import org.springframework.beans.factory.annotation.Autowired;
 import 
org.springframework.boot.actuate.autoconfigure.security.reactive.EndpointRequest;
 import 
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -39,6 +45,8 @@ import org.springframework.context.annotation.Configuration;
 import org.springframework.core.annotation.Order;
 import org.springframework.core.convert.converter.Converter;
 import org.springframework.core.env.Environment;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.http.HttpMethod;
 import 
org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
 import org.springframework.security.config.web.server.ServerHttpSecurity;
 import 
org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
@@ -57,27 +65,43 @@ import 
org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
 import org.springframework.security.web.server.SecurityWebFilterChain;
 import 
org.springframework.security.web.server.util.matcher.NegatedServerWebExchangeMatcher;
 import 
org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
+import 
org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
 import reactor.core.publisher.Mono;
 
 @EnableWebFluxSecurity
 @Configuration
 public class SecurityConfig {
 
-    private static final String AM_TYPE = "am.type";
+    public static final String AM_TYPE = "am.type";
 
     public enum AMType {
         OIDC,
         OAUTH2,
         SAML2,
-        WA
+        CAS
 
     }
 
     @Autowired
+    private ResourcePatternResolver resourceResolver;
+
+    @Autowired
     private Environment env;
 
     @Bean
     @Order(0)
+    @ConditionalOnProperty(name = AM_TYPE, havingValue = "SAML2")
+    public SecurityWebFilterChain saml2SecurityFilterChain(final 
ServerHttpSecurity http) {
+        ServerWebExchangeMatcher metadataMatcher =
+                ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, 
SAML2MetadataEndpoint.METADATA_URL);
+        return http.securityMatcher(metadataMatcher).
+                authorizeExchange().anyExchange().permitAll().
+                and().csrf().requireCsrfProtectionMatcher(new 
NegatedServerWebExchangeMatcher(metadataMatcher)).
+                and().build();
+    }
+
+    @Bean
+    @Order(1)
     public SecurityWebFilterChain actuatorSecurityFilterChain(final 
ServerHttpSecurity http) {
         ServerWebExchangeMatcher actuatorMatcher = 
EndpointRequest.toAnyEndpoint();
         return http.securityMatcher(actuatorMatcher).
@@ -118,7 +142,7 @@ public class SecurityConfig {
     @Bean
     @ConditionalOnMissingBean
     public Converter<Map<String, Object>, Map<String, Object>> 
jwtClaimSetConverter() {
-        return MappedJwtClaimSetConverter.withDefaults(Collections.emptyMap());
+        return MappedJwtClaimSetConverter.withDefaults(Map.of());
     }
 
     @Bean
@@ -183,7 +207,39 @@ public class SecurityConfig {
     }
 
     @Bean
-    @Order(1)
+    @ConditionalOnProperty(name = AM_TYPE, havingValue = "SAML2")
+    public SAML2Client saml2Client() {
+        SAML2Configuration cfg = new SAML2Configuration(
+                
resourceResolver.getResource(env.getProperty("am.saml2.keystore")),
+                env.getProperty("am.saml2.keystore.storepass"),
+                env.getProperty("am.saml2.keystore.keypass"),
+                resourceResolver.getResource(env.getProperty("am.saml2.idp")));
+        
cfg.setIdentityProviderMetadataResource(resourceResolver.getResource(env.getProperty("am.saml2.idp")));
+        cfg.setAuthnRequestBindingType(
+                
SAML2BindingType.valueOf(env.getProperty("am.saml2.sp.authnrequest.binding")).getUri());
+        cfg.setResponseBindingType(SAML2BindingType.POST.getUri());
+        cfg.setSpLogoutRequestBindingType(
+                
SAML2BindingType.valueOf(env.getProperty("am.saml2.sp.logout.request.binding")).getUri());
+        cfg.setSpLogoutResponseBindingType(
+                
SAML2BindingType.valueOf(env.getProperty("am.saml2.sp.logout.response.binding")).getUri());
+        
cfg.setServiceProviderEntityId(env.getProperty("am.saml2.sp.entityId"));
+        cfg.setWantsAssertionsSigned(true);
+        cfg.setAuthnRequestSigned(true);
+        cfg.setSpLogoutRequestSigned(true);
+        cfg.setAcceptedSkew(env.getProperty("am.saml2.sp.skew", int.class));
+
+        SAML2Client saml2Client = new SAML2Client(cfg);
+        saml2Client.setName(AMType.SAML2.name());
+        saml2Client.setCallbackUrl(env.getProperty("am.saml2.sp.entityId")
+                + 
SAML2WebSsoAuthenticationWebFilter.DEFAULT_FILTER_PROCESSES_URI);
+        saml2Client.setCallbackUrlResolver(new 
NoParameterCallbackUrlResolver());
+        saml2Client.init();
+
+        return saml2Client;
+    }
+
+    @Bean
+    @Order(2)
     @ConditionalOnProperty(name = AM_TYPE)
     public SecurityWebFilterChain routesSecurityFilterChain(
             final ServerHttpSecurity http,
@@ -208,9 +264,12 @@ public class SecurityConfig {
                 break;
 
             case SAML2:
+                SAML2Client saml2Client = saml2Client();
+                SAML2SecurityConfigUtils.forLogin(http, saml2Client, 
publicRouteMatcher);
+                SAML2SecurityConfigUtils.forLogout(builder, saml2Client, 
logoutRouteMatcher, ctx);
                 break;
 
-            case WA:
+            case CAS:
             default:
         }
 
diff --git 
a/sra/src/main/java/org/apache/syncope/sra/security/AbstractServerLogoutSuccessHandler.java
 
b/sra/src/main/java/org/apache/syncope/sra/security/AbstractServerLogoutSuccessHandler.java
new file mode 100644
index 0000000..3133d39
--- /dev/null
+++ 
b/sra/src/main/java/org/apache/syncope/sra/security/AbstractServerLogoutSuccessHandler.java
@@ -0,0 +1,77 @@
+/*
+ * 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;
+
+import java.net.URI;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.to.SRARouteTO;
+import org.apache.syncope.sra.RouteProvider;
+import 
org.apache.syncope.sra.security.web.server.DoNothingIfCommittedServerRedirectStrategy;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
+import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
+import org.springframework.context.ApplicationListener;
+import org.springframework.security.web.server.ServerRedirectStrategy;
+import org.springframework.security.web.server.WebFilterExchange;
+import 
org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler;
+
+public abstract class AbstractServerLogoutSuccessHandler
+        implements ServerLogoutSuccessHandler, 
ApplicationListener<RefreshRoutesEvent> {
+
+    private static final Map<String, Optional<URI>> CACHE = new 
ConcurrentHashMap<>();
+
+    protected final ServerRedirectStrategy redirectStrategy = new 
DoNothingIfCommittedServerRedirectStrategy();
+
+    @Autowired
+    private RouteProvider routeProvider;
+
+    @Value("${global.postLogout}")
+    private URI globalPostLogout;
+
+    @Override
+    public void onApplicationEvent(final RefreshRoutesEvent event) {
+        CACHE.clear();
+    }
+
+    protected URI getPostLogout(final WebFilterExchange exchange) {
+        URI postLogout = globalPostLogout;
+        String routeId = 
exchange.getExchange().getAttribute(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR);
+        if (StringUtils.isNotBlank(routeId)) {
+            Optional<URI> routePostLogout = 
Optional.ofNullable(CACHE.get(routeId)).orElseGet(() -> {
+                URI uri = null;
+                Optional<SRARouteTO> route = 
routeProvider.getRouteTOs().stream().
+                        filter(r -> routeId.equals(r.getKey())).findFirst();
+                if (route.isPresent()) {
+                    uri = route.get().getPostLogout();
+                }
+
+                CACHE.put(routeId, Optional.ofNullable(uri));
+                return CACHE.get(routeId);
+            });
+            if (routePostLogout.isPresent()) {
+                postLogout = routePostLogout.get();
+            }
+        }
+        return postLogout;
+    }
+}
diff --git 
a/sra/src/main/java/org/apache/syncope/sra/security/OAuth2SecurityConfigUtils.java
 
b/sra/src/main/java/org/apache/syncope/sra/security/oauth2/OAuth2SecurityConfigUtils.java
similarity index 96%
rename from 
sra/src/main/java/org/apache/syncope/sra/security/OAuth2SecurityConfigUtils.java
rename to 
sra/src/main/java/org/apache/syncope/sra/security/oauth2/OAuth2SecurityConfigUtils.java
index 5be5e52..cf6bbb4 100644
--- 
a/sra/src/main/java/org/apache/syncope/sra/security/OAuth2SecurityConfigUtils.java
+++ 
b/sra/src/main/java/org/apache/syncope/sra/security/oauth2/OAuth2SecurityConfigUtils.java
@@ -16,11 +16,13 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.sra.security;
+package org.apache.syncope.sra.security.oauth2;
 
-import java.util.Collections;
+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;
@@ -89,6 +91,7 @@ public final class OAuth2SecurityConfigUtils {
 
         OAuth2AuthorizationRequestRedirectWebFilter authRequestRedirectFilter =
                 new 
OAuth2AuthorizationRequestRedirectWebFilter(clientRegistrationRepository);
+        http.addFilterAt(authRequestRedirectFilter, 
SecurityWebFiltersOrder.HTTP_BASIC);
 
         AuthenticationWebFilter authenticationFilter =
                 new 
OAuth2LoginAuthenticationWebFilter(authenticationManager(amType), 
authorizedClientRepository);
@@ -99,15 +102,13 @@ public final class OAuth2SecurityConfigUtils {
         authenticationFilter.setAuthenticationSuccessHandler(new 
RedirectServerAuthenticationSuccessHandler());
         authenticationFilter.setAuthenticationFailureHandler((exchange, ex) -> 
Mono.error(ex));
         authenticationFilter.setSecurityContextRepository(new 
WebSessionServerSecurityContextRepository());
+        http.addFilterAt(authenticationFilter, 
SecurityWebFiltersOrder.AUTHENTICATION);
 
         MediaTypeServerWebExchangeMatcher htmlMatcher = new 
MediaTypeServerWebExchangeMatcher(MediaType.TEXT_HTML);
-        htmlMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL));
+        htmlMatcher.setIgnoredMediaTypes(Set.of(MediaType.ALL));
         ServerAuthenticationEntryPoint entrypoint =
                 new 
RedirectServerAuthenticationEntryPoint("/oauth2/authorization/" + 
amType.name());
         http.exceptionHandling().authenticationEntryPoint(new 
DelegateEntry(htmlMatcher, entrypoint).getEntryPoint());
-
-        http.addFilterAt(authRequestRedirectFilter, 
SecurityWebFiltersOrder.HTTP_BASIC);
-        http.addFilterAt(authenticationFilter, 
SecurityWebFiltersOrder.AUTHENTICATION);
     }
 
     public static void forLogout(
diff --git 
a/sra/src/main/java/org/apache/syncope/sra/security/OidcClientInitiatedServerLogoutSuccessHandler.java
 
b/sra/src/main/java/org/apache/syncope/sra/security/oauth2/OidcClientInitiatedServerLogoutSuccessHandler.java
similarity index 65%
rename from 
sra/src/main/java/org/apache/syncope/sra/security/OidcClientInitiatedServerLogoutSuccessHandler.java
rename to 
sra/src/main/java/org/apache/syncope/sra/security/oauth2/OidcClientInitiatedServerLogoutSuccessHandler.java
index 5ea8498..175ebd6 100644
--- 
a/sra/src/main/java/org/apache/syncope/sra/security/OidcClientInitiatedServerLogoutSuccessHandler.java
+++ 
b/sra/src/main/java/org/apache/syncope/sra/security/oauth2/OidcClientInitiatedServerLogoutSuccessHandler.java
@@ -16,33 +16,20 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.sra.security;
+package org.apache.syncope.sra.security.oauth2;
 
 import java.net.URI;
 import java.nio.charset.StandardCharsets;
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
 import javax.annotation.Resource;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.syncope.common.lib.to.SRARouteTO;
-import org.apache.syncope.sra.RouteProvider;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
-import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
-import org.springframework.context.ApplicationListener;
+import org.apache.syncope.sra.security.AbstractServerLogoutSuccessHandler;
 import reactor.core.publisher.Mono;
 import org.springframework.security.core.Authentication;
 import 
org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
 import 
org.springframework.security.oauth2.client.registration.ClientRegistration;
 import 
org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
 import org.springframework.security.oauth2.core.oidc.user.OidcUser;
-import org.springframework.security.web.server.DefaultServerRedirectStrategy;
-import org.springframework.security.web.server.ServerRedirectStrategy;
 import org.springframework.security.web.server.WebFilterExchange;
 import 
org.springframework.security.web.server.authentication.logout.RedirectServerLogoutSuccessHandler;
-import 
org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler;
 import org.springframework.util.Assert;
 import org.springframework.web.util.UriComponentsBuilder;
 
@@ -52,28 +39,22 @@ import org.springframework.web.util.UriComponentsBuilder;
  * @see <a 
href="https://openid.net/specs/openid-connect-session-1_0.html#RPLogout";>RP-Initiated
 Logout</a>
  * @see 
org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler
  */
-public class OidcClientInitiatedServerLogoutSuccessHandler
-        implements ServerLogoutSuccessHandler, 
ApplicationListener<RefreshRoutesEvent> {
-
-    private static final Map<String, Optional<URI>> CACHE = new 
ConcurrentHashMap<>();
-
-    private final ServerRedirectStrategy redirectStrategy = new 
DefaultServerRedirectStrategy();
-
-    private final RedirectServerLogoutSuccessHandler 
serverLogoutSuccessHandler =
-            new RedirectServerLogoutSuccessHandler();
+public class OidcClientInitiatedServerLogoutSuccessHandler extends 
AbstractServerLogoutSuccessHandler {
 
     @Resource(name = "oidcClientRegistrationRepository")
     private ReactiveClientRegistrationRepository clientRegistrationRepository;
 
-    @Autowired
-    private RouteProvider routeProvider;
-
-    @Value("${global.postLogout}")
-    private URI globalPostLogout;
+    protected final RedirectServerLogoutSuccessHandler 
serverLogoutSuccessHandler =
+            new RedirectServerLogoutSuccessHandler();
 
-    @Override
-    public void onApplicationEvent(final RefreshRoutesEvent event) {
-        CACHE.clear();
+    /**
+     * The URL to redirect to after successfully logging out when not 
originally an OIDC login
+     *
+     * @param logoutSuccessUrl the url to redirect to. Default is 
"/login?logout".
+     */
+    public void setLogoutSuccessUrl(final URI logoutSuccessUrl) {
+        Assert.notNull(logoutSuccessUrl, "logoutSuccessUrl cannot be null");
+        this.serverLogoutSuccessHandler.setLogoutSuccessUrl(logoutSuccessUrl);
     }
 
     @Override
@@ -106,24 +87,7 @@ public class OidcClientInitiatedServerLogoutSuccessHandler
         UriComponentsBuilder builder = 
UriComponentsBuilder.fromUri(endSessionEndpoint);
         builder.queryParam("id_token_hint", idToken(authentication));
 
-        URI postLogout = globalPostLogout;
-        String routeId = 
exchange.getExchange().getAttribute(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR);
-        if (StringUtils.isNotBlank(routeId)) {
-            Optional<URI> routePostLogout = 
Optional.ofNullable(CACHE.get(routeId)).orElseGet(() -> {
-                URI uri = null;
-                Optional<SRARouteTO> route = 
routeProvider.getRouteTOs().stream().
-                        filter(r -> routeId.equals(r.getKey())).findFirst();
-                if (route.isPresent()) {
-                    uri = route.get().getPostLogout();
-                }
-
-                CACHE.put(routeId, Optional.ofNullable(uri));
-                return CACHE.get(routeId);
-            });
-            if (routePostLogout.isPresent()) {
-                postLogout = routePostLogout.get();
-            }
-        }
+        URI postLogout = getPostLogout(exchange);
         builder.queryParam("post_logout_redirect_uri", postLogout);
 
         return builder.encode(StandardCharsets.UTF_8).build().toUri();
@@ -132,14 +96,4 @@ public class OidcClientInitiatedServerLogoutSuccessHandler
     private String idToken(final Authentication authentication) {
         return ((OidcUser) 
authentication.getPrincipal()).getIdToken().getTokenValue();
     }
-
-    /**
-     * The URL to redirect to after successfully logging out when not 
originally an OIDC login
-     *
-     * @param logoutSuccessUrl the url to redirect to. Default is 
"/login?logout".
-     */
-    public void setLogoutSuccessUrl(final URI logoutSuccessUrl) {
-        Assert.notNull(logoutSuccessUrl, "logoutSuccessUrl cannot be null");
-        this.serverLogoutSuccessHandler.setLogoutSuccessUrl(logoutSuccessUrl);
-    }
 }
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/ServerHttpContext.java
new file mode 100644
index 0000000..1e0b3ab
--- /dev/null
+++ 
b/sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerHttpContext.java
@@ -0,0 +1,226 @@
+/*
+ * 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.pac4j;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Optional;
+import org.apache.commons.lang3.ArrayUtils;
+import org.pac4j.core.context.Cookie;
+import org.pac4j.core.context.WebContext;
+import org.pac4j.core.context.session.SessionStore;
+import org.pac4j.core.util.CommonHelper;
+import org.springframework.http.HttpCookie;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.ResponseCookie;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.WebSession;
+import org.springframework.web.util.UriComponentsBuilder;
+
+public class ServerHttpContext implements WebContext {
+
+    private final ServerWebExchange exchange;
+
+    private ServerHttpSessionStore sessionStore;
+
+    private MultiValueMap<String, String> form;
+
+    private String body;
+
+    /**
+     * Build a WebFlux context from the current exchange and web session.
+     *
+     * @param exchange the current exchange
+     * @param webSession the current web session
+     */
+    public ServerHttpContext(final ServerWebExchange exchange, final 
WebSession webSession) {
+        this(exchange, new ServerHttpSessionStore(webSession));
+    }
+
+    /**
+     * Build a WebFlux context from the current exhange and from a session 
store.
+     *
+     * @param exchange the current exchange
+     * @param sessionStore the session store to use
+     */
+    public ServerHttpContext(
+            final ServerWebExchange exchange,
+            final ServerHttpSessionStore sessionStore) {
+
+        CommonHelper.assertNotNull("exchange", exchange);
+        CommonHelper.assertNotNull("sessionStore", sessionStore);
+        this.exchange = exchange;
+        this.sessionStore = sessionStore;
+    }
+
+    public ServerHttpSessionStore getNativeSessionStore() {
+        return this.sessionStore;
+    }
+
+    @Override
+    public SessionStore<ServerHttpContext> getSessionStore() {
+        return this.sessionStore;
+    }
+
+    @Override
+    public Optional<String> getRequestAttribute(final String name) {
+        return Optional.ofNullable(exchange.getAttribute(name));
+    }
+
+    @Override
+    public void setRequestAttribute(final String name, final Object value) {
+        exchange.getAttributes().put(name, value);
+    }
+
+    @Override
+    public Optional<String> getRequestParameter(final String name) {
+        Map<String, String[]> params = getRequestParameters();
+        if (params.containsKey(name)) {
+            String[] values = params.get(name);
+            if (!ArrayUtils.isEmpty(values)) {
+                return Optional.of(values[0]);
+            }
+        }
+        return Optional.empty();
+    }
+
+    public ServerHttpContext setForm(final MultiValueMap<String, String> form) 
{
+        this.form = form;
+        return this;
+    }
+
+    @Override
+    public Map<String, String[]> getRequestParameters() {
+        Map<String, String[]> params = new HashMap<>();
+
+        this.exchange.getRequest().getQueryParams().
+                forEach((key, value) -> params.put(key, new String[] { 
value.toString() }));
+
+        if (this.form != null) {
+            form.forEach((key, values) -> params.put(key, values.toArray(new 
String[0])));
+        }
+
+        return params;
+    }
+
+    @Override
+    public Optional<String> getRequestHeader(final String name) {
+        return 
Optional.ofNullable(exchange.getRequest().getHeaders().getFirst(name));
+    }
+
+    @Override
+    public String getRequestMethod() {
+        return this.exchange.getRequest().getMethodValue();
+    }
+
+    @Override
+    public String getRemoteAddr() {
+        return this.exchange.getRequest().getRemoteAddress().getHostString();
+    }
+
+    /**
+     * Return the native exchange.
+     *
+     * @return the native exchange
+     */
+    public ServerWebExchange getNative() {
+        return this.exchange;
+    }
+
+    @Override
+    public void setResponseHeader(final String name, final String value) {
+        this.exchange.getResponse().getHeaders().set(name, value);
+    }
+
+    @Override
+    public void setResponseContentType(final String content) {
+        this.exchange.getResponse().getHeaders().set(HttpHeaders.CONTENT_TYPE, 
content);
+    }
+
+    @Override
+    public String getProtocol() {
+        return isSecure() ? "https" : "http";
+    }
+
+    @Override
+    public String getServerName() {
+        return 
UriComponentsBuilder.fromHttpRequest(exchange.getRequest()).build().getHost();
+    }
+
+    @Override
+    public int getServerPort() {
+        return 
UriComponentsBuilder.fromHttpRequest(exchange.getRequest()).build().getPort();
+    }
+
+    @Override
+    public String getScheme() {
+        return 
UriComponentsBuilder.fromHttpRequest(exchange.getRequest()).build().getScheme();
+    }
+
+    @Override
+    public boolean isSecure() {
+        return this.exchange.getRequest().getSslInfo() != null;
+    }
+
+    @Override
+    public String getFullRequestURL() {
+        return 
UriComponentsBuilder.fromHttpRequest(exchange.getRequest()).build().toUriString();
+    }
+
+    @Override
+    public Collection<Cookie> getRequestCookies() {
+        MultiValueMap<String, HttpCookie> cookies = 
this.exchange.getRequest().getCookies();
+
+        Collection<Cookie> pac4jCookies = new LinkedHashSet<>();
+        cookies.toSingleValueMap().values().forEach(c -> {
+            Cookie cookie = new Cookie(c.getName(), c.getValue());
+            pac4jCookies.add(cookie);
+        });
+        return pac4jCookies;
+    }
+
+    @Override
+    public void addResponseCookie(final Cookie cookie) {
+        ResponseCookie.ResponseCookieBuilder c = 
ResponseCookie.from(cookie.getName(), cookie.getValue());
+        c.secure(cookie.isSecure());
+        c.path(cookie.getPath());
+        c.maxAge(cookie.getMaxAge());
+        c.httpOnly(cookie.isHttpOnly());
+        c.domain(cookie.getDomain());
+        this.exchange.getResponse().addCookie(c.build());
+    }
+
+    @Override
+    public String getPath() {
+        return exchange.getRequest().getPath().value();
+    }
+
+    public ServerHttpContext setBody(final String body) {
+        this.body = body;
+        return this;
+    }
+
+    @Override
+    public String getRequestContent() {
+        return body;
+    }
+}
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/ServerHttpSessionStore.java
new file mode 100644
index 0000000..622a92c
--- /dev/null
+++ 
b/sra/src/main/java/org/apache/syncope/sra/security/pac4j/ServerHttpSessionStore.java
@@ -0,0 +1,68 @@
+/*
+ * 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.pac4j;
+
+import java.util.Optional;
+import org.pac4j.core.context.session.SessionStore;
+import org.springframework.web.server.WebSession;
+
+public class ServerHttpSessionStore implements SessionStore<ServerHttpContext> 
{
+
+    private final WebSession webSession;
+
+    public ServerHttpSessionStore(final WebSession webSession) {
+        this.webSession = webSession;
+    }
+
+    @Override
+    public String getOrCreateSessionId(final ServerHttpContext context) {
+        return this.webSession.getId();
+    }
+
+    @Override
+    public Optional<Object> get(final ServerHttpContext context, final String 
key) {
+        return Optional.ofNullable(this.webSession.getAttribute(key));
+    }
+
+    @Override
+    public void set(final ServerHttpContext context, final String key, final 
Object value) {
+    }
+
+    @Override
+    public boolean destroySession(final ServerHttpContext context) {
+        return false;
+    }
+
+    @Override
+    public Optional<WebSession> getTrackableSession(final ServerHttpContext 
context) {
+        return Optional.ofNullable(this.webSession);
+    }
+
+    @Override
+    public Optional<SessionStore<ServerHttpContext>> buildFromTrackableSession(
+            final ServerHttpContext context, final Object trackableSession) {
+
+        return Optional.empty();
+    }
+
+    @Override
+    public boolean renewSession(final ServerHttpContext 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
new file mode 100644
index 0000000..29d0fa5
--- /dev/null
+++ 
b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2AnonymousWebFilter.java
@@ -0,0 +1,57 @@
+/*
+ * 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.saml2;
+
+import java.net.URI;
+import org.apache.syncope.sra.security.PublicRouteMatcher;
+import org.springframework.http.HttpStatus;
+import 
org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
+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 SAML2AnonymousWebFilter implements WebFilter {
+
+    public static final String INITIAL_REQUEST_URI = "INITIAL_REQUEST_URI";
+
+    private final PublicRouteMatcher publicRouteMatcher;
+
+    public SAML2AnonymousWebFilter(final PublicRouteMatcher 
publicRouteMatcher) {
+        this.publicRouteMatcher = publicRouteMatcher;
+    }
+
+    @Override
+    public Mono<Void> filter(final ServerWebExchange exchange, final 
WebFilterChain chain) {
+        return publicRouteMatcher.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(session -> {
+                    session.getAttributes().put(INITIAL_REQUEST_URI, 
exchange.getRequest().getURI());
+
+                    exchange.getResponse().setStatusCode(HttpStatus.SEE_OTHER);
+                    exchange.getResponse().getHeaders().
+                            
setLocation(URI.create(SAML2WebSsoAuthenticationRequestWebFilter.AUTHENTICATE_URL));
+                    return exchange.getResponse().setComplete();
+                });
+    }
+}
diff --git 
a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2AuthenticationToken.java
 
b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2AuthenticationToken.java
new file mode 100644
index 0000000..74e712e
--- /dev/null
+++ 
b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2AuthenticationToken.java
@@ -0,0 +1,49 @@
+/*
+ * 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.saml2;
+
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.StringUtils;
+import org.pac4j.saml.credentials.SAML2Credentials;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+
+public class SAML2AuthenticationToken extends AbstractAuthenticationToken {
+
+    private static final long serialVersionUID = 8322987617416135717L;
+
+    private final SAML2Credentials credentials;
+
+    public SAML2AuthenticationToken(final SAML2Credentials credentials) {
+        super(credentials.getUserProfile().getRoles().stream().
+                map(SimpleGrantedAuthority::new).collect(Collectors.toSet()));
+        this.credentials = credentials;
+        this.setAuthenticated(true);
+    }
+
+    @Override
+    public Object getCredentials() {
+        return StringUtils.EMPTY;
+    }
+
+    @Override
+    public SAML2Credentials getPrincipal() {
+        return credentials;
+    }
+}
diff --git 
a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2BindingType.java 
b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2BindingType.java
new file mode 100644
index 0000000..bf7895d
--- /dev/null
+++ 
b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2BindingType.java
@@ -0,0 +1,36 @@
+/*
+ * 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.saml2;
+
+import org.opensaml.saml.common.xml.SAMLConstants;
+
+public enum SAML2BindingType {
+    POST(SAMLConstants.SAML2_POST_BINDING_URI),
+    REDIRECT(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
+
+    private final String uri;
+
+    SAML2BindingType(final String uri) {
+        this.uri = uri;
+    }
+
+    public String getUri() {
+        return uri;
+    }
+}
diff --git 
a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2MetadataEndpoint.java
 
b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2MetadataEndpoint.java
new file mode 100644
index 0000000..229d232
--- /dev/null
+++ 
b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2MetadataEndpoint.java
@@ -0,0 +1,54 @@
+/*
+ * 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.saml2;
+
+import com.google.common.net.HttpHeaders;
+import org.apache.syncope.sra.SecurityConfig;
+import org.pac4j.saml.client.SAML2Client;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+import reactor.core.publisher.Mono;
+
+@RestController
+@RequestMapping(SAML2MetadataEndpoint.METADATA_URL)
+@ConditionalOnProperty(name = SecurityConfig.AM_TYPE, havingValue = "SAML2")
+public class SAML2MetadataEndpoint {
+
+    public static final String METADATA_URL = "/saml2/metadata";
+
+    private final SAML2Client saml2Client;
+
+    public SAML2MetadataEndpoint(final SAML2Client saml2Client) {
+        this.saml2Client = saml2Client;
+    }
+
+    @GetMapping(produces = { MediaType.APPLICATION_XML_VALUE })
+    @ResponseBody
+    public Mono<ResponseEntity<String>> metadata(final ServerHttpRequest 
request) {
+        return Mono.just(ResponseEntity.ok().
+                header(HttpHeaders.CONTENT_TYPE, 
MediaType.APPLICATION_XML_VALUE).
+                
body(saml2Client.getServiceProviderMetadataResolver().getMetadata()));
+    }
+}
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
new file mode 100644
index 0000000..fd2a924
--- /dev/null
+++ 
b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2RequestGenerator.java
@@ -0,0 +1,65 @@
+/*
+ * 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.saml2;
+
+import org.apache.syncope.sra.security.pac4j.ServerHttpContext;
+import java.net.URI;
+import org.pac4j.core.exception.http.RedirectionAction;
+import org.pac4j.core.exception.http.WithContentAction;
+import org.pac4j.core.exception.http.WithLocationAction;
+import org.pac4j.saml.client.SAML2Client;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import reactor.core.publisher.Mono;
+
+abstract class SAML2RequestGenerator {
+
+    protected final SAML2Client saml2Client;
+
+    protected SAML2RequestGenerator(final SAML2Client saml2Client) {
+        this.saml2Client = saml2Client;
+    }
+
+    protected Mono<Void> handle(
+            final RedirectionAction action,
+            final ServerHttpContext shc) {
+
+        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();
+        } else if (action instanceof WithContentAction) {
+            WithContentAction withContentAction = (WithContentAction) action;
+            String content = withContentAction.getContent();
+
+            if (content == null) {
+                throw new IllegalArgumentException("No content set for POST 
AuthnRequest");
+            }
+
+            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())));
+            });
+        } 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
new file mode 100644
index 0000000..4b71109
--- /dev/null
+++ 
b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2SecurityConfigUtils.java
@@ -0,0 +1,96 @@
+/*
+ * 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.saml2;
+
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ConfigurableApplicationContext;
+import 
org.springframework.security.authentication.ReactiveAuthenticationManager;
+import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
+import org.springframework.security.config.web.server.ServerHttpSecurity;
+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 {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(SAML2SecurityConfigUtils.class);
+
+    private static ReactiveAuthenticationManager authenticationManager() {
+        return authentication -> Mono.just(authentication).
+                filter(Authentication::isAuthenticated);
+    }
+
+    public static void forLogin(
+            final ServerHttpSecurity http,
+            final SAML2Client saml2Client,
+            final PublicRouteMatcher publicRouteMatcher) {
+
+        ReactiveAuthenticationManager authenticationManager = 
authenticationManager();
+
+        SAML2WebSsoAuthenticationRequestWebFilter authRequestFilter =
+                new SAML2WebSsoAuthenticationRequestWebFilter(saml2Client);
+        http.addFilterAt(authRequestFilter, 
SecurityWebFiltersOrder.HTTP_BASIC);
+
+        AuthenticationWebFilter authenticationFilter =
+                new SAML2WebSsoAuthenticationWebFilter(authenticationManager, 
saml2Client);
+        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 LogoutRouteMatcher logoutRouteMatcher,
+            final ConfigurableApplicationContext ctx) {
+
+        LogoutWebFilter logoutWebFilter = new LogoutWebFilter();
+        logoutWebFilter.setRequiresLogoutMatcher(logoutRouteMatcher);
+
+        SAML2ServerLogoutHandler logoutHandler = new 
SAML2ServerLogoutHandler(saml2Client);
+        logoutWebFilter.setLogoutHandler(logoutHandler);
+
+        try {
+            SAML2ServerLogoutSuccessHandler handler = 
ApplicationContextUtils.getOrCreateBean(ctx,
+                    SAML2ServerLogoutSuccessHandler.class.getName(),
+                    SAML2ServerLogoutSuccessHandler.class);
+            logoutWebFilter.setLogoutSuccessHandler(handler);
+        } catch (ClassNotFoundException e) {
+            LOG.error("While creating instance of {}",
+                    SAML2ServerLogoutSuccessHandler.class.getName(), e);
+        }
+
+        builder.and().addFilterAt(logoutWebFilter, 
SecurityWebFiltersOrder.LOGOUT);
+    }
+
+    private SAML2SecurityConfigUtils() {
+        // private constructor for static utility class
+    }
+}
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
new file mode 100644
index 0000000..0bcba31
--- /dev/null
+++ 
b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2ServerLogoutHandler.java
@@ -0,0 +1,56 @@
+/*
+ * 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.saml2;
+
+import org.apache.syncope.sra.security.pac4j.ServerHttpContext;
+import org.pac4j.saml.client.SAML2Client;
+import org.pac4j.saml.credentials.SAML2Credentials;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+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 SAML2ServerLogoutHandler extends SAML2RequestGenerator implements 
ServerLogoutHandler {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(SAML2ServerLogoutHandler.class);
+
+    public SAML2ServerLogoutHandler(final SAML2Client saml2Client) {
+        super(saml2Client);
+    }
+
+    @Override
+    public Mono<Void> logout(final WebFilterExchange exchange, final 
Authentication authentication) {
+        return exchange.getExchange().getSession().
+                flatMap(session -> {
+                    SAML2Credentials credentials = (SAML2Credentials) 
authentication.getPrincipal();
+
+                    LOG.debug("Creating SAML2 SP Logout Request for IDP[{}] 
and Profile[{}]",
+                            saml2Client.getIdentityProviderResolvedEntityId(), 
credentials.getUserProfile());
+
+                    ServerHttpContext shc = new 
ServerHttpContext(exchange.getExchange(), session);
+
+                    return session.invalidate().then(
+                            saml2Client.getLogoutAction(shc, 
credentials.getUserProfile(), null).
+                                    map(action -> handle(action, shc)).
+                                    orElseThrow(() -> new 
IllegalStateException("No action generated")));
+                }).onErrorResume(Mono::error);
+    }
+}
diff --git 
a/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2ServerLogoutSuccessHandler.java
 
b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2ServerLogoutSuccessHandler.java
new file mode 100644
index 0000000..3557eb5
--- /dev/null
+++ 
b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2ServerLogoutSuccessHandler.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.saml2;
+
+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 SAML2ServerLogoutSuccessHandler 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/saml2/SAML2WebSsoAuthenticationRequestWebFilter.java
 
b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2WebSsoAuthenticationRequestWebFilter.java
new file mode 100644
index 0000000..e57faad
--- /dev/null
+++ 
b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2WebSsoAuthenticationRequestWebFilter.java
@@ -0,0 +1,67 @@
+/*
+ * 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.saml2;
+
+import org.apache.syncope.sra.security.pac4j.ServerHttpContext;
+import org.pac4j.saml.client.SAML2Client;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import 
org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
+import 
org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
+import org.springframework.util.Assert;
+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 SAML2WebSsoAuthenticationRequestWebFilter extends 
SAML2RequestGenerator implements WebFilter {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(SAML2WebSsoAuthenticationRequestWebFilter.class);
+
+    public static final String AUTHENTICATE_URL = "/saml2/authenticate";
+
+    private ServerWebExchangeMatcher redirectMatcher = 
ServerWebExchangeMatchers.pathMatchers(AUTHENTICATE_URL);
+
+    public SAML2WebSsoAuthenticationRequestWebFilter(final SAML2Client 
saml2Client) {
+        super(saml2Client);
+    }
+
+    public void setRedirectMatcher(final ServerWebExchangeMatcher 
redirectMatcher) {
+        Assert.notNull(redirectMatcher, "redirectMatcher cannot be null");
+        this.redirectMatcher = redirectMatcher;
+    }
+
+    @Override
+    public Mono<Void> filter(final ServerWebExchange exchange, final 
WebFilterChain chain) {
+        return redirectMatcher.matches(exchange).
+                filter(matchResult -> matchResult.isMatch()).
+                switchIfEmpty(chain.filter(exchange).then(Mono.empty())).
+                flatMap(matchResult -> exchange.getSession()).
+                flatMap(session -> {
+                    LOG.debug("Creating SAML2 SP Authentication Request for 
IDP[{}]",
+                            saml2Client.getIdentityProviderResolvedEntityId());
+
+                    ServerHttpContext shc = new ServerHttpContext(exchange, 
session);
+
+                    return saml2Client.getRedirectionAction(shc).
+                            map(action -> handle(action, shc)).
+                            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
new file mode 100644
index 0000000..79ef94b
--- /dev/null
+++ 
b/sra/src/main/java/org/apache/syncope/sra/security/saml2/SAML2WebSsoAuthenticationWebFilter.java
@@ -0,0 +1,117 @@
+/*
+ * 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.saml2;
+
+import org.apache.syncope.sra.security.pac4j.ServerHttpContext;
+import java.net.URI;
+import 
org.apache.syncope.sra.security.web.server.DoNothingIfCommittedServerRedirectStrategy;
+import org.pac4j.core.util.Pac4jConstants;
+import org.pac4j.saml.client.SAML2Client;
+import org.pac4j.saml.credentials.SAML2Credentials;
+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.ServerWebExchangeMatcher;
+import 
org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.WebFilterChain;
+import reactor.core.publisher.Mono;
+
+public class SAML2WebSsoAuthenticationWebFilter extends 
AuthenticationWebFilter {
+
+    public static final String DEFAULT_FILTER_PROCESSES_URI = 
"/login/saml2/sso";
+
+    private final SAML2Client saml2Client;
+
+    private ServerWebExchangeMatcher matcher = new AndServerWebExchangeMatcher(
+            
ServerWebExchangeMatchers.pathMatchers(DEFAULT_FILTER_PROCESSES_URI),
+            exchange -> exchange.getRequest().getQueryParams().
+                    containsKey(Pac4jConstants.LOGOUT_ENDPOINT_PARAMETER)
+            ? ServerWebExchangeMatcher.MatchResult.notMatch()
+            : ServerWebExchangeMatcher.MatchResult.match());
+
+    public SAML2WebSsoAuthenticationWebFilter(
+            final ReactiveAuthenticationManager authenticationManager,
+            final SAML2Client saml2Client) {
+
+        super(authenticationManager);
+
+        this.saml2Client = saml2Client;
+
+        setRequiresAuthenticationMatcher(matchSamlResponse());
+
+        setServerAuthenticationConverter(convertSamlResponse());
+
+        setAuthenticationSuccessHandler(redirectToInitialRequestURI());
+    }
+
+    public void setMatcher(final ServerWebExchangeMatcher matcher) {
+        this.matcher = matcher;
+    }
+
+    @Override
+    public Mono<Void> filter(final ServerWebExchange exchange, final 
WebFilterChain chain) {
+        return super.filter(exchange, 
chain).then(Mono.defer(exchange.getResponse()::setComplete));
+    }
+
+    private ServerWebExchangeMatcher matchSamlResponse() {
+        return exchange -> exchange.getFormData().
+                filter(form -> form.containsKey("SAMLResponse")).
+                flatMap(form -> ServerWebExchangeMatcher.MatchResult.match()).
+                switchIfEmpty(ServerWebExchangeMatcher.MatchResult.notMatch());
+    }
+
+    private ServerAuthenticationConverter convertSamlResponse() {
+        return exchange -> exchange.getFormData().
+                flatMap(form -> this.matcher.matches(exchange).
+                flatMap(matchResult -> exchange.getSession()).
+                flatMap(session -> {
+                    ServerHttpContext shc = new ServerHttpContext(exchange, 
session).setForm(form);
+
+                    SAML2Credentials credentials = 
saml2Client.getCredentialsExtractor().extract(shc).
+                            orElseThrow(() -> new IllegalStateException("No 
AuthnResponse found"));
+
+                    saml2Client.getAuthenticator().validate(credentials, shc);
+
+                    return Mono.just(new 
SAML2AuthenticationToken(credentials));
+                }));
+    }
+
+    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(),
+                        (URI) 
session.getRequiredAttribute(SAML2AnonymousWebFilter.INITIAL_REQUEST_URI)));
+            }
+        };
+    }
+}
diff --git 
a/sra/src/main/java/org/apache/syncope/sra/security/web/server/DoNothingIfCommittedServerRedirectStrategy.java
 
b/sra/src/main/java/org/apache/syncope/sra/security/web/server/DoNothingIfCommittedServerRedirectStrategy.java
new file mode 100644
index 0000000..c78dc15
--- /dev/null
+++ 
b/sra/src/main/java/org/apache/syncope/sra/security/web/server/DoNothingIfCommittedServerRedirectStrategy.java
@@ -0,0 +1,34 @@
+/*
+ * 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.web.server;
+
+import java.net.URI;
+import org.springframework.security.web.server.DefaultServerRedirectStrategy;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+public class DoNothingIfCommittedServerRedirectStrategy extends 
DefaultServerRedirectStrategy {
+
+    @Override
+    public Mono<Void> sendRedirect(final ServerWebExchange exchange, final URI 
location) {
+        return exchange.getResponse().isCommitted()
+                ? Mono.empty()
+                : super.sendRedirect(exchange, location);
+    }
+}
diff --git a/sra/src/test/resources/debug/application-debug.properties 
b/sra/src/test/resources/debug/application-debug.properties
index 5958ee8..5af528f 100644
--- a/sra/src/test/resources/debug/application-debug.properties
+++ b/sra/src/test/resources/debug/application-debug.properties
@@ -19,15 +19,27 @@
 #am.oidc.client.id=oidcTestClientId
 #am.oidc.client.secret=oidcTestClientSecret
 
-am.type=OAUTH2
-am.oauth2.tokenUri=http://localhost:9080/syncope-wa/oauth2.0/accessToken
-am.oauth2.authorizationUri=http://localhost:9080/syncope-wa/oauth2.0/authorize
-am.oauth2.userInfoUri=http://localhost:9080/syncope-wa/oauth2.0/profile
-am.oauth2.userNameAttributeName=id
-am.oauth2.scopes=
-am.oauth2.jwkSetUri=
-am.oauth2.issuer=http://localhost:9080/syncope-wa
-am.oauth2.client.id=oauth2TestClientId
-am.oauth2.client.secret=oauth2TestClientSecret
+#am.type=OAUTH2
+#am.oauth2.tokenUri=http://localhost:9080/syncope-wa/oauth2.0/accessToken
+#am.oauth2.authorizationUri=http://localhost:9080/syncope-wa/oauth2.0/authorize
+#am.oauth2.userInfoUri=http://localhost:9080/syncope-wa/oauth2.0/profile
+#am.oauth2.userNameAttributeName=id
+#am.oauth2.scopes=
+#am.oauth2.jwkSetUri=
+#am.oauth2.issuer=http://localhost:9080/syncope-wa
+#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
 
 global.postLogout=http://localhost:8080/logout
diff --git a/sra/src/test/resources/debug/saml.keystore.jks 
b/sra/src/test/resources/debug/saml.keystore.jks
new file mode 100644
index 0000000..e2a5238
Binary files /dev/null and b/sra/src/test/resources/debug/saml.keystore.jks 
differ
diff --git a/wa/pom.xml b/wa/pom.xml
index b0c22c7..0dd676a 100644
--- a/wa/pom.xml
+++ b/wa/pom.xml
@@ -33,8 +33,6 @@ under the License.
   <packaging>pom</packaging>
   
   <properties>
-    <opensaml.version>4.0.0</opensaml.version>
-
     <rootpom.basedir>${basedir}/..</rootpom.basedir>
   </properties>
 
@@ -77,13 +75,6 @@ under the License.
     </profile>
   </profiles>
 
-  <repositories>
-    <repository>
-      <id>shibboleth</id>
-      <url>https://build.shibboleth.net/nexus/content/groups/public</url>
-    </repository>
-  </repositories>
-
   <modules>
     <module>bootstrap</module>
     <module>starter</module>
diff --git 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/saml/idp/metadata/RestfulSamlIdPMetadataGenerator.java
 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/saml/idp/metadata/RestfulSamlIdPMetadataGenerator.java
index 9719676..6323bf0 100644
--- 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/saml/idp/metadata/RestfulSamlIdPMetadataGenerator.java
+++ 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/saml/idp/metadata/RestfulSamlIdPMetadataGenerator.java
@@ -37,8 +37,6 @@ public class RestfulSamlIdPMetadataGenerator extends 
BaseSamlIdPMetadataGenerato
 
     private static final Logger LOG = 
LoggerFactory.getLogger(RestfulSamlIdPMetadataGenerator.class);
 
-    public static final String DEFAULT_APPLIES_FOR = "Syncope";
-
     private final WARestClient restClient;
 
     public RestfulSamlIdPMetadataGenerator(
@@ -55,7 +53,7 @@ public class RestfulSamlIdPMetadataGenerator extends 
BaseSamlIdPMetadataGenerato
             final Optional<SamlRegisteredService> registeredService) {
 
         LOG.info("Generating new SAML2 IdP metadata document");
-        doc.setAppliesTo(DEFAULT_APPLIES_FOR);
+        doc.setAppliesTo(WASAML2IdPMetadataService.DEFAULT_OWNER);
         SAML2IdPMetadataTO metadataTO = new SAML2IdPMetadataTO.Builder().
                 metadata(doc.getMetadata()).
                 encryptionKey(doc.getEncryptionKey()).
@@ -69,8 +67,8 @@ public class RestfulSamlIdPMetadataGenerator extends 
BaseSamlIdPMetadataGenerato
         Response response = null;
         try {
             response = 
client.getService(WASAML2IdPMetadataService.class).set(metadataTO);
-        } catch (Exception ex) {
-            LOG.warn("While generating SAML2 IdP metadata document", ex);
+        } catch (Exception e) {
+            LOG.warn("While generating SAML2 IdP metadata document", e);
         }
 
         return response != null && 
HttpStatus.valueOf(response.getStatus()).is2xxSuccessful() ? doc : null;
@@ -93,5 +91,4 @@ public class RestfulSamlIdPMetadataGenerator extends 
BaseSamlIdPMetadataGenerato
         }
         return restClient.getSyncopeClient();
     }
-
 }
diff --git 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/saml/idp/metadata/RestfulSamlIdPMetadataLocator.java
 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/saml/idp/metadata/RestfulSamlIdPMetadataLocator.java
index f00bba5..0e12872 100644
--- 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/saml/idp/metadata/RestfulSamlIdPMetadataLocator.java
+++ 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/saml/idp/metadata/RestfulSamlIdPMetadataLocator.java
@@ -46,24 +46,13 @@ public class RestfulSamlIdPMetadataLocator extends 
AbstractSamlIdPMetadataLocato
         this.restClient = restClient;
     }
 
-    private static String getAppliesToFor(final 
Optional<SamlRegisteredService> result) {
-        if (result.isPresent()) {
-            SamlRegisteredService registeredService = result.get();
-            return registeredService.getName() + '-' + 
registeredService.getId();
-        }
-        return RestfulSamlIdPMetadataGenerator.DEFAULT_APPLIES_FOR;
-    }
-
     @Override
     public SamlIdPMetadataDocument fetchInternal(final 
Optional<SamlRegisteredService> registeredService) {
         try {
             LOG.info("Locating SAML2 IdP metadata document");
-            SAML2IdPMetadataTO saml2IdPMetadataTO = 
getSyncopeClient().getService(WASAML2IdPMetadataService.class).
-                getByOwner(getAppliesToFor(registeredService));
 
-            if (saml2IdPMetadataTO == null) {
-                LOG.warn("No SAML2 IdP metadata document obtained from core");
-            } else {
+            SAML2IdPMetadataTO saml2IdPMetadataTO = 
fetchFromCore(registeredService);
+            if (saml2IdPMetadataTO != null) {
                 SamlIdPMetadataDocument document = new 
SamlIdPMetadataDocument();
                 document.setMetadata(saml2IdPMetadataTO.getMetadata());
                 
document.setEncryptionCertificate(saml2IdPMetadataTO.getEncryptionCertificate());
@@ -75,18 +64,36 @@ public class RestfulSamlIdPMetadataLocator extends 
AbstractSamlIdPMetadataLocato
                     LOG.debug("Found SAML2 IdP metadata document: {}", 
document.getId());
                     return document;
                 }
-                LOG.warn("Not a valid SAML2 IdP metadata document");
             }
 
+            LOG.warn("Not a valid SAML2 IdP metadata document");
             return null;
-        } catch (SyncopeClientException ex) {
-            if (ex.getType() == ClientExceptionType.NotFound) {
-                LOG.debug("No SAML2 IdP metadata document is available");
-            }
+        } catch (SyncopeClientException e) {
+            LOG.error("While fetching SAML2 IdP metadata", e);
         }
+
         return null;
     }
 
+    private SAML2IdPMetadataTO fetchFromCore(final 
Optional<SamlRegisteredService> registeredService) {
+        SAML2IdPMetadataTO result = null;
+
+        String appliesToFor = 
registeredService.map(SamlRegisteredService::getName).
+                orElse(WASAML2IdPMetadataService.DEFAULT_OWNER);
+        try {
+            result = 
getSyncopeClient().getService(WASAML2IdPMetadataService.class).getByOwner(appliesToFor);
+        } catch (SyncopeClientException e) {
+            if (e.getType() == ClientExceptionType.NotFound && 
registeredService.isPresent()) {
+                result = 
getSyncopeClient().getService(WASAML2IdPMetadataService.class).
+                        getByOwner(WASAML2IdPMetadataService.DEFAULT_OWNER);
+            } else {
+                throw e;
+            }
+        }
+
+        return result;
+    }
+
     private SyncopeClient getSyncopeClient() {
         if (!WARestClient.isReady()) {
             LOG.info("Syncope client is not yet ready");
diff --git a/wa/starter/src/main/resources/wa.properties 
b/wa/starter/src/main/resources/wa.properties
index eb69a87..01534fd 100644
--- a/wa/starter/src/main/resources/wa.properties
+++ b/wa/starter/src/main/resources/wa.properties
@@ -27,9 +27,13 @@ cas.server.name=http://localhost:8080
 cas.server.prefix=${cas.server.name}/syncope-wa
 cas.server.scope=syncope.org
 
+cas.tgc.secure=false
 cas.logout.follow-service-redirects=true
 
-cas.authn.saml-idp.entity-id=https://syncope.apache.org/saml
+cas.authn.saml-idp.entity-id=http://localhost:8080/saml
+cas.authn.saml-idp.metadata.metadata-backup-location=file:${conf.directory}/saml
+
+cas.authn.oidc.issuer=http://localhost:8080/syncope-wa/oidc/
 
 # Disable access to the login endpoint
 # if no target application is specified.
diff --git a/wa/starter/src/test/resources/debug/wa.properties 
b/wa/starter/src/test/resources/debug/wa.properties
index c44903d..29f53c3 100644
--- a/wa/starter/src/test/resources/debug/wa.properties
+++ b/wa/starter/src/test/resources/debug/wa.properties
@@ -31,7 +31,8 @@ cas.authn.syncope.url=http://localhost:9080/syncope/rest/
 cas.tgc.secure=false
 cas.logout.follow-service-redirects=true
 
-cas.authn.saml-idp.entity-id=https://syncope.apache.org/saml
+cas.authn.saml-idp.entity-id=http://localhost:8080/saml
+cas.authn.saml-idp.metadata.metadata-backup-location=file:${conf.directory}/saml
 
 cas.authn.oidc.issuer=http://localhost:8080/syncope-wa/oidc/
 

Reply via email to