You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2019/05/30 10:05:32 UTC

[syncope] branch master updated (b239e9f -> 23cc100)

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

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


    from b239e9f  Upgrading Swagger UI
     new 0486ab6  Upgrading Elasticsearch
     new 23cc100  [SYNCOPE-1455] SRA now working with routes defined in Core

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 archetype/pom.xml                                  |  14 +-
 .../META-INF/maven/archetype-metadata.xml          |  20 +
 .../resources/archetype-resources/enduser/pom.xml  | 177 --------
 .../main/resources/archetype-resources/fit/pom.xml | 275 ++++++++++++
 .../resources/archetype-resources/sra}/pom.xml     |  68 +--
 .../sra/src/test}/resources/keymaster.properties   |   0
 archetype/src/main/resources/meta-pom.xml          |   8 +
 .../client/lib/SyncopeClientFactoryBean.java       |   6 +-
 .../syncope/common/lib/to/GatewayRouteTO.java      |  26 +-
 .../syncope/common/lib/types/AMEntitlement.java    |   4 -
 .../syncope/common/lib/types/FilterFactory.java    |   7 +-
 ...{GatewayFilter.java => GatewayRouteFilter.java} |  20 +-
 ...ayPredicate.java => GatewayRoutePredicate.java} |  37 +-
 .../syncope/common/lib/types/PredicateCond.java    |   3 +-
 .../syncope/common/lib/types/PredicateFactory.java |   1 -
 .../rest/api/service/GatewayRouteService.java      |  12 -
 .../zookeper/ZookeeperKeymasterClientContext.java  |  38 +-
 .../syncope/core/logic/GatewayRouteLogic.java      |  41 +-
 .../rest/cxf/service/GatewayRouteServiceImpl.java  |   7 +-
 .../logic/init/IdMImplementationTypeLoader.java    |   3 +-
 .../init/ClassPathScanImplementationLookup.java    |   3 +-
 .../logic/init/IdRepoImplementationTypeLoader.java |   3 +-
 .../core/rest/cxf/RestServiceExceptionMapper.java  |   7 +-
 .../core/persistence/api/entity/GatewayRoute.java  |  16 +-
 .../core/persistence/jpa/RuntimeDomainLoader.java  |   4 +-
 .../core/persistence/jpa/StartupDomainLoader.java  |   2 +-
 .../persistence/jpa/entity/JPAGatewayRoute.java    |  29 +-
 .../persistence/jpa/inner/GatewayRouteTest.java    |   8 +-
 .../java/data/GatewayRouteDataBinderImpl.java      |  19 +-
 .../syncope/core/starter/SyncopeCoreStartup.java   |   2 +-
 docker/sra/pom.xml                                 |  36 +-
 docker/sra/src/main/resources/Dockerfile           |   4 +-
 .../sra}/src/main/resources/application.properties |   6 +-
 .../src/main/resources/keymaster.properties        |   0
 docker/sra/src/main/resources/log4j2.xml           |  16 +-
 .../sra}/src/main/resources/sra.properties         |   2 -
 docker/sra/src/main/resources/startup.sh           |   3 +-
 ...ompose-zookeeper.yml => docker-compose-all.yml} |  19 +-
 fit/console-reference/pom.xml                      |   3 +-
 fit/core-reference/pom.xml                         |   2 +-
 .../syncope/fit/core/GatewayRouteITCase.java       |  52 ++-
 fit/enduser-reference/pom.xml                      |   3 +-
 pom.xml                                            |  45 +-
 sra/pom.xml                                        | 188 ++++++--
 .../syncope/sra/CustomGatewayFilterFactory.java    |  40 +-
 .../syncope/sra/CustomRoutePredicateFactory.java   |  56 +++
 .../apache/syncope/sra/ManagementController.java   |  93 ++++
 .../java/org/apache/syncope/sra/RouteProvider.java | 472 +++++++++++++++++++++
 .../org/apache/syncope/sra/RouteRefresher.java     |  21 +-
 .../apache/syncope/sra/SyncopeSRAApplication.java  |  49 ++-
 .../org/apache/syncope/sra/SyncopeSRAShutdown.java |   2 +
 .../org/apache/syncope/sra/SyncopeSRAStartup.java  |   2 +
 sra/src/main/resources/application.properties      |   4 -
 sra/src/main/resources/log4j2.xml                  |   9 +-
 sra/src/main/resources/sra.properties              |   2 -
 .../BodyPropertyAddingGatewayFilterFactory.java    | 201 +++++++++
 .../BodyPropertyMatchingRoutePredicateFactory.java |  81 ++++
 .../org/apache/syncope/sra/SyncopeSRATest.java     | 207 +++++++++
 .../syncope/sra/SyncopeSRATestController.java      |  16 +-
 .../syncope/sra/SyncopeSRATestCoreStartup.java     | 116 +++++
 .../sra/SyncopeSRATestKeymasterStartup.java        |  92 ++++
 .../src/test}/resources/keymaster.properties       |   0
 62 files changed, 2275 insertions(+), 427 deletions(-)
 create mode 100644 archetype/src/main/resources/archetype-resources/fit/pom.xml
 copy {core/am/logic => archetype/src/main/resources/archetype-resources/sra}/pom.xml (60%)
 rename {sra/src/main => archetype/src/main/resources/archetype-resources/sra/src/test}/resources/keymaster.properties (100%)
 rename common/am/lib/src/main/java/org/apache/syncope/common/lib/types/{GatewayFilter.java => GatewayRouteFilter.java} (80%)
 rename common/am/lib/src/main/java/org/apache/syncope/common/lib/types/{GatewayPredicate.java => GatewayRoutePredicate.java} (74%)
 copy {sra => docker/sra}/src/main/resources/application.properties (85%)
 copy docker/{enduser => sra}/src/main/resources/keymaster.properties (100%)
 copy {sra => docker/sra}/src/main/resources/sra.properties (96%)
 rename docker/src/main/resources/docker-compose/{docker-compose-zookeeper.yml => docker-compose-all.yml} (82%)
 copy client/idrepo/console/src/main/java/org/apache/syncope/client/console/chartjs/Pie.java => sra/src/main/java/org/apache/syncope/sra/CustomGatewayFilterFactory.java (54%)
 create mode 100644 sra/src/main/java/org/apache/syncope/sra/CustomRoutePredicateFactory.java
 create mode 100644 sra/src/main/java/org/apache/syncope/sra/ManagementController.java
 create mode 100644 sra/src/main/java/org/apache/syncope/sra/RouteProvider.java
 copy ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/init/SCIMLoader.java => sra/src/main/java/org/apache/syncope/sra/RouteRefresher.java (61%)
 create mode 100644 sra/src/test/java/org/apache/syncope/sra/BodyPropertyAddingGatewayFilterFactory.java
 create mode 100644 sra/src/test/java/org/apache/syncope/sra/BodyPropertyMatchingRoutePredicateFactory.java
 create mode 100644 sra/src/test/java/org/apache/syncope/sra/SyncopeSRATest.java
 copy ext/saml2sp/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/NullRequestedAuthnContextProvider.java => sra/src/test/java/org/apache/syncope/sra/SyncopeSRATestController.java (71%)
 create mode 100644 sra/src/test/java/org/apache/syncope/sra/SyncopeSRATestCoreStartup.java
 create mode 100644 sra/src/test/java/org/apache/syncope/sra/SyncopeSRATestKeymasterStartup.java
 copy {common/keymaster/client-zookeeper/src/main => sra/src/test}/resources/keymaster.properties (100%)


[syncope] 01/02: Upgrading Elasticsearch

Posted by il...@apache.org.
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

commit 0486ab6f401540f7022ad827a2a138b827a0f4cf
Author: Francesco Chicchiriccò <il...@apache.org>
AuthorDate: Wed May 29 07:00:43 2019 +0200

    Upgrading Elasticsearch
---
 pom.xml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/pom.xml b/pom.xml
index ba97350..1de07d5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -416,7 +416,7 @@ under the License.
     <slf4j.version>1.7.26</slf4j.version>
     <opensaml.version>3.3.0</opensaml.version>
 
-    <elasticsearch.version>7.1.0</elasticsearch.version>
+    <elasticsearch.version>7.1.1</elasticsearch.version>
 
     <apacheds.version>2.0.0.AM25</apacheds.version>
     <apachedirapi.version>2.0.0.AM2</apachedirapi.version>
@@ -2508,7 +2508,7 @@ under the License.
             <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.0/apidocs/</link>
-            <link>https://artifacts.elastic.co/javadoc/org/elasticsearch/elasticsearch/7.1.0/index.html</link>
+            <link>https://artifacts.elastic.co/javadoc/org/elasticsearch/elasticsearch/7.1.1/index.html</link>
             <link>http://docs.swagger.io/swagger-core/v2.0.8/apidocs/</link>
           </links>
         </configuration>


[syncope] 02/02: [SYNCOPE-1455] SRA now working with routes defined in Core

Posted by il...@apache.org.
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

commit 23cc100d28e4dfe2b54304681a053fdcd1bffe26
Author: Francesco Chicchiriccò <il...@apache.org>
AuthorDate: Thu May 30 12:04:01 2019 +0200

    [SYNCOPE-1455] SRA now working with routes defined in Core
---
 archetype/pom.xml                                  |  14 +-
 .../META-INF/maven/archetype-metadata.xml          |  20 +
 .../resources/archetype-resources/enduser/pom.xml  | 177 --------
 .../main/resources/archetype-resources/fit/pom.xml | 275 ++++++++++++
 .../main/resources/archetype-resources/sra/pom.xml |  85 ++++
 .../sra/src/test}/resources/keymaster.properties   |   0
 archetype/src/main/resources/meta-pom.xml          |   8 +
 .../client/lib/SyncopeClientFactoryBean.java       |   6 +-
 .../syncope/common/lib/to/GatewayRouteTO.java      |  26 +-
 .../syncope/common/lib/types/AMEntitlement.java    |   4 -
 .../syncope/common/lib/types/FilterFactory.java    |   7 +-
 ...{GatewayFilter.java => GatewayRouteFilter.java} |  20 +-
 ...ayPredicate.java => GatewayRoutePredicate.java} |  37 +-
 .../syncope/common/lib/types/PredicateCond.java    |   3 +-
 .../syncope/common/lib/types/PredicateFactory.java |   1 -
 .../rest/api/service/GatewayRouteService.java      |  12 -
 .../zookeper/ZookeeperKeymasterClientContext.java  |  38 +-
 .../syncope/core/logic/GatewayRouteLogic.java      |  41 +-
 .../rest/cxf/service/GatewayRouteServiceImpl.java  |   7 +-
 .../logic/init/IdMImplementationTypeLoader.java    |   3 +-
 .../init/ClassPathScanImplementationLookup.java    |   3 +-
 .../logic/init/IdRepoImplementationTypeLoader.java |   3 +-
 .../core/rest/cxf/RestServiceExceptionMapper.java  |   7 +-
 .../core/persistence/api/entity/GatewayRoute.java  |  16 +-
 .../core/persistence/jpa/RuntimeDomainLoader.java  |   4 +-
 .../core/persistence/jpa/StartupDomainLoader.java  |   2 +-
 .../persistence/jpa/entity/JPAGatewayRoute.java    |  29 +-
 .../persistence/jpa/inner/GatewayRouteTest.java    |   8 +-
 .../java/data/GatewayRouteDataBinderImpl.java      |  19 +-
 .../syncope/core/starter/SyncopeCoreStartup.java   |   2 +-
 docker/sra/pom.xml                                 |  36 +-
 docker/sra/src/main/resources/Dockerfile           |   4 +-
 .../sra}/src/main/resources/application.properties |   6 +-
 .../sra/src/main/resources/keymaster.properties    |   9 +-
 docker/sra/src/main/resources/log4j2.xml           |  16 +-
 .../sra}/src/main/resources/sra.properties         |   2 -
 docker/sra/src/main/resources/startup.sh           |   3 +-
 ...ompose-zookeeper.yml => docker-compose-all.yml} |  19 +-
 fit/console-reference/pom.xml                      |   3 +-
 fit/core-reference/pom.xml                         |   2 +-
 .../syncope/fit/core/GatewayRouteITCase.java       |  52 ++-
 fit/enduser-reference/pom.xml                      |   3 +-
 pom.xml                                            |  41 +-
 sra/pom.xml                                        | 188 ++++++--
 ...tartup.java => CustomGatewayFilterFactory.java} |  32 +-
 .../syncope/sra/CustomRoutePredicateFactory.java   |  56 +++
 .../apache/syncope/sra/ManagementController.java   |  93 ++++
 .../java/org/apache/syncope/sra/RouteProvider.java | 472 +++++++++++++++++++++
 .../org/apache/syncope/sra/RouteRefresher.java     |  21 +-
 .../apache/syncope/sra/SyncopeSRAApplication.java  |  49 ++-
 .../org/apache/syncope/sra/SyncopeSRAShutdown.java |   2 +
 .../org/apache/syncope/sra/SyncopeSRAStartup.java  |   2 +
 sra/src/main/resources/application.properties      |   4 -
 sra/src/main/resources/log4j2.xml                  |   9 +-
 sra/src/main/resources/sra.properties              |   2 -
 .../BodyPropertyAddingGatewayFilterFactory.java    | 201 +++++++++
 .../BodyPropertyMatchingRoutePredicateFactory.java |  81 ++++
 .../org/apache/syncope/sra/SyncopeSRATest.java     | 207 +++++++++
 .../syncope/sra/SyncopeSRATestController.java}     |  15 +-
 .../syncope/sra/SyncopeSRATestCoreStartup.java     | 116 +++++
 .../sra/SyncopeSRATestKeymasterStartup.java        |  92 ++++
 .../{main => test}/resources/keymaster.properties  |   2 +-
 62 files changed, 2330 insertions(+), 387 deletions(-)

diff --git a/archetype/pom.xml b/archetype/pom.xml
index 2fcee7d..c51466b 100644
--- a/archetype/pom.xml
+++ b/archetype/pom.xml
@@ -42,7 +42,7 @@ under the License.
       <extension>
         <groupId>org.apache.maven.archetype</groupId>
         <artifactId>archetype-packaging</artifactId>
-        <version>3.0.1</version>
+        <version>3.1.0</version>
       </extension>
     </extensions>
 
@@ -334,6 +334,18 @@ under the License.
         <directory>../fit/enduser-reference/src/main/webapp/WEB-INF</directory>
         <targetPath>${project.build.outputDirectory}/archetype-resources/enduser/src/main/webapp/WEB-INF</targetPath>
       </resource>
+      
+      <resource>
+        <directory>../sra/src/main/resources</directory>
+        <targetPath>${project.build.outputDirectory}/archetype-resources/sra/src/main/resources</targetPath>
+      </resource>
+      <resource>
+        <directory>../sra/src/test/resources</directory>
+        <targetPath>${project.build.outputDirectory}/archetype-resources/sra/src/main/resources</targetPath>
+        <includes>
+          <include>keymaster.properties</include>          
+        </includes>
+      </resource>
     </resources>
   </build>
   
diff --git a/archetype/src/main/resources/META-INF/maven/archetype-metadata.xml b/archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
index 391cd25..f22b5fb 100644
--- a/archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
+++ b/archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
@@ -79,5 +79,25 @@ under the License.
         </fileSet>
       </fileSets>
     </module>
+    <module id="syncope-sra" dir="sra" name="syncope-sra">
+      <fileSets>
+        <fileSet filtered="false" encoding="UTF-8">
+          <directory>src/main/resources</directory>
+        </fileSet>
+        <fileSet filtered="false" encoding="UTF-8">
+          <directory>src/test/resources</directory>
+        </fileSet>
+      </fileSets>
+    </module>
+    <module id="syncope-fit" dir="fit" name="syncope-fit">
+      <fileSets>
+        <fileSet filtered="false" encoding="UTF-8">
+          <directory>src/main/resources</directory>
+        </fileSet>
+        <fileSet filtered="false" encoding="UTF-8">
+          <directory>src/test/resources</directory>
+        </fileSet>
+      </fileSets>
+    </module>
   </modules>
 </archetype-descriptor>
diff --git a/archetype/src/main/resources/archetype-resources/enduser/pom.xml b/archetype/src/main/resources/archetype-resources/enduser/pom.xml
index 1004ea7..2902c73 100644
--- a/archetype/src/main/resources/archetype-resources/enduser/pom.xml
+++ b/archetype/src/main/resources/archetype-resources/enduser/pom.xml
@@ -94,183 +94,6 @@ under the License.
 
   <profiles>
     <profile>
-      <id>embedded</id>
-      
-      <properties>
-        <conf.directory>${project.build.directory}/test-classes</conf.directory>
-      </properties>
-      
-      <dependencies>
-        <dependency>
-          <groupId>org.apache.syncope.fit</groupId>
-          <artifactId>syncope-fit-build-tools</artifactId>
-          <version>${syncope.version}</version>
-          <type>war</type>
-          <scope>test</scope>
-        </dependency>
-        <dependency>
-          <groupId>com.h2database</groupId>
-          <artifactId>h2</artifactId>
-          <scope>test</scope>
-        </dependency>
-      </dependencies>
-      
-      <build>
-        <defaultGoal>clean verify cargo:run</defaultGoal>  
-        
-        <plugins>
-          <plugin>
-            <groupId>org.apache.maven.plugins</groupId>
-            <artifactId>maven-antrun-plugin</artifactId>
-            <inherited>true</inherited>
-            <executions>
-              <execution>
-                <id>addFlowableTestContent</id>
-                <phase>prepare-package</phase>
-                <configuration>
-                  <target>
-                    <taskdef resource="net/sf/antcontrib/antcontrib.properties"
-                             classpathref="maven.plugin.classpath"/>
-
-                    <if>
-                      <not>
-                        <available file="../core/target/test-classes/backup/MasterContent.xml"/>
-                      </not>
-                      <then>
-                        <mkdir dir="../core/target/test-classes/backup"/>
-                        <copy file="../core/target/test-classes/domains/MasterContent.xml" 
-                              todir="../core/target/test-classes/backup"/>
-                      </then>
-                    </if>
-                    <if>
-                      <available file="../core/target/syncope/WEB-INF/lib/flowable-engine-${flowable.version}.jar"/>
-                      <then>
-                        <xslt basedir="../core/target/test-classes/backup" 
-                              includes="MasterContent.xml"
-                              destdir="../core/target/test-classes/domains"
-                              extension=".xml"
-                              force="true"
-                              style="../core/src/test/resources/addFlowableToContent.xsl"/>
-                      </then>
-                    </if>
-                  </target>
-                </configuration>
-                <goals>
-                  <goal>run</goal>
-                </goals>
-              </execution>
-              <execution>
-                <id>setupEmbeddedConf</id>
-                <phase>package</phase>
-                <configuration>
-                  <target>
-                    <delete dir="../core/target/syncope/WEB-INF/classes/domains"/>
-                    <copy todir="../core/target/syncope/WEB-INF/classes/domains">
-                      <fileset dir="../core/target/test-classes/domains"/>
-                    </copy>
-                    <copy file="../core/target/test-classes/connid.properties" 
-                          todir="../core/target/syncope/WEB-INF/classes" 
-                          overwrite="true"/>
-                    <copy file="../core/target/test-classes/keymaster.properties" 
-                          todir="../core/target/syncope/WEB-INF/classes" 
-                          overwrite="true"/>
-
-                    <copy file="../console/target/test-classes/keymaster.properties" 
-                          todir="../console/target/syncope-console/WEB-INF/classes" 
-                          overwrite="true"/>
-
-                    <copy file="${project.build.directory}/test-classes/enduser.properties" 
-                          todir="${project.build.directory}/${project.build.finalName}/WEB-INF/classes" 
-                          overwrite="true"/>                    
-                    <copy file="${project.build.directory}/test-classes/keymaster.properties" 
-                          todir="${project.build.directory}/${project.build.finalName}/WEB-INF/classes" 
-                          overwrite="true"/>                    
-                    <copy file="${project.build.directory}/test-classes/customFormAttributes.json" 
-                          todir="${project.build.directory}/${project.build.finalName}/WEB-INF/classes" 
-                          overwrite="true"/>
-                    <copy file="${project.build.directory}/test-classes/customTemplate.json" 
-                          todir="${project.build.directory}/${project.build.finalName}/WEB-INF/classes" 
-                          overwrite="true"/>
-                  </target>
-                </configuration>
-                <goals>
-                  <goal>run</goal>
-                </goals>
-              </execution>
-            </executions>
-            <dependencies>
-              <dependency>
-                <groupId>ant-contrib</groupId>
-                <artifactId>ant-contrib</artifactId>
-                <version>20020829</version>
-              </dependency>
-            </dependencies>
-          </plugin>
-      
-          <plugin>
-            <groupId>org.codehaus.cargo</groupId>
-            <artifactId>cargo-maven2-plugin</artifactId>
-            <inherited>true</inherited>
-            <configuration>
-              <container>
-                <containerId>tomcat9x</containerId>
-                <zipUrlInstaller>
-                  <url>http://central.maven.org/maven2/org/apache/tomcat/tomcat/${tomcat.version}/tomcat-${tomcat.version}.zip</url>
-                  <downloadDir>${settings.localRepository}/org/codehaus/cargo/cargo-container-archives</downloadDir>
-                  <extractDir>${project.build.directory}/cargo/extract</extractDir>
-                </zipUrlInstaller>
-                <dependencies>
-                  <dependency>
-                    <groupId>com.h2database</groupId>
-                    <artifactId>h2</artifactId>
-                  </dependency>
-                </dependencies>
-                <timeout>300000</timeout>
-                <log>${cargo.log}</log>
-                <output>${cargo.output}</output> 
-              </container>
-              <configuration>
-                <properties>
-                  <cargo.jvmargs>
-                    -Dspring.profiles.active=embedded
-                    -XX:+CMSClassUnloadingEnabled -Xmx1024m -Xms512m</cargo.jvmargs>
-                </properties>
-              </configuration>
-              <deployables>
-                <deployable>
-                  <groupId>org.apache.syncope.fit</groupId>
-                  <artifactId>syncope-fit-build-tools</artifactId>
-                  <type>war</type>
-                  <properties>
-                    <context>syncope-fit-build-tools</context>
-                  </properties>
-                </deployable>
-                <deployable>
-                  <location>../core/target/syncope</location>
-                  <properties>
-                    <context>syncope</context>
-                  </properties>
-                </deployable>
-                <deployable>
-                  <location>../console/target/syncope-console</location>
-                  <properties>
-                    <context>syncope-console</context>
-                  </properties>
-                </deployable>
-                <deployable>
-                  <location>${project.build.directory}/${project.build.finalName}</location>
-                  <properties>
-                    <context>syncope-enduser</context>
-                  </properties>
-                </deployable>
-              </deployables>
-            </configuration>
-          </plugin>
-        </plugins>  
-      </build>
-    </profile>
-
-    <profile>
       <id>all</id>
 
       <dependencies>
diff --git a/archetype/src/main/resources/archetype-resources/fit/pom.xml b/archetype/src/main/resources/archetype-resources/fit/pom.xml
new file mode 100644
index 0000000..7c939d2
--- /dev/null
+++ b/archetype/src/main/resources/archetype-resources/fit/pom.xml
@@ -0,0 +1,275 @@
+<?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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>${groupId}</groupId>
+    <artifactId>${rootArtifactId}</artifactId>
+    <version>${version}</version>
+  </parent>
+
+  <name>Apache Syncope sample project - FIT</name>
+  <groupId>${groupId}</groupId>
+  <artifactId>${artifactId}</artifactId>
+  <packaging>war</packaging>
+
+  <properties>
+    <exec.skip>true</exec.skip>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.syncope.fit</groupId>
+      <artifactId>syncope-fit-build-tools</artifactId>
+      <version>${syncope.version}</version>
+      <type>war</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.h2database</groupId>
+      <artifactId>h2</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-install-plugin</artifactId>
+        <configuration>
+          <skip>true</skip>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-deploy-plugin</artifactId>
+        <configuration>
+          <skip>true</skip>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-war-plugin</artifactId>
+        <inherited>false</inherited>
+        <configuration>
+          <failOnMissingWebXml>false</failOnMissingWebXml>
+        </configuration>
+        <executions>
+          <execution>
+            <id>default-war</id>
+            <phase>none</phase>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-antrun-plugin</artifactId>
+        <inherited>true</inherited>
+        <executions>
+          <execution>
+            <id>addFlowableTestContent</id>
+            <phase>prepare-package</phase>
+            <configuration>
+              <target>
+                <taskdef resource="net/sf/antcontrib/antcontrib.properties"
+                         classpathref="maven.plugin.classpath"/>
+
+                <if>
+                  <not>
+                    <available file="../core/target/test-classes/backup/MasterContent.xml"/>
+                  </not>
+                  <then>
+                    <mkdir dir="../core/target/test-classes/backup"/>
+                    <copy file="../core/target/test-classes/domains/MasterContent.xml" 
+                          todir="../core/target/test-classes/backup"/>
+                  </then>
+                </if>
+                <if>
+                  <available file="../core/target/syncope/WEB-INF/lib/flowable-engine-${flowable.version}.jar"/>
+                  <then>
+                    <xslt basedir="../core/target/test-classes/backup" 
+                          includes="MasterContent.xml"
+                          destdir="../core/target/test-classes/domains"
+                          extension=".xml"
+                          force="true"
+                          style="../core/src/test/resources/addFlowableToContent.xsl"/>
+                  </then>
+                </if>
+              </target>
+            </configuration>
+            <goals>
+              <goal>run</goal>
+            </goals>
+          </execution>
+          <execution>
+            <id>setupEmbeddedConf</id>
+            <phase>package</phase>
+            <configuration>
+              <target>
+                <delete dir="../core/target/syncope/WEB-INF/classes/domains"/>
+                <copy todir="../core/target/syncope/WEB-INF/classes/domains">
+                  <fileset dir="../core/target/test-classes/domains"/>
+                </copy>
+                <copy file="../core/target/test-classes/connid.properties" 
+                      todir="../core/target/syncope/WEB-INF/classes" 
+                      overwrite="true"/>
+                <copy file="../core/target/test-classes/keymaster.properties" 
+                      todir="../core/target/syncope/WEB-INF/classes" 
+                      overwrite="true"/>
+
+                <copy file="../console/target/test-classes/keymaster.properties" 
+                      todir="../console/target/syncope-console/WEB-INF/classes" 
+                      overwrite="true"/>
+
+                <copy file="../enduser/target/test-classes/keymaster.properties" 
+                      todir="../enduser/target/syncope-enduser/WEB-INF/classes" 
+                      overwrite="true"/>                
+                <copy file="../enduser/target/test-classes/customFormAttributes.json" 
+                      todir="../enduser/target/syncope-enduser/WEB-INF/classes" 
+                      overwrite="true"/>
+                <copy file="../enduser/target/test-classes/customTemplate.json" 
+                      todir="../enduser/target/syncope-enduser/WEB-INF/classes" 
+                      overwrite="true"/>
+              </target>
+            </configuration>
+            <goals>
+              <goal>run</goal>
+            </goals>
+          </execution>
+        </executions>
+        <dependencies>
+          <dependency>
+            <groupId>ant-contrib</groupId>
+            <artifactId>ant-contrib</artifactId>
+            <version>20020829</version>
+          </dependency>
+        </dependencies>
+      </plugin>
+      
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>exec-maven-plugin</artifactId>
+        <inherited>true</inherited>
+        <executions>
+          <execution>
+            <id>default-cli</id>
+            <phase>pre-integration-test</phase>
+            <goals>
+              <goal>exec</goal>
+            </goals>
+            <configuration>
+              <executable>java</executable>
+              <arguments>
+                <argument>-jar</argument>
+                <argument>${basedir}/../sra/target/syncope-sra.jar</argument>
+                <argument>-Dreactor.netty.http.server.accessLogEnabled=true</argument>
+              </arguments>
+              <environmentVariables>
+                <LOADER_PATH>${basedir}/../sra/target/test-classes</LOADER_PATH>
+              </environmentVariables>
+              <async>true</async>
+              <asyncDestroyOnShutdown>true</asyncDestroyOnShutdown>
+            </configuration>                        
+          </execution>
+        </executions>
+      </plugin>
+      
+      <plugin>
+        <groupId>org.codehaus.cargo</groupId>
+        <artifactId>cargo-maven2-plugin</artifactId>
+        <inherited>true</inherited>
+        <configuration>
+          <container>
+            <containerId>tomcat9x</containerId>
+            <zipUrlInstaller>
+              <url>http://central.maven.org/maven2/org/apache/tomcat/tomcat/${tomcat.version}/tomcat-${tomcat.version}.zip</url>
+              <downloadDir>${settings.localRepository}/org/codehaus/cargo/cargo-container-archives</downloadDir>
+              <extractDir>${project.build.directory}/cargo/extract</extractDir>
+            </zipUrlInstaller>
+            <dependencies>
+              <dependency>
+                <groupId>com.h2database</groupId>
+                <artifactId>h2</artifactId>
+              </dependency>
+            </dependencies>
+            <timeout>300000</timeout>
+            <log>${cargo.log}</log>
+            <output>${cargo.output}</output> 
+          </container>
+          <configuration>
+            <properties>
+              <cargo.jvmargs>
+                -Dspring.profiles.active=embedded
+                -XX:+CMSClassUnloadingEnabled -Xmx1024m -Xms512m</cargo.jvmargs>
+            </properties>
+          </configuration>
+          <deployables>
+            <deployable>
+              <groupId>org.apache.syncope.fit</groupId>
+              <artifactId>syncope-fit-build-tools</artifactId>
+              <type>war</type>
+              <properties>
+                <context>syncope-fit-build-tools</context>
+              </properties>
+            </deployable>
+            <deployable>
+              <location>../core/target/syncope</location>
+              <properties>
+                <context>syncope</context>
+              </properties>
+            </deployable>
+            <deployable>
+              <location>../console/target/syncope-console</location>
+              <properties>
+                <context>syncope-console</context>
+              </properties>
+            </deployable>
+            <deployable>
+              <location>../enduser/target/syncope-enduser</location>
+              <properties>
+                <context>syncope-enduser</context>
+              </properties>
+            </deployable>
+          </deployables>
+        </configuration>
+      </plugin>
+    </plugins> 
+  </build>
+  
+  <profiles>
+    <profile>
+      <id>embedded</id>
+
+      <properties>
+        <exec.skip>false</exec.skip>
+      </properties>
+
+      <build>
+        <defaultGoal>clean verify cargo:run</defaultGoal>  
+      </build>
+    </profile>
+  </profiles>
+</project>
diff --git a/archetype/src/main/resources/archetype-resources/sra/pom.xml b/archetype/src/main/resources/archetype-resources/sra/pom.xml
new file mode 100644
index 0000000..48376d9
--- /dev/null
+++ b/archetype/src/main/resources/archetype-resources/sra/pom.xml
@@ -0,0 +1,85 @@
+<?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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>${groupId}</groupId>
+    <artifactId>${rootArtifactId}</artifactId>
+    <version>${version}</version>
+  </parent>
+
+  <name>Apache Syncope sample project - SRA</name>
+  <groupId>${groupId}</groupId>
+  <artifactId>${artifactId}</artifactId>
+  <packaging>jar</packaging>
+
+  <dependencies>
+    <dependency>
+      <groupId>${groupId}</groupId>
+      <artifactId>syncope-common</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    
+    <dependency>
+      <groupId>org.apache.syncope</groupId>
+      <artifactId>syncope-sra</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.syncope.ext.self-keymaster</groupId>
+      <artifactId>syncope-ext-self-keymaster-client</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.common.keymaster</groupId>
+      <artifactId>syncope-common-keymaster-client-zookeeper</artifactId>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <finalName>syncope-sra</finalName>
+
+    <plugins>
+      <plugin>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-maven-plugin</artifactId>
+        <configuration>
+          <mainClass>org.apache.syncope.sra.SyncopeSRAApplication</mainClass>
+          <layout>ZIP</layout>
+        </configuration>
+        <executions>
+          <execution>
+            <goals>
+              <goal>repackage</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+
+    <resources>
+      <resource>
+        <directory>src/main/resources</directory>
+        <filtering>true</filtering>
+      </resource>
+    </resources>
+  </build>
+</project>
diff --git a/sra/src/main/resources/keymaster.properties b/archetype/src/main/resources/archetype-resources/sra/src/test/resources/keymaster.properties
similarity index 100%
copy from sra/src/main/resources/keymaster.properties
copy to archetype/src/main/resources/archetype-resources/sra/src/test/resources/keymaster.properties
diff --git a/archetype/src/main/resources/meta-pom.xml b/archetype/src/main/resources/meta-pom.xml
index d16d99a..8eb4a43 100644
--- a/archetype/src/main/resources/meta-pom.xml
+++ b/archetype/src/main/resources/meta-pom.xml
@@ -83,6 +83,12 @@ under the License.
       </dependency>
 
       <dependency>
+        <groupId>org.apache.syncope</groupId>
+        <artifactId>syncope-sra</artifactId>
+        <version>${syncope.version}</version>
+      </dependency>
+
+      <dependency>
         <groupId>org.apache.syncope.ext.self-keymaster</groupId>
         <artifactId>syncope-ext-self-keymaster-rest-cxf</artifactId>
         <version>${syncope.version}</version>
@@ -128,6 +134,8 @@ under the License.
     <module>core</module>
     <module>console</module>
     <module>enduser</module>
+    <module>sra</module>
+    <module>fit</module>
   </modules>
 
 </project>
diff --git a/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClientFactoryBean.java b/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClientFactoryBean.java
index f055f83..c250a19 100644
--- a/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClientFactoryBean.java
+++ b/client/idrepo/lib/src/main/java/org/apache/syncope/client/lib/SyncopeClientFactoryBean.java
@@ -277,13 +277,11 @@ public class SyncopeClientFactoryBean {
 
     /**
      * Builds client instance which will be passing the provided value in the
-     * {@link javax.ws.rs.core.HttpHeaders#AUTHORIZATION}
-     * request header.
+     * {@link javax.ws.rs.core.HttpHeaders#AUTHORIZATION} request header.
      *
      * @param jwt value received after login, in the {@link RESTHeaders#TOKEN} response header
      * @return client instance which will be passing the provided value in the
-     * {@link javax.ws.rs.core.HttpHeaders#AUTHORIZATION}
-     * request header
+     * {@link javax.ws.rs.core.HttpHeaders#AUTHORIZATION} request header
      */
     public SyncopeClient create(final String jwt) {
         return create(new JWTAuthenticationHandler(jwt));
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/GatewayRouteTO.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/GatewayRouteTO.java
index f973c70..17ae03a 100644
--- a/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/GatewayRouteTO.java
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/GatewayRouteTO.java
@@ -25,12 +25,16 @@ import java.util.List;
 import javax.ws.rs.PathParam;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
 import org.apache.commons.lang3.builder.EqualsBuilder;
 import org.apache.commons.lang3.builder.HashCodeBuilder;
-import org.apache.syncope.common.lib.types.GatewayFilter;
-import org.apache.syncope.common.lib.types.GatewayPredicate;
+import org.apache.syncope.common.lib.types.GatewayRouteFilter;
+import org.apache.syncope.common.lib.types.GatewayRoutePredicate;
 import org.apache.syncope.common.lib.types.GatewayRouteStatus;
 
+@XmlRootElement(name = "gatewayRoute")
+@XmlType
 public class GatewayRouteTO implements EntityTO {
 
     private static final long serialVersionUID = 4044528284951757870L;
@@ -39,11 +43,13 @@ public class GatewayRouteTO implements EntityTO {
 
     private String name;
 
+    private int order = 0;
+
     private URI target;
 
-    private final List<GatewayFilter> filters = new ArrayList<>();
+    private final List<GatewayRouteFilter> filters = new ArrayList<>();
 
-    private final List<GatewayPredicate> predicates = new ArrayList<>();
+    private final List<GatewayRoutePredicate> predicates = new ArrayList<>();
 
     private GatewayRouteStatus status;
 
@@ -66,6 +72,14 @@ public class GatewayRouteTO implements EntityTO {
         this.name = name;
     }
 
+    public int getOrder() {
+        return order;
+    }
+
+    public void setOrder(final int order) {
+        this.order = order;
+    }
+
     public URI getTarget() {
         return target;
     }
@@ -77,14 +91,14 @@ public class GatewayRouteTO implements EntityTO {
     @XmlElementWrapper(name = "filters")
     @XmlElement(name = "filter")
     @JsonProperty("filters")
-    public List<GatewayFilter> getFilters() {
+    public List<GatewayRouteFilter> getFilters() {
         return filters;
     }
 
     @XmlElementWrapper(name = "predicates")
     @XmlElement(name = "predicate")
     @JsonProperty("predicates")
-    public List<GatewayPredicate> getPredicates() {
+    public List<GatewayRoutePredicate> getPredicates() {
         return predicates;
     }
 
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/AMEntitlement.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/AMEntitlement.java
index 6d7d762..06d511c 100644
--- a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/AMEntitlement.java
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/AMEntitlement.java
@@ -26,12 +26,8 @@ import java.util.TreeSet;
 
 public final class AMEntitlement {
 
-    public static final String GATEWAY_ROUTE_LIST = "GATEWAY_ROUTE_LIST";
-
     public static final String GATEWAY_ROUTE_CREATE = "GATEWAY_ROUTE_CREATE";
 
-    public static final String GATEWAY_ROUTE_READ = "GATEWAY_ROUTE_READ";
-
     public static final String GATEWAY_ROUTE_UPDATE = "GATEWAY_ROUTE_UPDATE";
 
     public static final String GATEWAY_ROUTE_DELETE = "GATEWAY_ROUTE_DELETE";
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/FilterFactory.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/FilterFactory.java
index 1120af9..e7fd117 100644
--- a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/FilterFactory.java
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/FilterFactory.java
@@ -26,8 +26,7 @@ public enum FilterFactory {
     ADD_REQUEST_PARAMETER,
     ADD_RESPONSE_HEADER,
     HYSTRIX,
-    MODIFY_REQUEST_BODY,
-    MODIFY_RESPONSE_BODY,
+    FALLBACK_HEADERS,
     PREFIX_PATH,
     PRESERVE_HOST_HEADER,
     REDIRECT,
@@ -45,9 +44,7 @@ public enum FilterFactory {
     SAVE_SESSION,
     STRIP_PREFIX,
     REQUEST_HEADER_TO_REQUEST_URI,
-    CHANGE_REQUEST_URI,
-    SET_REQIEST_SIZE,
-    FALLBACK_HEADERS,
+    SET_REQUEST_SIZE,
     CUSTOM
 
 }
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/GatewayFilter.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/GatewayRouteFilter.java
similarity index 80%
rename from common/am/lib/src/main/java/org/apache/syncope/common/lib/types/GatewayFilter.java
rename to common/am/lib/src/main/java/org/apache/syncope/common/lib/types/GatewayRouteFilter.java
index cbf946e..1b103a5 100644
--- a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/GatewayFilter.java
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/GatewayRouteFilter.java
@@ -19,16 +19,20 @@
 package org.apache.syncope.common.lib.types;
 
 import java.io.Serializable;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
 import org.apache.commons.lang3.builder.EqualsBuilder;
 import org.apache.commons.lang3.builder.HashCodeBuilder;
 
-public class GatewayFilter implements Serializable {
+@XmlRootElement(name = "gatewayRouteFilter")
+@XmlType
+public class GatewayRouteFilter implements Serializable {
 
     private static final long serialVersionUID = -635785645207375128L;
 
     public static class Builder {
 
-        private final GatewayFilter instance = new GatewayFilter();
+        private final GatewayRouteFilter instance = new GatewayRouteFilter();
 
         public Builder factory(final FilterFactory factory) {
             instance.setFactory(factory);
@@ -40,7 +44,7 @@ public class GatewayFilter implements Serializable {
             return this;
         }
 
-        public GatewayFilter build() {
+        public GatewayRouteFilter build() {
             return instance;
         }
     }
@@ -84,10 +88,18 @@ public class GatewayFilter implements Serializable {
         if (getClass() != obj.getClass()) {
             return false;
         }
-        final GatewayFilter other = (GatewayFilter) obj;
+        final GatewayRouteFilter other = (GatewayRouteFilter) obj;
         return new EqualsBuilder().
                 append(factory, other.factory).
                 append(args, other.args).
                 build();
     }
+
+    @Override
+    public String toString() {
+        return "GatewayFilter{"
+                + "factory=" + factory
+                + ", args=" + args
+                + '}';
+    }
 }
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/GatewayPredicate.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/GatewayRoutePredicate.java
similarity index 74%
rename from common/am/lib/src/main/java/org/apache/syncope/common/lib/types/GatewayPredicate.java
rename to common/am/lib/src/main/java/org/apache/syncope/common/lib/types/GatewayRoutePredicate.java
index 27c6635..9f26bbe 100644
--- a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/GatewayPredicate.java
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/GatewayRoutePredicate.java
@@ -19,16 +19,25 @@
 package org.apache.syncope.common.lib.types;
 
 import java.io.Serializable;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
 import org.apache.commons.lang3.builder.EqualsBuilder;
 import org.apache.commons.lang3.builder.HashCodeBuilder;
 
-public class GatewayPredicate implements Serializable {
+@XmlRootElement(name = "gatewayRoutePredicate")
+@XmlType
+public class GatewayRoutePredicate implements Serializable {
 
     private static final long serialVersionUID = -635785645207375128L;
 
     public static class Builder {
 
-        private final GatewayPredicate instance = new GatewayPredicate();
+        private final GatewayRoutePredicate instance = new GatewayRoutePredicate();
+
+        public Builder negate() {
+            instance.setNegate(true);
+            return this;
+        }
 
         public Builder cond(final PredicateCond cond) {
             instance.setCond(cond);
@@ -45,17 +54,27 @@ public class GatewayPredicate implements Serializable {
             return this;
         }
 
-        public GatewayPredicate build() {
+        public GatewayRoutePredicate build() {
             return instance;
         }
     }
 
+    private boolean negate;
+
     private PredicateCond cond;
 
     private PredicateFactory factory;
 
     private String args;
 
+    public boolean isNegate() {
+        return negate;
+    }
+
+    public void setNegate(final boolean negate) {
+        this.negate = negate;
+    }
+
     public PredicateCond getCond() {
         return cond;
     }
@@ -100,11 +119,21 @@ public class GatewayPredicate implements Serializable {
         if (getClass() != obj.getClass()) {
             return false;
         }
-        final GatewayPredicate other = (GatewayPredicate) obj;
+        final GatewayRoutePredicate other = (GatewayRoutePredicate) obj;
         return new EqualsBuilder().
                 append(cond, other.cond).
                 append(factory, other.factory).
                 append(args, other.args).
                 build();
     }
+
+    @Override
+    public String toString() {
+        return "GatewayPredicate{"
+                + "negate=" + negate
+                + ", cond=" + cond
+                + ", factory=" + factory
+                + ", args=" + args
+                + '}';
+    }
 }
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/PredicateCond.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/PredicateCond.java
index 93b0bad..12d2704 100644
--- a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/PredicateCond.java
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/PredicateCond.java
@@ -23,7 +23,6 @@ import javax.xml.bind.annotation.XmlEnum;
 @XmlEnum
 public enum PredicateCond {
     AND,
-    OR,
-    NOT
+    OR
 
 }
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/PredicateFactory.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/PredicateFactory.java
index 5cf4e77..c1e431f 100644
--- a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/PredicateFactory.java
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/PredicateFactory.java
@@ -32,7 +32,6 @@ public enum PredicateFactory {
     PATH,
     QUERY,
     REMOTE_ADDR,
-    READ_BODY,
     CUSTOM
 
 }
diff --git a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/GatewayRouteService.java b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/GatewayRouteService.java
index 6b957b8..c44af7e 100644
--- a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/GatewayRouteService.java
+++ b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/GatewayRouteService.java
@@ -129,16 +129,4 @@ public interface GatewayRouteService extends JAXRSService {
     @Path("push")
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
     void pushToSRA();
-
-    /**
-     * Push route with matching key to SRA.
-     *
-     * @param key route key
-     */
-    @ApiResponses(
-            @ApiResponse(responseCode = "204", description = "Operation was successful"))
-    @POST
-    @Path("push/{key}")
-    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
-    void pushToSRA(@NotNull @PathParam("key") String key);
 }
diff --git a/common/keymaster/client-zookeeper/src/main/java/org/apache/syncope/common/keymaster/client/zookeper/ZookeeperKeymasterClientContext.java b/common/keymaster/client-zookeeper/src/main/java/org/apache/syncope/common/keymaster/client/zookeper/ZookeeperKeymasterClientContext.java
index 234093f..033f5b7 100644
--- a/common/keymaster/client-zookeeper/src/main/java/org/apache/syncope/common/keymaster/client/zookeper/ZookeeperKeymasterClientContext.java
+++ b/common/keymaster/client-zookeeper/src/main/java/org/apache/syncope/common/keymaster/client/zookeper/ZookeeperKeymasterClientContext.java
@@ -59,7 +59,8 @@ public class ZookeeperKeymasterClientContext {
     @Value("${keymaster.maxRetries:3}")
     private Integer maxRetries;
 
-    @ConditionalOnExpression("#{'${keymaster.address}' matches '^(?!http).+'}")
+    @ConditionalOnExpression("#{'${keymaster.address}' "
+            + "matches '^((\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})|[a-z\\.]+):[0-9]+$'}")
     @Bean
     public CuratorFramework curatorFramework() throws InterruptedException {
         if (StringUtils.isNotBlank(username) && StringUtils.isNotBlank(password)) {
@@ -86,20 +87,18 @@ public class ZookeeperKeymasterClientContext {
                 connectString(address).
                 retryPolicy(new ExponentialBackoffRetry(baseSleepTimeMs, maxRetries));
         if (StringUtils.isNotBlank(username)) {
-            clientBuilder.
-                    authorization("digest", (username).getBytes()).
-                    aclProvider(new ACLProvider() {
-
-                        @Override
-                        public List<ACL> getDefaultAcl() {
-                            return ZooDefs.Ids.CREATOR_ALL_ACL;
-                        }
-
-                        @Override
-                        public List<ACL> getAclForPath(final String path) {
-                            return ZooDefs.Ids.CREATOR_ALL_ACL;
-                        }
-                    });
+            clientBuilder.authorization("digest", username.getBytes()).aclProvider(new ACLProvider() {
+
+                @Override
+                public List<ACL> getDefaultAcl() {
+                    return ZooDefs.Ids.CREATOR_ALL_ACL;
+                }
+
+                @Override
+                public List<ACL> getAclForPath(final String path) {
+                    return ZooDefs.Ids.CREATOR_ALL_ACL;
+                }
+            });
         }
         CuratorFramework client = clientBuilder.build();
         client.start();
@@ -108,20 +107,23 @@ public class ZookeeperKeymasterClientContext {
         return client;
     }
 
-    @ConditionalOnExpression("#{'${keymaster.address}' matches '^(?!http).+'}")
+    @ConditionalOnExpression("#{'${keymaster.address}' "
+            + "matches '^((\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})|[a-z\\.]+):[0-9]+$'}")
     @Bean
     public ConfParamOps selfConfParamOps() {
         return new ZookeeperConfParamOps();
     }
 
-    @ConditionalOnExpression("#{'${keymaster.address}' matches '^(?!http).+'}")
+    @ConditionalOnExpression("#{'${keymaster.address}' "
+            + "matches '^((\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})|[a-z\\.]+):[0-9]+$'}")
     @Bean
     public ServiceOps serviceOps() {
         return new ZookeeperServiceDiscoveryOps();
         //return new ZookeeperServiceOps();
     }
 
-    @ConditionalOnExpression("#{'${keymaster.address}' matches '^(?!http).+'}")
+    @ConditionalOnExpression("#{'${keymaster.address}' "
+            + "matches '^((\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})|[a-z\\.]+):[0-9]+$'}")
     @Bean
     public DomainOps domainOps() {
         return new ZookeeperDomainOps();
diff --git a/core/am/logic/src/main/java/org/apache/syncope/core/logic/GatewayRouteLogic.java b/core/am/logic/src/main/java/org/apache/syncope/core/logic/GatewayRouteLogic.java
index 3d57c17..71a343e 100644
--- a/core/am/logic/src/main/java/org/apache/syncope/core/logic/GatewayRouteLogic.java
+++ b/core/am/logic/src/main/java/org/apache/syncope/core/logic/GatewayRouteLogic.java
@@ -19,9 +19,20 @@
 package org.apache.syncope.core.logic;
 
 import java.lang.reflect.Method;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
 import java.util.List;
 import java.util.stream.Collectors;
+import javax.annotation.Resource;
+import javax.ws.rs.core.HttpHeaders;
 import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.cxf.transport.http.auth.DefaultBasicAuthSupplier;
+import org.apache.syncope.common.keymaster.client.api.KeymasterException;
+import org.apache.syncope.common.keymaster.client.api.ServiceOps;
+import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
 import org.apache.syncope.common.lib.to.GatewayRouteTO;
 import org.apache.syncope.common.lib.types.AMEntitlement;
 import org.apache.syncope.core.persistence.api.dao.GatewayRouteDAO;
@@ -45,6 +56,16 @@ public class GatewayRouteLogic extends AbstractTransactionalLogic<GatewayRouteTO
     @Autowired
     private EntityFactory entityFactory;
 
+    @Autowired
+    private ServiceOps serviceOps;
+
+    @Resource(name = "anonymousUser")
+    private String anonymousUser;
+
+    @Resource(name = "anonymousKey")
+    private String anonymousKey;
+
+    @PreAuthorize("isAuthenticated()")
     public List<GatewayRouteTO> list() {
         return routeDAO.findAll().stream().map(binder::getGatewayRouteTO).collect(Collectors.toList());
     }
@@ -57,7 +78,7 @@ public class GatewayRouteLogic extends AbstractTransactionalLogic<GatewayRouteTO
         return binder.getGatewayRouteTO(routeDAO.save(route));
     }
 
-    @PreAuthorize("hasRole('" + AMEntitlement.GATEWAY_ROUTE_READ + "')")
+    @PreAuthorize("isAuthenticated()")
     public GatewayRouteTO read(final String key) {
         GatewayRoute route = routeDAO.find(key);
         if (route == null) {
@@ -92,12 +113,18 @@ public class GatewayRouteLogic extends AbstractTransactionalLogic<GatewayRouteTO
 
     @PreAuthorize("hasRole('" + AMEntitlement.GATEWAY_ROUTE_PUSH + "')")
     public void pushToSRA() {
-        // TODO
-    }
-
-    @PreAuthorize("hasRole('" + AMEntitlement.GATEWAY_ROUTE_PUSH + "')")
-    public void pushToSRA(final String key) {
-        // TODO
+        try {
+            NetworkService sra = serviceOps.get(NetworkService.Type.SRA);
+            HttpClient.newBuilder().build().sendAsync(
+                    HttpRequest.newBuilder(URI.create(
+                            StringUtils.appendIfMissing(sra.getAddress(), "/") + "management/routes/refresh")).
+                            header(HttpHeaders.AUTHORIZATION,
+                                    DefaultBasicAuthSupplier.getBasicAuthHeader(anonymousUser, anonymousKey)).
+                            POST(HttpRequest.BodyPublishers.noBody()).build(),
+                    HttpResponse.BodyHandlers.discarding());
+        } catch (KeymasterException e) {
+            throw new NotFoundException("Could not find any SRA instance", e);
+        }
     }
 
     @Override
diff --git a/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/GatewayRouteServiceImpl.java b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/GatewayRouteServiceImpl.java
index f02f0c1..545b630 100644
--- a/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/GatewayRouteServiceImpl.java
+++ b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/GatewayRouteServiceImpl.java
@@ -33,7 +33,7 @@ public class GatewayRouteServiceImpl extends AbstractServiceImpl implements Gate
 
     @Autowired
     private GatewayRouteLogic logic;
-    
+
     @Override
     public List<GatewayRouteTO> list() {
         return logic.list();
@@ -67,9 +67,4 @@ public class GatewayRouteServiceImpl extends AbstractServiceImpl implements Gate
     public void pushToSRA() {
         logic.pushToSRA();
     }
-
-    @Override
-    public void pushToSRA(final String key) {
-        logic.pushToSRA(key);
-    }
 }
diff --git a/core/idm/logic/src/main/java/org/apache/syncope/core/logic/init/IdMImplementationTypeLoader.java b/core/idm/logic/src/main/java/org/apache/syncope/core/logic/init/IdMImplementationTypeLoader.java
index b3a039c..1a64ee1 100644
--- a/core/idm/logic/src/main/java/org/apache/syncope/core/logic/init/IdMImplementationTypeLoader.java
+++ b/core/idm/logic/src/main/java/org/apache/syncope/core/logic/init/IdMImplementationTypeLoader.java
@@ -21,6 +21,7 @@ package org.apache.syncope.core.logic.init;
 import org.apache.syncope.common.lib.types.IdMImplementationType;
 import org.apache.syncope.common.lib.types.ImplementationTypesHolder;
 import org.apache.syncope.core.persistence.api.SyncopeCoreLoader;
+import org.springframework.core.Ordered;
 import org.springframework.stereotype.Component;
 
 @Component
@@ -28,7 +29,7 @@ public class IdMImplementationTypeLoader implements SyncopeCoreLoader {
 
     @Override
     public int getOrder() {
-        return Integer.MIN_VALUE;
+        return Ordered.HIGHEST_PRECEDENCE;
     }
 
     @Override
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
index f1c21a8..c594ecb 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
@@ -61,6 +61,7 @@ import org.apache.syncope.core.spring.security.JWTSSOProvider;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
+import org.springframework.core.Ordered;
 import org.springframework.core.type.filter.AssignableTypeFilter;
 import org.springframework.util.ClassUtils;
 
@@ -91,7 +92,7 @@ public class ClassPathScanImplementationLookup implements ImplementationLookup {
 
     @Override
     public int getOrder() {
-        return 250;
+        return Ordered.HIGHEST_PRECEDENCE;
     }
 
     /**
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/IdRepoImplementationTypeLoader.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/IdRepoImplementationTypeLoader.java
index 3735d1c..0eded04 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/IdRepoImplementationTypeLoader.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/IdRepoImplementationTypeLoader.java
@@ -21,6 +21,7 @@ package org.apache.syncope.core.logic.init;
 import org.apache.syncope.common.lib.types.IdRepoImplementationType;
 import org.apache.syncope.common.lib.types.ImplementationTypesHolder;
 import org.apache.syncope.core.persistence.api.SyncopeCoreLoader;
+import org.springframework.core.Ordered;
 import org.springframework.stereotype.Component;
 
 @Component
@@ -28,7 +29,7 @@ public class IdRepoImplementationTypeLoader implements SyncopeCoreLoader {
 
     @Override
     public int getOrder() {
-        return Integer.MIN_VALUE;
+        return Ordered.HIGHEST_PRECEDENCE;
     }
 
     @Override
diff --git a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/RestServiceExceptionMapper.java b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/RestServiceExceptionMapper.java
index d3b1b77..f5e6be1 100644
--- a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/RestServiceExceptionMapper.java
+++ b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/RestServiceExceptionMapper.java
@@ -103,10 +103,13 @@ public class RestServiceExceptionMapper implements ExceptionMapper<Exception> {
 
             builder = builder(ClientExceptionType.DelegatedAdministration, ExceptionUtils.getRootCauseMessage(ex));
         } else if (ex instanceof EntityExistsException || ex instanceof DuplicateException
-                || ex instanceof PersistenceException && ex.getCause() instanceof EntityExistsException) {
+                || ((ex instanceof PersistenceException || ex instanceof DataIntegrityViolationException)
+                && ex.getCause() instanceof EntityExistsException)) {
 
             builder = builder(ClientExceptionType.EntityExists,
-                    getPersistenceErrorMessage(ex instanceof PersistenceException ? ex.getCause() : ex));
+                    getPersistenceErrorMessage(
+                            ex instanceof PersistenceException || ex instanceof DataIntegrityViolationException
+                                    ? ex.getCause() : ex));
         } else if (ex instanceof DataIntegrityViolationException || ex instanceof UncategorizedDataAccessException) {
             builder = builder(ClientExceptionType.DataIntegrityViolation, getPersistenceErrorMessage(ex));
         } else if (ex instanceof ConnectorException) {
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/GatewayRoute.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/GatewayRoute.java
index 70f1e6e..4aef3f7 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/GatewayRoute.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/GatewayRoute.java
@@ -20,8 +20,8 @@ package org.apache.syncope.core.persistence.api.entity;
 
 import java.net.URI;
 import java.util.List;
-import org.apache.syncope.common.lib.types.GatewayFilter;
-import org.apache.syncope.common.lib.types.GatewayPredicate;
+import org.apache.syncope.common.lib.types.GatewayRouteFilter;
+import org.apache.syncope.common.lib.types.GatewayRoutePredicate;
 import org.apache.syncope.common.lib.types.GatewayRouteStatus;
 
 public interface GatewayRoute extends Entity {
@@ -30,17 +30,21 @@ public interface GatewayRoute extends Entity {
 
     void setName(String name);
 
+    int getOrder();
+
+    void setOrder(int order);
+
     URI getTarget();
 
     void setTarget(URI target);
 
-    List<GatewayFilter> getFilters();
+    List<GatewayRouteFilter> getFilters();
 
-    void setFilters(List<GatewayFilter> filters);
+    void setFilters(List<GatewayRouteFilter> filters);
 
-    List<GatewayPredicate> getPredicates();
+    List<GatewayRoutePredicate> getPredicates();
 
-    void setPredicates(List<GatewayPredicate> predicates);
+    void setPredicates(List<GatewayRoutePredicate> predicates);
 
     GatewayRouteStatus getStatus();
 
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/RuntimeDomainLoader.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/RuntimeDomainLoader.java
index ffc258d..36ba211 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/RuntimeDomainLoader.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/RuntimeDomainLoader.java
@@ -53,7 +53,7 @@ public class RuntimeDomainLoader implements DomainWatcher {
 
             ApplicationContextProvider.getApplicationContext().getBeansOfType(SyncopeCoreLoader.class).values().
                     stream().sorted(Comparator.comparing(SyncopeCoreLoader::getOrder)).
-                    forEach(loader -> {
+                    forEachOrdered(loader -> {
                         String loaderName = AopUtils.getTargetClass(loader).getName();
 
                         LOG.debug("[{}] Starting on domain '{}'", loaderName, domain);
@@ -72,7 +72,7 @@ public class RuntimeDomainLoader implements DomainWatcher {
 
             ApplicationContextProvider.getApplicationContext().getBeansOfType(SyncopeCoreLoader.class).values().
                     stream().sorted(Comparator.comparing(SyncopeCoreLoader::getOrder).reversed()).
-                    forEach(loader -> {
+                    forEachOrdered(loader -> {
                         String loaderName = AopUtils.getTargetClass(loader).getName();
 
                         LOG.debug("[{}] Starting on domain '{}'", loaderName, domain);
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/StartupDomainLoader.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/StartupDomainLoader.java
index 6364dd2..545c14c 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/StartupDomainLoader.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/StartupDomainLoader.java
@@ -69,7 +69,7 @@ public class StartupDomainLoader implements SyncopeCoreLoader {
 
     @Override
     public int getOrder() {
-        return Ordered.HIGHEST_PRECEDENCE;
+        return Ordered.LOWEST_PRECEDENCE;
     }
 
     @Override
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAGatewayRoute.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAGatewayRoute.java
index 8aee362..a6c4e1e 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAGatewayRoute.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAGatewayRoute.java
@@ -29,8 +29,8 @@ import javax.persistence.Enumerated;
 import javax.persistence.Lob;
 import javax.persistence.Table;
 import javax.validation.constraints.NotNull;
-import org.apache.syncope.common.lib.types.GatewayFilter;
-import org.apache.syncope.common.lib.types.GatewayPredicate;
+import org.apache.syncope.common.lib.types.GatewayRouteFilter;
+import org.apache.syncope.common.lib.types.GatewayRoutePredicate;
 import org.apache.syncope.common.lib.types.GatewayRouteStatus;
 import org.apache.syncope.core.persistence.api.entity.GatewayRoute;
 import org.apache.syncope.core.persistence.jpa.validation.entity.GatewayRouteCheck;
@@ -48,6 +48,9 @@ public class JPAGatewayRoute extends AbstractGeneratedKeyEntity implements Gatew
     @Column(unique = true, nullable = false)
     private String name;
 
+    private Integer routeOrder;
+
+    @NotNull
     private String target;
 
     @Lob
@@ -71,6 +74,16 @@ public class JPAGatewayRoute extends AbstractGeneratedKeyEntity implements Gatew
     }
 
     @Override
+    public int getOrder() {
+        return routeOrder == null ? 0 : routeOrder;
+    }
+
+    @Override
+    public void setOrder(final int order) {
+        this.routeOrder = order;
+    }
+
+    @Override
     public URI getTarget() {
         return URI.create(target);
     }
@@ -81,26 +94,26 @@ public class JPAGatewayRoute extends AbstractGeneratedKeyEntity implements Gatew
     }
 
     @Override
-    public List<GatewayFilter> getFilters() {
+    public List<GatewayRouteFilter> getFilters() {
         return filters == null
                 ? Collections.emptyList()
-                : Arrays.asList(POJOHelper.deserialize(filters, GatewayFilter[].class));
+                : Arrays.asList(POJOHelper.deserialize(filters, GatewayRouteFilter[].class));
     }
 
     @Override
-    public void setFilters(final List<GatewayFilter> filters) {
+    public void setFilters(final List<GatewayRouteFilter> filters) {
         this.filters = POJOHelper.serialize(filters);
     }
 
     @Override
-    public List<GatewayPredicate> getPredicates() {
+    public List<GatewayRoutePredicate> getPredicates() {
         return predicates == null
                 ? Collections.emptyList()
-                : Arrays.asList(POJOHelper.deserialize(predicates, GatewayPredicate[].class));
+                : Arrays.asList(POJOHelper.deserialize(predicates, GatewayRoutePredicate[].class));
     }
 
     @Override
-    public void setPredicates(final List<GatewayPredicate> predicates) {
+    public void setPredicates(final List<GatewayRoutePredicate> predicates) {
         this.predicates = POJOHelper.serialize(predicates);
     }
 
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/GatewayRouteTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/GatewayRouteTest.java
index 2bcc959..7fdab60 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/GatewayRouteTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/GatewayRouteTest.java
@@ -28,8 +28,8 @@ import java.util.List;
 import java.util.UUID;
 import javax.ws.rs.HttpMethod;
 import org.apache.syncope.common.lib.types.FilterFactory;
-import org.apache.syncope.common.lib.types.GatewayFilter;
-import org.apache.syncope.common.lib.types.GatewayPredicate;
+import org.apache.syncope.common.lib.types.GatewayRouteFilter;
+import org.apache.syncope.common.lib.types.GatewayRoutePredicate;
 import org.apache.syncope.common.lib.types.GatewayRouteStatus;
 import org.apache.syncope.common.lib.types.PredicateFactory;
 import org.apache.syncope.core.persistence.api.dao.GatewayRouteDAO;
@@ -68,9 +68,9 @@ public class GatewayRouteTest extends AbstractTest {
         GatewayRoute route = entityFactory.newEntity(GatewayRoute.class);
         route.setName("just for test");
         route.setTarget(URI.create("http://httpbin.org:80"));
-        route.setPredicates(Arrays.asList(new GatewayPredicate.Builder().
+        route.setPredicates(Arrays.asList(new GatewayRoutePredicate.Builder().
                 factory(PredicateFactory.METHOD).args(HttpMethod.GET).build()));
-        route.setFilters(Arrays.asList(new GatewayFilter.Builder().
+        route.setFilters(Arrays.asList(new GatewayRouteFilter.Builder().
                 factory(FilterFactory.ADD_REQUEST_HEADER).args("X-Request-Foo, Bar").build()));
         route.setStatus(GatewayRouteStatus.DRAFT);
 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GatewayRouteDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GatewayRouteDataBinderImpl.java
index ac7e563..0259510 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GatewayRouteDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/GatewayRouteDataBinderImpl.java
@@ -18,7 +18,11 @@
  */
 package org.apache.syncope.core.provisioning.java.data;
 
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.to.GatewayRouteTO;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.GatewayRouteStatus;
 import org.apache.syncope.core.persistence.api.entity.GatewayRoute;
 import org.apache.syncope.core.provisioning.api.data.GatewayRouteDataBinder;
 import org.springframework.stereotype.Component;
@@ -28,11 +32,23 @@ public class GatewayRouteDataBinderImpl implements GatewayRouteDataBinder {
 
     @Override
     public void getGatewayRoute(final GatewayRoute route, final GatewayRouteTO routeTO) {
+        SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.RequiredValuesMissing);
+        if (StringUtils.isBlank(routeTO.getName())) {
+            sce.getElements().add("name");
+        }
+        if (routeTO.getTarget() == null) {
+            sce.getElements().add("target");
+        }
+        if (!sce.isEmpty()) {
+            throw sce;
+        }
+
         route.setName(routeTO.getName());
+        route.setOrder(routeTO.getOrder());
         route.setTarget(routeTO.getTarget());
         route.setFilters(routeTO.getFilters());
         route.setPredicates(routeTO.getPredicates());
-        route.setStatus(routeTO.getStatus());
+        route.setStatus(routeTO.getStatus() == null ? GatewayRouteStatus.DRAFT : routeTO.getStatus());
     }
 
     @Override
@@ -40,6 +56,7 @@ public class GatewayRouteDataBinderImpl implements GatewayRouteDataBinder {
         GatewayRouteTO routeTO = new GatewayRouteTO();
         routeTO.setKey(route.getKey());
         routeTO.setName(route.getName());
+        routeTO.setOrder(route.getOrder());
         routeTO.setTarget(route.getTarget());
         routeTO.getFilters().addAll(route.getFilters());
         routeTO.getPredicates().addAll(route.getPredicates());
diff --git a/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreStartup.java b/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreStartup.java
index a1d130a..f93de92 100644
--- a/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreStartup.java
+++ b/core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreStartup.java
@@ -51,7 +51,7 @@ public class SyncopeCoreStartup extends SyncopeCoreStartStop
     public void onApplicationEvent(final ContextRefreshedEvent event) {
         event.getApplicationContext().getBeansOfType(SyncopeCoreLoader.class).values().stream().
                 sorted(Comparator.comparing(SyncopeCoreLoader::getOrder)).
-                forEach(loader -> {
+                forEachOrdered(loader -> {
                     String loaderName = AopUtils.getTargetClass(loader).getName();
 
                     LOG.debug("[{}] Starting initialization", loaderName);
diff --git a/docker/sra/pom.xml b/docker/sra/pom.xml
index 2866259..6a80959 100644
--- a/docker/sra/pom.xml
+++ b/docker/sra/pom.xml
@@ -42,26 +42,36 @@ under the License.
       <groupId>org.apache.syncope</groupId>
       <artifactId>syncope-sra</artifactId>
       <version>${project.version}</version>
-      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.syncope.ext.self-keymaster</groupId>
+      <artifactId>syncope-ext-self-keymaster-client</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.common.keymaster</groupId>
+      <artifactId>syncope-common-keymaster-client-zookeeper</artifactId>
+      <version>${project.version}</version>
     </dependency>
   </dependencies>
 
   <build>
     <plugins>
       <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-antrun-plugin</artifactId>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-maven-plugin</artifactId>
+        <configuration>
+          <mainClass>org.apache.syncope.sra.SyncopeSRAApplication</mainClass>
+          <layout>ZIP</layout>
+        </configuration>
         <executions>
           <execution>
             <goals>
-              <goal>run</goal>
+              <goal>repackage</goal>
             </goals>
-            <phase>package</phase>
             <configuration>
-              <target>
-                <copy file="${settings.localRepository}/org/apache/syncope/syncope-sra/${syncope.version}/syncope-sra-${syncope.version}.jar"
-                      todir="${project.build.outputDirectory}"/>
-              </target>
+              <outputDirectory>${project.build.outputDirectory}</outputDirectory>
             </configuration>
           </execution>
         </executions>
@@ -105,14 +115,6 @@ under the License.
         <directory>src/main/resources</directory>
         <filtering>true</filtering>
       </resource>
-
-      <resource>
-        <directory>${project.basedir}/../../sra/src/main/resources</directory>
-        <includes>
-          <include>application.properties</include>
-        </includes>
-        <filtering>true</filtering>
-      </resource>
     </resources>
   </build>
 
diff --git a/docker/sra/src/main/resources/Dockerfile b/docker/sra/src/main/resources/Dockerfile
index 5651fc0..c7398f3 100644
--- a/docker/sra/src/main/resources/Dockerfile
+++ b/docker/sra/src/main/resources/Dockerfile
@@ -26,10 +26,10 @@ RUN mkdir /opt/syncope/conf
 RUN mkdir /opt/syncope/lib
 RUN mkdir /opt/syncope/log
 
-COPY application.properties /opt/syncope/conf/
+COPY *.properties /opt/syncope/conf/
 COPY log4j2.xml /opt/syncope/conf/
 
-COPY syncope-sra-*jar /opt/syncope/lib/syncope-sra.jar
+COPY syncope-docker-sra-*jar /opt/syncope/lib/syncope-sra.jar
 
 COPY startup.sh /opt/syncope/bin
 RUN chmod 755 /opt/syncope/bin/startup.sh
diff --git a/sra/src/main/resources/application.properties b/docker/sra/src/main/resources/application.properties
similarity index 85%
copy from sra/src/main/resources/application.properties
copy to docker/sra/src/main/resources/application.properties
index 98bc964..b6b5147 100644
--- a/sra/src/main/resources/application.properties
+++ b/docker/sra/src/main/resources/application.properties
@@ -20,13 +20,9 @@ spring.main.banner-mode=log
 
 server.port=8080
 
-management.endpoint.gateway.enabled=true
-management.endpoints.web.exposure.include=gateway
-
 spring.cloud.gateway.metrics.enabled=true
 management.endpoint.metrics.enabled=true
-management.endpoints.web.exposure.include=*
 management.endpoint.prometheus.enabled=true
 management.metrics.export.prometheus.enabled=true
 
-service.discovery.address=http://localhost:8080/
+service.discovery.address=${SERVICE_DISCOVERY_ADDRESS}
diff --git a/sra/src/main/resources/sra.properties b/docker/sra/src/main/resources/keymaster.properties
similarity index 86%
copy from sra/src/main/resources/sra.properties
copy to docker/sra/src/main/resources/keymaster.properties
index 6789cfd..14e8ca6 100644
--- a/sra/src/main/resources/sra.properties
+++ b/docker/sra/src/main/resources/keymaster.properties
@@ -14,9 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-sra.directory=${conf.directory}
-
-anonymousUser=${anonymousUser}
-anonymousKey=${anonymousKey}
-
-useGZIPCompression=true
+keymaster.address=${KEYMASTER_ADDRESS}
+keymaster.username=${KEYMASTER_USERNAME}
+keymaster.password=${KEYMASTER_PASSWORD}
diff --git a/docker/sra/src/main/resources/log4j2.xml b/docker/sra/src/main/resources/log4j2.xml
index 033f1ed..6fdccd1 100644
--- a/docker/sra/src/main/resources/log4j2.xml
+++ b/docker/sra/src/main/resources/log4j2.xml
@@ -32,11 +32,23 @@ under the License.
     <asyncLogger name="org.apache.syncope.client.lib" additivity="false" level="OFF">
       <appender-ref ref="console"/>
     </asyncLogger>
-
     <asyncLogger name="org.apache.syncope.sra" additivity="false" level="INFO">
       <appender-ref ref="console"/>
     </asyncLogger>
-    
+
+    <asyncLogger name="org.apache.cxf" additivity="false" level="ERROR">
+      <appender-ref ref="console"/>
+    </asyncLogger>
+
+    <asyncLogger name="org.springframework.cloud.gateway" additivity="false" level="INFO">
+      <appender-ref ref="console"/>
+    </asyncLogger>
+
+    <!-- Requires -Dreactor.netty.http.server.accessLogEnabled=true to work-->
+    <asyncLogger name="reactor.netty.http.server.AccessLog" additivity="false" level="INFO">
+      <appender-ref ref="console"/>
+    </asyncLogger>
+
     <root level="INFO">
       <appender-ref ref="console"/>
     </root>
diff --git a/sra/src/main/resources/sra.properties b/docker/sra/src/main/resources/sra.properties
similarity index 96%
copy from sra/src/main/resources/sra.properties
copy to docker/sra/src/main/resources/sra.properties
index 6789cfd..77cb93e 100644
--- a/sra/src/main/resources/sra.properties
+++ b/docker/sra/src/main/resources/sra.properties
@@ -14,8 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-sra.directory=${conf.directory}
-
 anonymousUser=${anonymousUser}
 anonymousKey=${anonymousKey}
 
diff --git a/docker/sra/src/main/resources/startup.sh b/docker/sra/src/main/resources/startup.sh
index 69093c3..df42979 100755
--- a/docker/sra/src/main/resources/startup.sh
+++ b/docker/sra/src/main/resources/startup.sh
@@ -19,4 +19,5 @@
 
 export LOADER_PATH="/opt/syncope/conf,/opt/syncope/lib"
 java -Dfile.encoding=UTF-8 -server -Xms1536m -Xmx1536m -XX:NewSize=256m -XX:MaxNewSize=256m \
- -XX:+DisableExplicitGC -Djava.security.egd=file:/dev/./urandom -jar /opt/syncope/lib/syncope-sra.jar
+ -XX:+DisableExplicitGC -Djava.security.egd=file:/dev/./urandom \
+ -Dreactor.netty.http.server.accessLogEnabled=true -jar /opt/syncope/lib/syncope-sra.jar
diff --git a/docker/src/main/resources/docker-compose/docker-compose-zookeeper.yml b/docker/src/main/resources/docker-compose/docker-compose-all.yml
similarity index 82%
rename from docker/src/main/resources/docker-compose/docker-compose-zookeeper.yml
rename to docker/src/main/resources/docker-compose/docker-compose-all.yml
index 2d216f6..1ed6be1 100644
--- a/docker/src/main/resources/docker-compose/docker-compose-zookeeper.yml
+++ b/docker/src/main/resources/docker-compose/docker-compose-all.yml
@@ -15,7 +15,7 @@
 # specific language governing permissions and limitations
 # under the License.
 
-# Full deployment (Core, Console, Enduser) on PostgreSQL, with Keymaster on Zookeeper
+# Full deployment (Core, Console, Enduser, SRA) on PostgreSQL, with Keymaster on Zookeeper
 
 # Zookeeper is configured without JAAS, hence empty KEYMASTER_USERNAME / KEYMASTER_PASSWORD
 # are passed to other containers
@@ -38,6 +38,7 @@ services:
    syncope:
      depends_on:
        - db
+       - keymaster
      image: apache/syncope:${SYNCOPE_VERSION}
      ports:
        - "18080:8080"
@@ -58,6 +59,7 @@ services:
    syncope-console:
      depends_on:
        - syncope
+       - keymaster
      image: apache/syncope-console:${SYNCOPE_VERSION}
      ports:
        - "28080:8080"
@@ -71,6 +73,7 @@ services:
    syncope-enduser:
      depends_on:
        - syncope
+       - keymaster
      image: apache/syncope-enduser:${SYNCOPE_VERSION}
      ports:
        - "38080:8080"
@@ -80,3 +83,17 @@ services:
        KEYMASTER_USERNAME: ${KEYMASTER_USERNAME:-}
        KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD:-}
        SERVICE_DISCOVERY_ADDRESS: http://syncope-enduser:8080/syncope-enduser/
+
+   syncope-sra:
+     depends_on:
+       - syncope
+       - keymaster
+     image: apache/syncope-sra:${SYNCOPE_VERSION}
+     ports:
+       - "48080:8080"
+     restart: always
+     environment:
+       KEYMASTER_ADDRESS: keymaster:2181
+       KEYMASTER_USERNAME: ${KEYMASTER_USERNAME:-}
+       KEYMASTER_PASSWORD: ${KEYMASTER_PASSWORD:-}
+       SERVICE_DISCOVERY_ADDRESS: http://syncope-sra:8080/
diff --git a/fit/console-reference/pom.xml b/fit/console-reference/pom.xml
index d5ee498..1971cc9 100644
--- a/fit/console-reference/pom.xml
+++ b/fit/console-reference/pom.xml
@@ -291,8 +291,7 @@ under the License.
                     -Dspring.profiles.active=embedded
                     -javaagent:${java.home}/lib/hotswap/hotswap-agent.jar=autoHotswap=true,disablePlugin=Spring,disablePlugin=Hibernate,disablePlugin=CxfJAXRS
                     -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000
-                    -XX:+CMSClassUnloadingEnabled
-                    -Xmx1024m -Xms512m</cargo.jvmargs>
+                    -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC -Xmx1024m -Xms512m</cargo.jvmargs>
                 </properties>
               </configuration>
             </configuration>
diff --git a/fit/core-reference/pom.xml b/fit/core-reference/pom.xml
index 039d1f74..0f6bf3b 100644
--- a/fit/core-reference/pom.xml
+++ b/fit/core-reference/pom.xml
@@ -1626,7 +1626,7 @@ under the License.
                     -Dspring.profiles.active=embedded
                     -javaagent:${java.home}/lib/hotswap/hotswap-agent.jar=autoHotswap=true,disablePlugin=Spring,disablePlugin=Hibernate
                     -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000
-                    -XX:+CMSClassUnloadingEnabled -XX:+UseG1GC -Xmx1024m -Xms512m</cargo.jvmargs>
+                    -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC -Xmx1024m -Xms512m</cargo.jvmargs>
                 </properties>
               </configuration>
             </configuration>
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/GatewayRouteITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/GatewayRouteITCase.java
index 5d4f8c4..4994066 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/GatewayRouteITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/GatewayRouteITCase.java
@@ -32,8 +32,8 @@ import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.to.GatewayRouteTO;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.FilterFactory;
-import org.apache.syncope.common.lib.types.GatewayFilter;
-import org.apache.syncope.common.lib.types.GatewayPredicate;
+import org.apache.syncope.common.lib.types.GatewayRouteFilter;
+import org.apache.syncope.common.lib.types.GatewayRoutePredicate;
 import org.apache.syncope.common.lib.types.GatewayRouteStatus;
 import org.apache.syncope.common.lib.types.PredicateFactory;
 import org.apache.syncope.common.rest.api.service.GatewayRouteService;
@@ -69,9 +69,9 @@ public class GatewayRouteITCase extends AbstractITCase {
         GatewayRouteTO route = new GatewayRouteTO();
         route.setName("just for test");
         route.setTarget(URI.create("http://httpbin.org:80"));
-        route.getPredicates().add(new GatewayPredicate.Builder().
+        route.getPredicates().add(new GatewayRoutePredicate.Builder().
                 factory(PredicateFactory.METHOD).args(HttpMethod.GET).build());
-        route.getFilters().add(new GatewayFilter.Builder().
+        route.getFilters().add(new GatewayRouteFilter.Builder().
                 factory(FilterFactory.ADD_REQUEST_HEADER).args("X-Request-Foo, Bar").build());
         route.setStatus(GatewayRouteStatus.DRAFT);
 
@@ -103,4 +103,48 @@ public class GatewayRouteITCase extends AbstractITCase {
         int endCount = gatewayRouteService.list().size();
         assertEquals(endCount, beforeCount);
     }
+
+    @Test
+    public void exceptions() {
+        GatewayRouteTO route = new GatewayRouteTO();
+        try {
+            gatewayRouteService.create(route);
+            fail();
+        } catch (SyncopeClientException e) {
+            assertEquals(ClientExceptionType.RequiredValuesMissing, e.getType());
+        }
+
+        route.setName("createException");
+        try {
+            gatewayRouteService.create(route);
+            fail();
+        } catch (SyncopeClientException e) {
+            assertEquals(ClientExceptionType.RequiredValuesMissing, e.getType());
+        }
+
+        route.setTarget(URI.create("http://httpbin.org:80"));
+        Response response = gatewayRouteService.create(route);
+        assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatusInfo().getStatusCode());
+
+        try {
+            gatewayRouteService.create(route);
+            fail();
+        } catch (SyncopeClientException e) {
+            assertEquals(ClientExceptionType.EntityExists, e.getType());
+        }
+
+        route.setKey(UUID.randomUUID().toString());
+        try {
+            gatewayRouteService.update(route);
+            fail();
+        } catch (SyncopeClientException e) {
+            assertEquals(ClientExceptionType.NotFound, e.getType());
+        }
+        try {
+            gatewayRouteService.delete(route.getKey());
+            fail();
+        } catch (SyncopeClientException e) {
+            assertEquals(ClientExceptionType.NotFound, e.getType());
+        }
+    }
 }
diff --git a/fit/enduser-reference/pom.xml b/fit/enduser-reference/pom.xml
index 8cd5fe2..9bf0e73 100644
--- a/fit/enduser-reference/pom.xml
+++ b/fit/enduser-reference/pom.xml
@@ -282,8 +282,7 @@ under the License.
                     -Dspring.profiles.active=embedded
                     -javaagent:${java.home}/lib/hotswap/hotswap-agent.jar=autoHotswap=true,disablePlugin=Spring,disablePlugin=Hibernate,disablePlugin=CxfJAXRS
                     -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000
-                    -XX:+CMSClassUnloadingEnabled
-                    -Xmx1024m -Xms512m</cargo.jvmargs>
+                    -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC -Xmx1024m -Xms512m</cargo.jvmargs>
                 </properties>
               </configuration>
             </configuration>
diff --git a/pom.xml b/pom.xml
index 1de07d5..639d09a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -396,6 +396,7 @@ under the License.
     <spring.version>5.1.7.RELEASE</spring.version>
     <spring-security.version>5.1.5.RELEASE</spring-security.version>
     <spring-boot.version>2.1.5.RELEASE</spring-boot.version>
+    <spring-cloud-gateway.version>2.1.1.RELEASE</spring-cloud-gateway.version>
 
     <openjpa.version>3.1.0</openjpa.version>
     <hikaricp.version>3.3.1</hikaricp.version>
@@ -1035,10 +1036,13 @@ under the License.
 
       <dependency>
         <groupId>org.springframework.cloud</groupId>
-        <artifactId>spring-cloud-dependencies</artifactId>
-        <version>Greenwich.SR1</version>
-        <type>pom</type>
-        <scope>import</scope>
+        <artifactId>spring-cloud-starter-gateway</artifactId>
+        <version>${spring-cloud-gateway.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.springframework.cloud</groupId>
+        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
+        <version>${spring-cloud-gateway.version}</version>
       </dependency>
 
       <dependency>
@@ -1831,6 +1835,17 @@ under the License.
         </exclusions>
       </dependency>
       <dependency>
+        <groupId>org.apache.curator</groupId>
+        <artifactId>curator-x-discovery</artifactId>
+        <version>${curator.version}</version>
+        <exclusions>
+          <exclusion>
+            <groupId>org.apache.zookeeper</groupId>
+            <artifactId>zookeeper</artifactId>
+          </exclusion>
+        </exclusions>
+      </dependency>
+      <dependency>
         <groupId>org.apache.zookeeper</groupId>
         <artifactId>zookeeper</artifactId>
         <version>${zookeeper.version}</version>
@@ -1848,6 +1863,24 @@ under the License.
 
       <!-- TEST -->
       <dependency>
+        <groupId>org.apache.curator</groupId>
+        <artifactId>curator-test</artifactId>
+        <version>2.13.0</version>
+        <scope>test</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.cxf</groupId>
+        <artifactId>cxf-rt-transports-http-netty-server</artifactId>
+        <version>${cxf.version}</version>
+        <scope>test</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.springframework.cloud</groupId>
+        <artifactId>spring-cloud-contract-wiremock</artifactId>
+        <version>${spring-cloud-gateway.version}</version>
+        <scope>test</scope>
+      </dependency>
+      <dependency>
         <groupId>org.bouncycastle</groupId>
         <artifactId>bcpkix-jdk15on</artifactId>
         <version>1.61</version>
diff --git a/sra/pom.xml b/sra/pom.xml
index b9df9a8..5b0d595 100644
--- a/sra/pom.xml
+++ b/sra/pom.xml
@@ -43,18 +43,18 @@ under the License.
       <artifactId>spring-cloud-starter-gateway</artifactId>
     </dependency>
     <dependency>
-      <groupId>org.springframework.cloud</groupId>
-      <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.springframework.cloud</groupId>
-      <artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-security</artifactId>
       <exclusions>
         <exclusion>
-          <artifactId>spring-boot-starter-web</artifactId>
           <groupId>org.springframework.boot</groupId>
+          <artifactId>spring-boot-starter-logging</artifactId>
         </exclusion>
-      </exclusions>
+      </exclusions>    
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.cloud</groupId>
+      <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
     </dependency>
     <dependency>
       <groupId>org.springframework.boot</groupId>
@@ -77,13 +77,8 @@ under the License.
     </dependency>
 
     <dependency>
-      <groupId>org.apache.syncope.ext.self-keymaster</groupId>
-      <artifactId>syncope-ext-self-keymaster-client</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
       <groupId>org.apache.syncope.common.keymaster</groupId>
-      <artifactId>syncope-common-keymaster-client-zookeeper</artifactId>
+      <artifactId>syncope-common-keymaster-client-api</artifactId>
       <version>${project.version}</version>
     </dependency>
 
@@ -124,6 +119,33 @@ under the License.
     </dependency>
 
     <dependency>
+      <groupId>org.apache.syncope.common.keymaster</groupId>
+      <artifactId>syncope-common-keymaster-client-zookeeper</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.curator</groupId>
+      <artifactId>curator-test</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.cxf</groupId>
+      <artifactId>cxf-rt-transports-http-netty-server</artifactId>
+      <exclusions>
+        <exclusion>
+          <groupId>org.apache.geronimo.specs</groupId>
+          <artifactId>geronimo-servlet_2.5_spec</artifactId>
+        </exclusion>
+      </exclusions>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.cloud</groupId>
+      <artifactId>spring-cloud-contract-wiremock</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-test</artifactId>
       <scope>test</scope>
@@ -138,18 +160,14 @@ under the License.
   <build>
     <plugins>
       <plugin>
-        <groupId>org.springframework.boot</groupId>
-        <artifactId>spring-boot-maven-plugin</artifactId>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <inherited>true</inherited>
         <configuration>
-          <mainClass>org.apache.syncope.sra.SyncopeSRAApplication</mainClass>
+          <systemPropertyVariables>
+            <reactor.netty.http.server.accessLogEnabled>true</reactor.netty.http.server.accessLogEnabled>
+          </systemPropertyVariables>
         </configuration>
-        <executions>
-          <execution>
-            <goals>
-              <goal>repackage</goal>
-            </goals>
-          </execution>
-        </executions>
       </plugin>
     </plugins>
     
@@ -158,7 +176,6 @@ under the License.
         <directory>src/main/resources</directory>
         <filtering>true</filtering>
       </resource>
-
       <resource>
         <directory>${basedir}/../src/main/resources</directory>
         <filtering>true</filtering>
@@ -167,10 +184,131 @@ under the License.
         </includes>
       </resource>
     </resources>
+    
+    <testResources>
+      <testResource>
+        <directory>${basedir}/src/test/resources</directory>
+        <filtering>true</filtering>
+      </testResource>
+    </testResources>
   </build>
 
   <profiles>
     <profile>
+      <id>debug</id>
+
+      <properties>
+        <skipTests>true</skipTests>
+      </properties>
+
+      <dependencies>
+        <dependency>
+          <groupId>org.apache.syncope.common.keymaster</groupId>
+          <artifactId>syncope-common-keymaster-client-zookeeper</artifactId>
+          <version>${project.version}</version>
+          <scope>compile</scope>
+        </dependency>
+        <dependency>
+          <groupId>org.apache.curator</groupId>
+          <artifactId>curator-test</artifactId>
+          <scope>compile</scope>
+        </dependency>
+        <dependency>
+          <groupId>org.apache.cxf</groupId>
+          <artifactId>cxf-rt-transports-http-netty-server</artifactId>
+          <scope>compile</scope>
+        </dependency>
+      </dependencies>
+
+      <build>
+        <defaultGoal>clean package io.fabric8:docker-maven-plugin:start spring-boot:run</defaultGoal>
+
+        <plugins>
+          <plugin>
+            <groupId>io.fabric8</groupId>
+            <artifactId>docker-maven-plugin</artifactId>
+            <configuration>
+              <images>
+                <image>
+                  <name>zookeeper:${zookeeper.version}</name>
+                  <run>
+                    <ports>
+                      <port>2181:2181</port>
+                    </ports>
+                    <volumes>
+                      <bind>
+                        <volume>${project.build.testOutputDirectory}/zoo.cfg:/conf/zoo.cfg</volume>
+                        <volume>${project.build.testOutputDirectory}/java.env:/conf/java.env</volume>
+                        <volume>${project.build.testOutputDirectory}/server-jaas.conf:/conf/server-jaas.conf</volume>
+                        <volume>${project.build.testOutputDirectory}/client-jaas.conf:/conf/client-jaas.conf</volume>
+                      </bind>
+                    </volumes>
+                  </run>
+                </image>
+              </images>
+            </configuration>
+          </plugin>
+
+          <plugin>
+            <groupId>org.codehaus.mojo</groupId>
+            <artifactId>build-helper-maven-plugin</artifactId>
+            <executions>
+              <execution>
+                <goals>
+                  <goal>add-source</goal>
+                </goals>
+                <configuration>
+                  <sources>
+                    <source>${basedir}/src/test/java</source>
+                  </sources>
+                </configuration>
+              </execution>
+            </executions>
+          </plugin>
+
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-compiler-plugin</artifactId>
+            <configuration>
+              <excludes>
+                <exclude>**/org/apache/syncope/sra/**Test.java</exclude>
+                <exclude>**/org/apache/syncope/sra/**Keymaster*.java</exclude>
+              </excludes>
+            </configuration>
+          </plugin>
+
+          <plugin>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-maven-plugin</artifactId>
+            <configuration>
+              <mainClass>org.apache.syncope.sra.SyncopeSRAApplication</mainClass>
+              <systemPropertyVariables>
+                <reactor.netty.http.server.accessLogEnabled>true</reactor.netty.http.server.accessLogEnabled>
+              </systemPropertyVariables>
+              <jvmArguments>
+                -Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n
+              </jvmArguments>
+            </configuration>
+          </plugin>
+        </plugins>
+        
+        <resources>
+          <resource>
+            <directory>${basedir}/src/test/resources</directory>
+            <filtering>true</filtering>
+          </resource>
+        </resources>
+
+        <testResources>
+          <testResource>
+            <directory>${basedir}/../common/keymaster/client-zookeeper/src/main/resources</directory>
+            <filtering>true</filtering>
+          </testResource>
+        </testResources>
+      </build>
+    </profile>
+
+    <profile>
       <id>site</id>
 
       <build>
diff --git a/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAStartup.java b/sra/src/main/java/org/apache/syncope/sra/CustomGatewayFilterFactory.java
similarity index 54%
copy from sra/src/main/java/org/apache/syncope/sra/SyncopeSRAStartup.java
copy to sra/src/main/java/org/apache/syncope/sra/CustomGatewayFilterFactory.java
index 08ea447..7ac2cd8 100644
--- a/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAStartup.java
+++ b/sra/src/main/java/org/apache/syncope/sra/CustomGatewayFilterFactory.java
@@ -18,14 +18,32 @@
  */
 package org.apache.syncope.sra;
 
-import org.springframework.context.ApplicationListener;
-import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.cloud.gateway.filter.GatewayFilter;
+import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
 
-public class SyncopeSRAStartup extends SyncopeSRAStartStop
-        implements ApplicationListener<ContextRefreshedEvent> {
+/**
+ * Base class for custom gateway filter factories.
+ */
+public abstract class CustomGatewayFilterFactory
+        extends AbstractGatewayFilterFactory<CustomGatewayFilterFactory.Config> {
 
-    @Override
-    public void onApplicationEvent(final ContextRefreshedEvent event) {
-        serviceOps.register(getNetworkService());
+    public static class Config {
+
+        private String data;
+
+        public String getData() {
+            return data;
+        }
+
+        public void setData(final String data) {
+            this.data = data;
+        }
     }
+
+    public CustomGatewayFilterFactory() {
+        super(CustomGatewayFilterFactory.Config.class);
+    }
+
+    @Override
+    public abstract GatewayFilter apply(Config config);
 }
diff --git a/sra/src/main/java/org/apache/syncope/sra/CustomRoutePredicateFactory.java b/sra/src/main/java/org/apache/syncope/sra/CustomRoutePredicateFactory.java
new file mode 100644
index 0000000..22d9299
--- /dev/null
+++ b/sra/src/main/java/org/apache/syncope/sra/CustomRoutePredicateFactory.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;
+
+import java.util.function.Predicate;
+import org.springframework.cloud.gateway.handler.AsyncPredicate;
+import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
+import org.springframework.web.server.ServerWebExchange;
+
+/**
+ * Base class for custom predicate factories.
+ */
+public abstract class CustomRoutePredicateFactory
+        extends AbstractRoutePredicateFactory<CustomRoutePredicateFactory.Config> {
+
+    public static class Config {
+
+        private String data;
+
+        public String getData() {
+            return data;
+        }
+
+        public void setData(final String data) {
+            this.data = data;
+        }
+    }
+
+    public CustomRoutePredicateFactory() {
+        super(CustomRoutePredicateFactory.Config.class);
+    }
+
+    @Override
+    public abstract AsyncPredicate<ServerWebExchange> applyAsync(Config config);
+
+    @Override
+    public Predicate<ServerWebExchange> apply(final Config config) {
+        throw new UnsupportedOperationException(getClass().getName() + " is only async.");
+    }
+}
diff --git a/sra/src/main/java/org/apache/syncope/sra/ManagementController.java b/sra/src/main/java/org/apache/syncope/sra/ManagementController.java
new file mode 100644
index 0000000..7d15556
--- /dev/null
+++ b/sra/src/main/java/org/apache/syncope/sra/ManagementController.java
@@ -0,0 +1,93 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cloud.gateway.route.Route;
+import org.springframework.cloud.gateway.route.RouteDefinition;
+import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
+import org.springframework.cloud.gateway.route.RouteLocator;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import reactor.core.publisher.Mono;
+
+@RestController
+@RequestMapping(path = "/management")
+public class ManagementController {
+
+    @Autowired
+    private RouteRefresher routeRefresher;
+
+    @Autowired
+    private RouteDefinitionLocator routeDefinitionLocator;
+
+    @Autowired
+    private RouteLocator routeLocator;
+
+    @PostMapping("/routes/refresh")
+    public Mono<Void> refresh() {
+        routeRefresher.refresh();
+        return Mono.empty();
+    }
+
+    @GetMapping("/routes")
+    public Mono<List<Map<String, Object>>> routes() {
+        Mono<Map<String, RouteDefinition>> routeDefs =
+                routeDefinitionLocator.getRouteDefinitions().collectMap(RouteDefinition::getId);
+        Mono<List<Route>> routes = routeLocator.getRoutes().collectList();
+        return Mono.zip(routeDefs, routes).map(tuple -> {
+            Map<String, RouteDefinition> defs = tuple.getT1();
+            List<Route> routeList = tuple.getT2();
+            List<Map<String, Object>> allRoutes = new ArrayList<>();
+
+            routeList.forEach(route -> {
+                Map<String, Object> r = new HashMap<>();
+                r.put("route_id", route.getId());
+                r.put("order", route.getOrder());
+
+                if (defs.containsKey(route.getId())) {
+                    r.put("route_definition", defs.get(route.getId()));
+                } else {
+                    Map<String, Object> obj = new HashMap<>();
+
+                    obj.put("predicate", route.getPredicate().toString());
+
+                    if (!route.getFilters().isEmpty()) {
+                        obj.put("filters",
+                                route.getFilters().stream().map(Object::toString).collect(Collectors.toList()));
+                    }
+
+                    if (!obj.isEmpty()) {
+                        r.put("route_object", obj);
+                    }
+                }
+                allRoutes.add(r);
+            });
+
+            return allRoutes;
+        });
+    }
+}
diff --git a/sra/src/main/java/org/apache/syncope/sra/RouteProvider.java b/sra/src/main/java/org/apache/syncope/sra/RouteProvider.java
new file mode 100644
index 0000000..709705a
--- /dev/null
+++ b/sra/src/main/java/org/apache/syncope/sra/RouteProvider.java
@@ -0,0 +1,472 @@
+/*
+ * 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;
+
+import java.time.ZonedDateTime;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.BooleanUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.lib.AnonymousAuthenticationHandler;
+import org.apache.syncope.client.lib.SyncopeClient;
+import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
+import org.apache.syncope.common.keymaster.client.api.ServiceOps;
+import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
+import org.apache.syncope.common.lib.to.GatewayRouteTO;
+import org.apache.syncope.common.lib.types.GatewayRouteFilter;
+import org.apache.syncope.common.lib.types.GatewayRoutePredicate;
+import org.apache.syncope.common.lib.types.GatewayRouteStatus;
+import org.apache.syncope.common.rest.api.service.GatewayRouteService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.cloud.gateway.filter.GatewayFilter;
+import org.springframework.cloud.gateway.filter.OrderedGatewayFilter;
+import org.springframework.cloud.gateway.filter.factory.AddRequestHeaderGatewayFilterFactory;
+import org.springframework.cloud.gateway.filter.factory.AddRequestParameterGatewayFilterFactory;
+import org.springframework.cloud.gateway.filter.factory.AddResponseHeaderGatewayFilterFactory;
+import org.springframework.cloud.gateway.filter.factory.FallbackHeadersGatewayFilterFactory;
+import org.springframework.cloud.gateway.filter.factory.HystrixGatewayFilterFactory;
+import org.springframework.cloud.gateway.filter.factory.PrefixPathGatewayFilterFactory;
+import org.springframework.cloud.gateway.filter.factory.PreserveHostHeaderGatewayFilterFactory;
+import org.springframework.cloud.gateway.filter.factory.RedirectToGatewayFilterFactory;
+import org.springframework.cloud.gateway.filter.factory.RemoveRequestHeaderGatewayFilterFactory;
+import org.springframework.cloud.gateway.filter.factory.RemoveResponseHeaderGatewayFilterFactory;
+import org.springframework.cloud.gateway.filter.factory.RequestHeaderToRequestUriGatewayFilterFactory;
+import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory;
+import org.springframework.cloud.gateway.filter.factory.RequestSizeGatewayFilterFactory;
+import org.springframework.cloud.gateway.filter.factory.RetryGatewayFilterFactory;
+import org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory;
+import org.springframework.cloud.gateway.filter.factory.RewriteResponseHeaderGatewayFilterFactory;
+import org.springframework.cloud.gateway.filter.factory.SaveSessionGatewayFilterFactory;
+import org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory;
+import org.springframework.cloud.gateway.filter.factory.SetPathGatewayFilterFactory;
+import org.springframework.cloud.gateway.filter.factory.SetRequestHeaderGatewayFilterFactory;
+import org.springframework.cloud.gateway.filter.factory.SetResponseHeaderGatewayFilterFactory;
+import org.springframework.cloud.gateway.filter.factory.SetStatusGatewayFilterFactory;
+import org.springframework.cloud.gateway.filter.factory.StripPrefixGatewayFilterFactory;
+import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
+import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter;
+import org.springframework.cloud.gateway.handler.AsyncPredicate;
+import org.springframework.cloud.gateway.handler.predicate.AfterRoutePredicateFactory;
+import org.springframework.cloud.gateway.handler.predicate.BeforeRoutePredicateFactory;
+import org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactory;
+import org.springframework.cloud.gateway.handler.predicate.CookieRoutePredicateFactory;
+import org.springframework.cloud.gateway.handler.predicate.HeaderRoutePredicateFactory;
+import org.springframework.cloud.gateway.handler.predicate.HostRoutePredicateFactory;
+import org.springframework.cloud.gateway.handler.predicate.MethodRoutePredicateFactory;
+import org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory;
+import org.springframework.cloud.gateway.handler.predicate.QueryRoutePredicateFactory;
+import org.springframework.cloud.gateway.handler.predicate.RemoteAddrRoutePredicateFactory;
+import org.springframework.cloud.gateway.route.Route;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.Ordered;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+
+@Component
+public class RouteProvider {
+
+    private static final Logger LOG = LoggerFactory.getLogger(RouteProvider.class);
+
+    @Autowired
+    private ServiceOps serviceOps;
+
+    @Autowired
+    private ConfigurableApplicationContext ctx;
+
+    @Value("${anonymousUser}")
+    private String anonymousUser;
+
+    @Value("${anonymousKey}")
+    private String anonymousKey;
+
+    @Value("${useGZIPCompression}")
+    private boolean useGZIPCompression;
+
+    private SyncopeClient client;
+
+    @SuppressWarnings("unchecked")
+    private GatewayFilter toFilter(final String routeId, final GatewayRouteFilter gwfilter)
+            throws ClassNotFoundException {
+
+        GatewayFilter filter;
+
+        switch (gwfilter.getFactory()) {
+            case ADD_REQUEST_HEADER:
+                String[] addRequestHeaderArgs = gwfilter.getArgs().split(",");
+                filter = ctx.getBean(AddRequestHeaderGatewayFilterFactory.class).
+                        apply(c -> c.setName(addRequestHeaderArgs[0].trim()).
+                        setValue(addRequestHeaderArgs[1].trim()));
+                break;
+
+            case ADD_REQUEST_PARAMETER:
+                String[] addRequestParameterArgs = gwfilter.getArgs().split(",");
+                filter = ctx.getBean(AddRequestParameterGatewayFilterFactory.class).
+                        apply(c -> c.setName(addRequestParameterArgs[0].trim()).
+                        setValue(addRequestParameterArgs[1].trim()));
+                break;
+
+            case ADD_RESPONSE_HEADER:
+                String[] addResponseHeaderArgs = gwfilter.getArgs().split(",");
+                filter = ctx.getBean(AddResponseHeaderGatewayFilterFactory.class).
+                        apply(c -> c.setName(addResponseHeaderArgs[0].trim()).
+                        setValue(addResponseHeaderArgs[1].trim()));
+                break;
+
+            case HYSTRIX:
+                String[] hystrixArgs = gwfilter.getArgs().split(",");
+                filter = ctx.getBean(HystrixGatewayFilterFactory.class).
+                        apply(routeId, c -> {
+                            if (StringUtils.isNotBlank(hystrixArgs[0])) {
+                                c.setName(hystrixArgs[0].trim());
+                            }
+                            if (StringUtils.isNotBlank(hystrixArgs[1])) {
+                                c.setFallbackUri(hystrixArgs[1].trim());
+                            }
+                        });
+                break;
+
+            case FALLBACK_HEADERS:
+                String[] fallbackHeadersArgs = gwfilter.getArgs().split(",");
+                filter = ctx.getBean(FallbackHeadersGatewayFilterFactory.class).
+                        apply(c -> {
+                            if (StringUtils.isNotBlank(fallbackHeadersArgs[0])) {
+                                c.setCauseExceptionMessageHeaderName(fallbackHeadersArgs[0].trim());
+                            }
+                            if (StringUtils.isNotBlank(fallbackHeadersArgs[1])) {
+                                c.setExecutionExceptionMessageHeaderName(fallbackHeadersArgs[1].trim());
+                            }
+                            if (StringUtils.isNotBlank(fallbackHeadersArgs[2])) {
+                                c.setExecutionExceptionTypeHeaderName(fallbackHeadersArgs[2].trim());
+                            }
+                            if (StringUtils.isNotBlank(fallbackHeadersArgs[3])) {
+                                c.setRootCauseExceptionTypeHeaderName(fallbackHeadersArgs[3].trim());
+                            }
+                        });
+                break;
+
+            case PREFIX_PATH:
+                filter = ctx.getBean(PrefixPathGatewayFilterFactory.class).
+                        apply(c -> c.setPrefix(gwfilter.getArgs().trim()));
+                break;
+
+            case PRESERVE_HOST_HEADER:
+                filter = ctx.getBean(PreserveHostHeaderGatewayFilterFactory.class).apply();
+                break;
+
+            case REDIRECT:
+                String[] redirectArgs = gwfilter.getArgs().split(",");
+                filter = ctx.getBean(RedirectToGatewayFilterFactory.class).
+                        apply(redirectArgs[0].trim(), redirectArgs[1].trim());
+                break;
+
+            case REMOVE_REQUEST_HEADER:
+                filter = ctx.getBean(RemoveRequestHeaderGatewayFilterFactory.class).
+                        apply(c -> c.setName(gwfilter.getArgs().trim()));
+                break;
+
+            case REMOVE_RESPONSE_HEADER:
+                filter = ctx.getBean(RemoveResponseHeaderGatewayFilterFactory.class).
+                        apply(c -> c.setName(gwfilter.getArgs().trim()));
+                break;
+
+            case REQUEST_RATE_LIMITER:
+                String[] requestRateLimiterArgs = gwfilter.getArgs().split(",");
+                filter = ctx.getBean(RequestRateLimiterGatewayFilterFactory.class).
+                        apply(c -> {
+                            if (StringUtils.isNotBlank(requestRateLimiterArgs[0])) {
+                                c.setDenyEmptyKey(BooleanUtils.toBoolean(requestRateLimiterArgs[0].trim()));
+                            }
+                            if (StringUtils.isNotBlank(requestRateLimiterArgs[1])) {
+                                c.setEmptyKeyStatus(requestRateLimiterArgs[1].trim());
+                            }
+                            if (StringUtils.isNotBlank(requestRateLimiterArgs[2])) {
+                                c.setKeyResolver(ctx.getBean(requestRateLimiterArgs[2].trim(), KeyResolver.class));
+                            }
+                            if (StringUtils.isNotBlank(requestRateLimiterArgs[3])) {
+                                c.setRateLimiter(ctx.getBean(requestRateLimiterArgs[3].trim(), RateLimiter.class));
+                            }
+                            if (StringUtils.isNotBlank(requestRateLimiterArgs[4])) {
+                                c.setStatusCode(HttpStatus.valueOf(requestRateLimiterArgs[4].trim()));
+                            }
+                        });
+                break;
+
+            case REWRITE_PATH:
+                String[] rewritePathArgs = gwfilter.getArgs().split(",");
+                filter = ctx.getBean(RewritePathGatewayFilterFactory.class).
+                        apply(c -> c.setRegexp(rewritePathArgs[0].trim()).
+                        setReplacement(rewritePathArgs[1].trim()));
+                break;
+
+            case RETRY:
+                filter = ctx.getBean(RetryGatewayFilterFactory.class).
+                        apply(c -> c.setRetries(Integer.valueOf(gwfilter.getArgs().trim())));
+                break;
+
+            case SECURE_HEADERS:
+                filter = ctx.getBean(SecureHeadersGatewayFilterFactory.class).apply(c -> {
+                });
+                break;
+
+            case SET_PATH:
+                filter = ctx.getBean(SetPathGatewayFilterFactory.class).
+                        apply(c -> c.setTemplate(gwfilter.getArgs().trim()));
+                break;
+
+            case SET_REQUEST_HEADER:
+                String[] setRequestHeaderArgs = gwfilter.getArgs().split(",");
+                filter = ctx.getBean(SetRequestHeaderGatewayFilterFactory.class).
+                        apply(c -> c.setName(setRequestHeaderArgs[0].trim()).
+                        setValue(setRequestHeaderArgs[1].trim()));
+                break;
+
+            case SET_RESPONSE_HEADER:
+                String[] setResponseHeaderArgs = gwfilter.getArgs().split(",");
+                filter = ctx.getBean(SetResponseHeaderGatewayFilterFactory.class).
+                        apply(c -> c.setName(setResponseHeaderArgs[0].trim()).
+                        setValue(setResponseHeaderArgs[1].trim()));
+                break;
+
+            case REWRITE_RESPONSE_HEADER:
+                String[] rewriteResponseHeaderArgs = gwfilter.getArgs().split(",");
+                filter = ctx.getBean(RewriteResponseHeaderGatewayFilterFactory.class).
+                        apply(c -> c.setReplacement(rewriteResponseHeaderArgs[2].trim()).
+                        setRegexp(rewriteResponseHeaderArgs[1].trim()).
+                        setName(rewriteResponseHeaderArgs[0].trim()));
+                break;
+
+            case SET_STATUS:
+                filter = ctx.getBean(SetStatusGatewayFilterFactory.class).
+                        apply(c -> c.setStatus(gwfilter.getArgs().trim()));
+                break;
+
+            case SAVE_SESSION:
+                filter = ctx.getBean(SaveSessionGatewayFilterFactory.class).apply(c -> {
+                });
+                break;
+
+            case STRIP_PREFIX:
+                filter = ctx.getBean(StripPrefixGatewayFilterFactory.class).
+                        apply(c -> c.setParts(Integer.valueOf(gwfilter.getArgs().trim())));
+                break;
+
+            case REQUEST_HEADER_TO_REQUEST_URI:
+                filter = ctx.getBean(RequestHeaderToRequestUriGatewayFilterFactory.class).
+                        apply(c -> c.setName(gwfilter.getArgs().trim()));
+                break;
+
+            case SET_REQUEST_SIZE:
+                filter = ctx.getBean(RequestSizeGatewayFilterFactory.class).
+                        apply(c -> c.setMaxSize(Long.valueOf(gwfilter.getArgs().trim())));
+                break;
+
+            case CUSTOM:
+                String[] customArgs = gwfilter.getArgs().split(";");
+                CustomGatewayFilterFactory factory;
+                if (ctx.getBeanFactory().containsSingleton(customArgs[0])) {
+                    factory = (CustomGatewayFilterFactory) ctx.getBeanFactory().getSingleton(customArgs[0]);
+                } else {
+                    factory = (CustomGatewayFilterFactory) ctx.getBeanFactory().
+                            createBean(Class.forName(customArgs[0]), AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
+                    ctx.getBeanFactory().registerSingleton(customArgs[0], factory);
+                }
+                filter = factory.apply(c -> c.setData(customArgs[1]));
+                break;
+
+            default:
+                filter = null;
+        }
+
+        if (filter == null) {
+            throw new IllegalArgumentException("Could not translate " + gwfilter);
+        }
+
+        return filter instanceof Ordered ? filter : new OrderedGatewayFilter(filter, 0);
+    }
+
+    private AsyncPredicate<ServerWebExchange> toPredicate(final GatewayRoutePredicate gwpredicate, final boolean negate)
+            throws ClassNotFoundException {
+
+        AsyncPredicate<ServerWebExchange> predicate;
+        switch (gwpredicate.getFactory()) {
+            case AFTER:
+                predicate = ctx.getBean(AfterRoutePredicateFactory.class).
+                        applyAsync(c -> c.setDatetime(ZonedDateTime.parse(gwpredicate.getArgs().trim())));
+                break;
+
+            case BEFORE:
+                predicate = ctx.getBean(BeforeRoutePredicateFactory.class).
+                        applyAsync(c -> c.setDatetime(ZonedDateTime.parse(gwpredicate.getArgs().trim())));
+                break;
+
+            case BETWEEN:
+                String[] betweenArgs = gwpredicate.getArgs().split(",");
+                predicate = ctx.getBean(BetweenRoutePredicateFactory.class).
+                        applyAsync(c -> c.setDatetime1(ZonedDateTime.parse(betweenArgs[0].trim())).
+                        setDatetime2(ZonedDateTime.parse(betweenArgs[1].trim())));
+                break;
+
+            case COOKIE:
+                String[] cookieArgs = gwpredicate.getArgs().split(",");
+                predicate = ctx.getBean(CookieRoutePredicateFactory.class).
+                        applyAsync(c -> c.setName(cookieArgs[0].trim()).
+                        setRegexp(cookieArgs[1].trim()));
+                break;
+
+            case HEADER:
+                predicate = ctx.getBean(HeaderRoutePredicateFactory.class).
+                        applyAsync(c -> c.setHeader(gwpredicate.getArgs().trim()));
+                break;
+
+            case HOST:
+                String[] hostArgs = gwpredicate.getArgs().split(",");
+                predicate = ctx.getBean(HostRoutePredicateFactory.class).
+                        applyAsync(c -> c.setPatterns(Arrays.asList(hostArgs)));
+                break;
+
+            case METHOD:
+                predicate = ctx.getBean(MethodRoutePredicateFactory.class).
+                        applyAsync(c -> c.setMethod(HttpMethod.resolve(gwpredicate.getArgs().trim())));
+                break;
+
+            case PATH:
+                String[] pathArgs = gwpredicate.getArgs().split(",");
+                predicate = ctx.getBean(PathRoutePredicateFactory.class).
+                        applyAsync(c -> c.setPatterns(Arrays.asList(pathArgs)));
+                break;
+
+            case QUERY:
+                String[] queryArgs = gwpredicate.getArgs().split(",");
+                predicate = ctx.getBean(QueryRoutePredicateFactory.class).
+                        applyAsync(c -> c.setParam(queryArgs[0].trim()).
+                        setRegexp(queryArgs[1].trim()));
+                break;
+
+            case REMOTE_ADDR:
+                String[] remoteAddrArgs = gwpredicate.getArgs().split(",");
+                predicate = ctx.getBean(RemoteAddrRoutePredicateFactory.class).
+                        applyAsync(c -> c.setSources(Arrays.asList(remoteAddrArgs)));
+                break;
+
+            case CUSTOM:
+                String[] customArgs = gwpredicate.getArgs().split(";");
+                CustomRoutePredicateFactory factory;
+                if (ctx.getBeanFactory().containsSingleton(customArgs[0])) {
+                    factory = (CustomRoutePredicateFactory) ctx.getBeanFactory().getSingleton(customArgs[0]);
+                } else {
+                    factory = (CustomRoutePredicateFactory) ctx.getBeanFactory().
+                            createBean(Class.forName(customArgs[0]), AbstractBeanDefinition.AUTOWIRE_BY_TYPE, false);
+                    ctx.getBeanFactory().registerSingleton(customArgs[0], factory);
+                }
+                predicate = factory.applyAsync(c -> c.setData(customArgs[1]));
+                break;
+
+            default:
+                predicate = null;
+        }
+
+        if (predicate == null) {
+            throw new IllegalArgumentException("Could not translate " + gwpredicate);
+        }
+
+        if (negate) {
+            predicate.negate();
+        }
+        return predicate;
+    }
+
+    private Route.AsyncBuilder toRoute(final GatewayRouteTO gwroute) {
+        Route.AsyncBuilder builder = new Route.AsyncBuilder().
+                id(gwroute.getKey()).order(gwroute.getOrder()).uri(gwroute.getTarget());
+
+        if (gwroute.getPredicates().isEmpty()) {
+            builder.predicate(exchange -> true);
+        } else {
+            gwroute.getPredicates().forEach(gwpredicate -> {
+                if (builder.getPredicate() == null) {
+                    try {
+                        builder.asyncPredicate(toPredicate(gwpredicate, gwpredicate.isNegate()));
+                    } catch (Exception e) {
+                        LOG.error("Could not translate {}, skipping", gwpredicate, e);
+                    }
+                } else {
+                    try {
+                        switch (gwpredicate.getCond()) {
+                            case OR:
+                                builder.or(toPredicate(gwpredicate, gwpredicate.isNegate()));
+                                break;
+
+                            case AND:
+                            default:
+                                builder.and(toPredicate(gwpredicate, gwpredicate.isNegate()));
+                        }
+                    } catch (Exception e) {
+                        LOG.error("Could not translate {}, skipping", gwpredicate, e);
+                    }
+                }
+            });
+        }
+
+        if (!gwroute.getFilters().isEmpty()) {
+            builder.filters(gwroute.getFilters().stream().
+                    map(gwfilter -> {
+                        try {
+                            return toFilter(gwroute.getKey(), gwfilter);
+                        } catch (Exception e) {
+                            LOG.error("Could not translate {}, skipping", gwfilter, e);
+                            return null;
+                        }
+                    }).
+                    filter(Objects::nonNull).
+                    collect(Collectors.toList()));
+        }
+
+        return builder;
+    }
+
+    public List<Route.AsyncBuilder> fetch() {
+        synchronized (this) {
+            if (client == null) {
+                try {
+                    client = new SyncopeClientFactoryBean().
+                            setAddress(serviceOps.get(NetworkService.Type.CORE).getAddress()).
+                            setUseCompression(useGZIPCompression).
+                            create(new AnonymousAuthenticationHandler(anonymousUser, anonymousKey));
+                } catch (Exception e) {
+                    LOG.error("Could not init SyncopeClient", e);
+                    return Collections.emptyList();
+                }
+            }
+        }
+
+        return client.getService(GatewayRouteService.class).list().stream().
+                filter(gwroute -> gwroute.getStatus() == GatewayRouteStatus.PUBLISHED).
+                map(gwroute -> toRoute(gwroute)).
+                collect(Collectors.toList());
+    }
+}
diff --git a/core/idm/logic/src/main/java/org/apache/syncope/core/logic/init/IdMImplementationTypeLoader.java b/sra/src/main/java/org/apache/syncope/sra/RouteRefresher.java
similarity index 61%
copy from core/idm/logic/src/main/java/org/apache/syncope/core/logic/init/IdMImplementationTypeLoader.java
copy to sra/src/main/java/org/apache/syncope/sra/RouteRefresher.java
index b3a039c..8d19965 100644
--- a/core/idm/logic/src/main/java/org/apache/syncope/core/logic/init/IdMImplementationTypeLoader.java
+++ b/sra/src/main/java/org/apache/syncope/sra/RouteRefresher.java
@@ -16,23 +16,24 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.core.logic.init;
+package org.apache.syncope.sra;
 
-import org.apache.syncope.common.lib.types.IdMImplementationType;
-import org.apache.syncope.common.lib.types.ImplementationTypesHolder;
-import org.apache.syncope.core.persistence.api.SyncopeCoreLoader;
+import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.ApplicationEventPublisherAware;
 import org.springframework.stereotype.Component;
 
 @Component
-public class IdMImplementationTypeLoader implements SyncopeCoreLoader {
+public class RouteRefresher implements ApplicationEventPublisherAware {
+
+    private ApplicationEventPublisher publisher;
 
     @Override
-    public int getOrder() {
-        return Integer.MIN_VALUE;
+    public void setApplicationEventPublisher(final ApplicationEventPublisher publisher) {
+        this.publisher = publisher;
     }
 
-    @Override
-    public void load() {
-        ImplementationTypesHolder.getInstance().putAll(IdMImplementationType.values());
+    public void refresh() {
+        publisher.publishEvent(new RefreshRoutesEvent(this));
     }
 }
diff --git a/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAApplication.java b/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAApplication.java
index 09c620d..c0bda58 100644
--- a/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAApplication.java
+++ b/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAApplication.java
@@ -18,21 +18,66 @@
  */
 package org.apache.syncope.sra;
 
+import org.apache.syncope.common.lib.types.IdRepoEntitlement;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.cloud.gateway.route.RouteLocator;
 import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
+import org.springframework.context.EnvironmentAware;
 import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.core.env.Environment;
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
+import org.springframework.security.config.web.server.ServerHttpSecurity;
+import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.web.server.SecurityWebFilterChain;
+import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
+import org.springframework.web.util.pattern.PathPatternParser;
+import reactor.core.publisher.Flux;
 
+@PropertySource("classpath:sra.properties")
+@PropertySource(value = "file:${conf.directory}/sra.properties", ignoreResourceNotFound = true)
+@EnableWebFluxSecurity
 @SpringBootApplication
-public class SyncopeSRAApplication {
+public class SyncopeSRAApplication implements EnvironmentAware {
 
     public static void main(final String[] args) {
         SpringApplication.run(SyncopeSRAApplication.class, args);
     }
 
+    @Autowired
+    private RouteProvider provider;
+
+    private Environment env;
+
+    @Override
+    public void setEnvironment(final Environment env) {
+        this.env = env;
+    }
+
     @Bean
     public RouteLocator routes(final RouteLocatorBuilder builder) {
-        return builder.routes().build();
+        return () -> Flux.fromIterable(provider.fetch()).map(routeBuilder -> routeBuilder.build());
+    }
+
+    @Bean
+    public SecurityWebFilterChain springSecurityFilterChain(final ServerHttpSecurity http) {
+        http.csrf().disable().securityMatcher(
+                new PathPatternParserServerWebExchangeMatcher(new PathPatternParser().parse("/management/**"))).
+                authorizeExchange().anyExchange().hasRole(IdRepoEntitlement.ANONYMOUS).and().httpBasic();
+        return http.build();
+    }
+
+    @Bean
+    public MapReactiveUserDetailsService userDetailsService() {
+        UserDetails user = User.builder().
+                username(env.getProperty("anonymousUser")).
+                password("{noop}" + env.getProperty("anonymousKey")).
+                roles(IdRepoEntitlement.ANONYMOUS).
+                build();
+        return new MapReactiveUserDetailsService(user);
     }
 }
diff --git a/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAShutdown.java b/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAShutdown.java
index d335836..4ed772c 100644
--- a/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAShutdown.java
+++ b/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAShutdown.java
@@ -20,7 +20,9 @@ package org.apache.syncope.sra;
 
 import org.springframework.context.ApplicationListener;
 import org.springframework.context.event.ContextClosedEvent;
+import org.springframework.stereotype.Component;
 
+@Component
 public class SyncopeSRAShutdown extends SyncopeSRAStartStop
         implements ApplicationListener<ContextClosedEvent> {
 
diff --git a/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAStartup.java b/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAStartup.java
index 08ea447..cbc7b59 100644
--- a/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAStartup.java
+++ b/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAStartup.java
@@ -20,7 +20,9 @@ package org.apache.syncope.sra;
 
 import org.springframework.context.ApplicationListener;
 import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.stereotype.Component;
 
+@Component
 public class SyncopeSRAStartup extends SyncopeSRAStartStop
         implements ApplicationListener<ContextRefreshedEvent> {
 
diff --git a/sra/src/main/resources/application.properties b/sra/src/main/resources/application.properties
index 98bc964..b50f4f9 100644
--- a/sra/src/main/resources/application.properties
+++ b/sra/src/main/resources/application.properties
@@ -20,12 +20,8 @@ spring.main.banner-mode=log
 
 server.port=8080
 
-management.endpoint.gateway.enabled=true
-management.endpoints.web.exposure.include=gateway
-
 spring.cloud.gateway.metrics.enabled=true
 management.endpoint.metrics.enabled=true
-management.endpoints.web.exposure.include=*
 management.endpoint.prometheus.enabled=true
 management.metrics.export.prometheus.enabled=true
 
diff --git a/sra/src/main/resources/log4j2.xml b/sra/src/main/resources/log4j2.xml
index d7211f5..3e0fa0c 100644
--- a/sra/src/main/resources/log4j2.xml
+++ b/sra/src/main/resources/log4j2.xml
@@ -52,11 +52,18 @@ under the License.
     <asyncLogger name="org.apache.syncope.client.lib" additivity="false" level="OFF">
       <appender-ref ref="main"/>
     </asyncLogger>
-
     <asyncLogger name="org.apache.syncope.sra" additivity="false" level="INFO">
       <appender-ref ref="main"/>
     </asyncLogger>
 
+    <asyncLogger name="org.apache.cxf" additivity="false" level="ERROR">
+      <appender-ref ref="main"/>
+    </asyncLogger>
+
+    <asyncLogger name="org.springframework.cloud.gateway" additivity="false" level="INFO">
+      <appender-ref ref="main"/>
+    </asyncLogger>
+
     <!-- Requires -Dreactor.netty.http.server.accessLogEnabled=true to work-->
     <asyncLogger name="reactor.netty.http.server.AccessLog" additivity="false" level="INFO">
       <appender-ref ref="access"/>
diff --git a/sra/src/main/resources/sra.properties b/sra/src/main/resources/sra.properties
index 6789cfd..77cb93e 100644
--- a/sra/src/main/resources/sra.properties
+++ b/sra/src/main/resources/sra.properties
@@ -14,8 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-sra.directory=${conf.directory}
-
 anonymousUser=${anonymousUser}
 anonymousKey=${anonymousKey}
 
diff --git a/sra/src/test/java/org/apache/syncope/sra/BodyPropertyAddingGatewayFilterFactory.java b/sra/src/test/java/org/apache/syncope/sra/BodyPropertyAddingGatewayFilterFactory.java
new file mode 100644
index 0000000..aec3dcc
--- /dev/null
+++ b/sra/src/test/java/org/apache/syncope/sra/BodyPropertyAddingGatewayFilterFactory.java
@@ -0,0 +1,201 @@
+/*
+ * 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;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.zookeeper.common.IOUtils;
+import org.reactivestreams.Publisher;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.cloud.gateway.filter.GatewayFilter;
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
+import org.springframework.core.Ordered;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.core.io.buffer.DataBufferFactory;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseCookie;
+import org.springframework.http.client.reactive.ClientHttpResponse;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+/**
+ * Inspired by {@link org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactory}.
+ */
+public class BodyPropertyAddingGatewayFilterFactory extends CustomGatewayFilterFactory {
+
+    private static final Logger LOG = LoggerFactory.getLogger(BodyPropertyAddingGatewayFilterFactory.class);
+
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
+    private static boolean isCompressed(final byte[] bytes) {
+        if ((bytes == null) || (bytes.length < 2)) {
+            return false;
+        } else {
+            return ((bytes[0] == (byte) (GZIPInputStream.GZIP_MAGIC))
+                    && (bytes[1] == (byte) (GZIPInputStream.GZIP_MAGIC >> 8)));
+        }
+    }
+
+    @Override
+    public GatewayFilter apply(final Config config) {
+        return new ModifyResponseGatewayFilter(config);
+    }
+
+    public class ModifyResponseGatewayFilter implements GatewayFilter, Ordered {
+
+        private final Config config;
+
+        public ModifyResponseGatewayFilter(final Config config) {
+            this.config = config;
+        }
+
+        @Override
+        public Mono<Void> filter(final ServerWebExchange exchange, final GatewayFilterChain chain) {
+            ServerHttpResponse originalResponse = exchange.getResponse();
+
+            DataBufferFactory bufferFactory = originalResponse.bufferFactory();
+            ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(originalResponse) {
+
+                @Override
+                public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
+                    if (body instanceof Flux) {
+                        Flux<? extends DataBuffer> flux = (Flux<? extends DataBuffer>) body;
+
+                        return super.writeWith(flux.buffer().map(dataBuffers -> {
+                            ByteArrayOutputStream payload = new ByteArrayOutputStream();
+                            dataBuffers.forEach(buffer -> {
+                                byte[] array = new byte[buffer.readableByteCount()];
+                                buffer.read(array);
+                                try {
+                                    payload.write(array);
+                                } catch (IOException e) {
+                                    LOG.error("While reading original body content", e);
+                                }
+                            });
+
+                            byte[] input = payload.toByteArray();
+
+                            InputStream is = null;
+                            boolean compressed = false;
+                            byte[] output;
+                            try {
+                                if (isCompressed(input)) {
+                                    compressed = true;
+                                    is = new GZIPInputStream(new ByteArrayInputStream(input));
+                                } else {
+                                    is = new ByteArrayInputStream(input);
+                                }
+
+                                ObjectNode content = (ObjectNode) MAPPER.readTree(is);
+                                String[] kv = config.getData().split("=");
+                                content.put(kv[0], kv[1]);
+
+                                output = MAPPER.writeValueAsBytes(content);
+                            } catch (IOException e) {
+                                LOG.error("While (de)serializing as JSON", e);
+                                output = ArrayUtils.clone(input);
+                            } finally {
+                                IOUtils.closeStream(is);
+                            }
+
+                            if (compressed) {
+                                try (ByteArrayOutputStream baos = new ByteArrayOutputStream(output.length);
+                                        GZIPOutputStream gzipos = new GZIPOutputStream(baos)) {
+
+                                    gzipos.write(output);
+                                    gzipos.close();
+                                    output = baos.toByteArray();
+                                } catch (IOException e) {
+                                    LOG.error("While GZIP-encoding output", e);
+                                }
+                            }
+
+                            return bufferFactory.wrap(output);
+                        }));
+                    }
+
+                    return super.writeWith(body);
+                }
+            };
+
+            return chain.filter(exchange.mutate().response(responseDecorator).build());
+        }
+
+        @Override
+        public int getOrder() {
+            return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
+        }
+    }
+
+    public class ResponseAdapter implements ClientHttpResponse {
+
+        private final Flux<DataBuffer> flux;
+
+        private final HttpHeaders headers;
+
+        @SuppressWarnings("unchecked")
+        public ResponseAdapter(final Publisher<? extends DataBuffer> body, final HttpHeaders headers) {
+            this.headers = headers;
+            if (body instanceof Flux) {
+                flux = (Flux) body;
+            } else {
+                flux = ((Mono) body).flux();
+            }
+        }
+
+        @Override
+        public Flux<DataBuffer> getBody() {
+            return flux;
+        }
+
+        @Override
+        public HttpHeaders getHeaders() {
+            return headers;
+        }
+
+        @Override
+        public HttpStatus getStatusCode() {
+            return null;
+        }
+
+        @Override
+        public int getRawStatusCode() {
+            return 0;
+        }
+
+        @Override
+        public MultiValueMap<String, ResponseCookie> getCookies() {
+            return null;
+        }
+    }
+}
diff --git a/sra/src/test/java/org/apache/syncope/sra/BodyPropertyMatchingRoutePredicateFactory.java b/sra/src/test/java/org/apache/syncope/sra/BodyPropertyMatchingRoutePredicateFactory.java
new file mode 100644
index 0000000..bff3903
--- /dev/null
+++ b/sra/src/test/java/org/apache/syncope/sra/BodyPropertyMatchingRoutePredicateFactory.java
@@ -0,0 +1,81 @@
+/*
+ * 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;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import java.util.List;
+import org.springframework.cloud.gateway.filter.AdaptCachedBodyGlobalFilter;
+import org.springframework.cloud.gateway.handler.AsyncPredicate;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.core.io.buffer.DataBufferUtils;
+import org.springframework.http.codec.HttpMessageReader;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
+import org.springframework.web.reactive.function.server.HandlerStrategies;
+import org.springframework.web.reactive.function.server.ServerRequest;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+/**
+ * Inspired by {@link org.springframework.cloud.gateway.handler.predicate.ReadBodyPredicateFactory}.
+ */
+public class BodyPropertyMatchingRoutePredicateFactory extends CustomRoutePredicateFactory {
+
+    private static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject";
+
+    private static final List<HttpMessageReader<?>> MESSAGE_READERS =
+            HandlerStrategies.withDefaults().messageReaders();
+
+    @Override
+    public AsyncPredicate<ServerWebExchange> applyAsync(final Config config) {
+        return exchange -> {
+            JsonNode cachedBody = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY);
+            if (cachedBody == null) {
+                // Join all the DataBuffers so we have a single DataBuffer for the body
+                return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> {
+                    // Update the retain counts so we can read the body twice, once to parse into an object
+                    // that we can test the predicate against and a second time when the HTTP client sends
+                    // the request downstream 
+                    // Note: if we end up reading the body twice we will run into a problem, but as of right
+                    // now there is no good use case for doing this
+                    DataBufferUtils.retain(dataBuffer);
+                    // Make a slice for each read so each read has its own read/write indexes
+                    Flux<DataBuffer> cachedFlux = Flux.defer(() -> Flux.just(
+                            dataBuffer.slice(0, dataBuffer.readableByteCount())));
+
+                    ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
+
+                        @Override
+                        public Flux<DataBuffer> getBody() {
+                            return cachedFlux;
+                        }
+                    };
+                    return ServerRequest.create(exchange.mutate().request(mutatedRequest).build(), MESSAGE_READERS).
+                            bodyToMono(JsonNode.class).doOnNext(value -> {
+                        exchange.getAttributes().put(CACHE_REQUEST_BODY_OBJECT_KEY, value);
+                        exchange.getAttributes().put(AdaptCachedBodyGlobalFilter.CACHED_REQUEST_BODY_KEY, cachedFlux);
+                    }).map(objectValue -> objectValue.has(config.getData()));
+                });
+            } else {
+                return Mono.just(cachedBody.has(config.getData()));
+            }
+        };
+    }
+}
diff --git a/sra/src/test/java/org/apache/syncope/sra/SyncopeSRATest.java b/sra/src/test/java/org/apache/syncope/sra/SyncopeSRATest.java
new file mode 100644
index 0000000..3b16c64
--- /dev/null
+++ b/sra/src/test/java/org/apache/syncope/sra/SyncopeSRATest.java
@@ -0,0 +1,207 @@
+/*
+ * 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;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.get;
+import static com.github.tomakehurst.wiremock.client.WireMock.post;
+import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+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 java.io.IOException;
+import java.net.URI;
+import org.apache.syncope.common.lib.to.GatewayRouteTO;
+import org.apache.syncope.common.lib.types.FilterFactory;
+import org.apache.syncope.common.lib.types.GatewayRouteFilter;
+import org.apache.syncope.common.lib.types.GatewayRoutePredicate;
+import org.apache.syncope.common.lib.types.GatewayRouteStatus;
+import org.apache.syncope.common.lib.types.PredicateCond;
+import org.apache.syncope.common.lib.types.PredicateFactory;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.reactive.server.WebTestClient;
+import org.springframework.web.reactive.function.BodyInserters;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+@AutoConfigureWireMock(port = 0)
+public class SyncopeSRATest {
+
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
+    @Autowired
+    private WebTestClient webClient;
+
+    @Autowired
+    private RouteRefresher routeRefresher;
+
+    @Value("${wiremock.server.port}")
+    private int wiremockPort;
+
+    @BeforeEach
+    public void clearRoutes() {
+        SyncopeSRATestCoreStartup.ROUTES.clear();
+    }
+
+    @Test
+    public void root() {
+        webClient.get().exchange().expectStatus().isNotFound();
+    }
+
+    @Test
+    public void getAddResponseHeader() {
+        // 1. no mapping for URL
+        webClient.get().uri("/getAddResponseHeader").exchange().expectStatus().isNotFound();
+
+        // 2. stub for proxied URL
+        stubFor(get(urlEqualTo("/getAddResponseHeader")).willReturn(aResponse()));
+
+        // 3. create route configuration
+        GatewayRouteTO routeTO = new GatewayRouteTO();
+        routeTO.setKey("getAddResponseHeader");
+        routeTO.setStatus(GatewayRouteStatus.PUBLISHED);
+        routeTO.setTarget(URI.create("http://localhost:" + wiremockPort));
+        routeTO.getPredicates().add(new GatewayRoutePredicate.Builder().
+                factory(PredicateFactory.METHOD).args("GET").build());
+        routeTO.getPredicates().add(new GatewayRoutePredicate.Builder().
+                factory(PredicateFactory.PATH).args("/getAddResponseHeader").cond(PredicateCond.AND).build());
+        routeTO.getFilters().add(new GatewayRouteFilter.Builder().
+                factory(FilterFactory.ADD_RESPONSE_HEADER).args("Hello,World").build());
+
+        SyncopeSRATestCoreStartup.ROUTES.put(routeTO.getKey(), routeTO);
+
+        routeRefresher.refresh();
+
+        // 4. now mapping works for URL
+        webClient.get().uri("/getAddResponseHeader").exchange().
+                expectStatus().isOk().
+                expectHeader().valueEquals("Hello", "World");
+
+        // 5. update route configuration
+        routeTO.getFilters().clear();
+        routeTO.getFilters().add(new GatewayRouteFilter.Builder().
+                factory(FilterFactory.ADD_RESPONSE_HEADER).args("Hello,WorldZ").build());
+
+        routeRefresher.refresh();
+
+        // 6. mapping for URL is updated too
+        webClient.get().uri("/getAddResponseHeader").exchange().
+                expectStatus().isOk().
+                expectHeader().valueEquals("Hello", "WorldZ");
+
+        // 7. update route configuration again
+        routeTO.getFilters().clear();
+
+        routeRefresher.refresh();
+
+        // 8. mapping for URL is updated again
+        webClient.get().uri("/getAddResponseHeader").exchange().
+                expectStatus().isOk().
+                expectHeader().doesNotExist("Hello");
+    }
+
+    @Test
+    public void hystrix() {
+        webClient.get().uri("/fallback").exchange().
+                expectStatus().isOk().
+                expectBody().
+                consumeWith(response -> assertThat(response.getResponseBody()).isEqualTo("fallback".getBytes()));
+
+        stubFor(get(urlEqualTo("/delay/3")).
+                willReturn(aResponse().
+                        withBody("no fallback").
+                        withFixedDelay(3000)));
+
+        GatewayRouteTO routeTO = new GatewayRouteTO();
+        routeTO.setKey("hystrix");
+        routeTO.setStatus(GatewayRouteStatus.PUBLISHED);
+        routeTO.setTarget(URI.create("http://localhost:" + wiremockPort));
+        routeTO.getPredicates().add(new GatewayRoutePredicate.Builder().
+                factory(PredicateFactory.HOST).args("*.hystrix.com").build());
+        routeTO.getFilters().add(new GatewayRouteFilter.Builder().
+                factory(FilterFactory.HYSTRIX).args("fallbackcmd,forward:/fallback").build());
+
+        SyncopeSRATestCoreStartup.ROUTES.put(routeTO.getKey(), routeTO);
+
+        routeRefresher.refresh();
+
+        webClient.get().uri("/delay/3").
+                header(HttpHeaders.HOST, "www.hystrix.com").
+                exchange().
+                expectStatus().isOk().
+                expectBody().
+                consumeWith(response -> assertThat(response.getResponseBody()).isEqualTo("fallback".getBytes()));
+    }
+
+    @Test
+    public void custom() {
+        stubFor(post(urlEqualTo("/custom")).
+                willReturn(aResponse().
+                        withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).
+                        withBody("{\"data\": \"data\"}")));
+
+        GatewayRouteTO routeTO = new GatewayRouteTO();
+        routeTO.setKey("custom");
+        routeTO.setStatus(GatewayRouteStatus.PUBLISHED);
+        routeTO.setTarget(URI.create("http://localhost:" + wiremockPort));
+        routeTO.getPredicates().add(new GatewayRoutePredicate.Builder().
+                factory(PredicateFactory.CUSTOM).
+                args(BodyPropertyMatchingRoutePredicateFactory.class.getName() + ";cool").build());
+        routeTO.getFilters().add(new GatewayRouteFilter.Builder().
+                factory(FilterFactory.ADD_RESPONSE_HEADER).args("Custom,matched").build());
+        routeTO.getFilters().add(new GatewayRouteFilter.Builder().
+                factory(FilterFactory.CUSTOM).
+                args(BodyPropertyAddingGatewayFilterFactory.class.getName() + ";customized=true").build());
+
+        SyncopeSRATestCoreStartup.ROUTES.put(routeTO.getKey(), routeTO);
+
+        routeRefresher.refresh();
+
+        webClient.post().uri("/custom").
+                body(BodyInserters.fromObject(MAPPER.createObjectNode().put("other", true))).
+                exchange().
+                expectStatus().isNotFound();
+
+        webClient.post().uri("/custom").
+                body(BodyInserters.fromObject(MAPPER.createObjectNode().put("cool", true))).
+                exchange().
+                expectStatus().isOk().
+                expectHeader().valueEquals("Custom", "matched").
+                expectBody().
+                consumeWith(response -> {
+                    try {
+                        JsonNode body = MAPPER.readTree(response.getResponseBody());
+                        assertTrue(body.has("customized"));
+                        assertTrue(body.get("customized").asBoolean());
+                    } catch (IOException e) {
+                        fail(e.getMessage(), e);
+                    }
+                });
+    }
+}
diff --git a/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAShutdown.java b/sra/src/test/java/org/apache/syncope/sra/SyncopeSRATestController.java
similarity index 69%
copy from sra/src/main/java/org/apache/syncope/sra/SyncopeSRAShutdown.java
copy to sra/src/test/java/org/apache/syncope/sra/SyncopeSRATestController.java
index d335836..c56490f 100644
--- a/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAShutdown.java
+++ b/sra/src/test/java/org/apache/syncope/sra/SyncopeSRATestController.java
@@ -18,14 +18,15 @@
  */
 package org.apache.syncope.sra;
 
-import org.springframework.context.ApplicationListener;
-import org.springframework.context.event.ContextClosedEvent;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import reactor.core.publisher.Mono;
 
-public class SyncopeSRAShutdown extends SyncopeSRAStartStop
-        implements ApplicationListener<ContextClosedEvent> {
+@RestController
+public class SyncopeSRATestController {
 
-    @Override
-    public void onApplicationEvent(final ContextClosedEvent event) {
-        serviceOps.unregister(getNetworkService());
+    @RequestMapping("/fallback")
+    public Mono<String> fallback() {
+        return Mono.just("fallback");
     }
 }
diff --git a/sra/src/test/java/org/apache/syncope/sra/SyncopeSRATestCoreStartup.java b/sra/src/test/java/org/apache/syncope/sra/SyncopeSRATestCoreStartup.java
new file mode 100644
index 0000000..9a12b66
--- /dev/null
+++ b/sra/src/test/java/org/apache/syncope/sra/SyncopeSRATestCoreStartup.java
@@ -0,0 +1,116 @@
+/*
+ * 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;
+
+import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.core.Response;
+import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
+import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;
+import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
+import org.apache.syncope.common.lib.to.GatewayRouteTO;
+import org.apache.syncope.common.rest.api.service.GatewayRouteService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.core.Ordered;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SyncopeSRATestCoreStartup extends SyncopeSRAStartStop
+        implements ApplicationListener<ContextRefreshedEvent>, Ordered {
+
+    public static final String ADDRESS = "http://localhost:9080/syncope/rest";
+
+    public static final Map<String, GatewayRouteTO> ROUTES = new ConcurrentHashMap<>();
+
+    @Autowired
+    private RouteRefresher routeRefresher;
+
+    @Override
+    public int getOrder() {
+        return Ordered.LOWEST_PRECEDENCE;
+    }
+
+    @Override
+    public void onApplicationEvent(final ContextRefreshedEvent event) {
+        // 1. start (mocked) Core as embedded CXF
+        JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
+        sf.setAddress(ADDRESS);
+        sf.setResourceClasses(GatewayRouteService.class);
+        sf.setResourceProvider(
+                GatewayRouteService.class,
+                new SingletonResourceProvider(new StubGatewayRouteService(), true));
+        sf.setProviders(Collections.singletonList(new JacksonJsonProvider()));
+        sf.create();
+
+        // 2. register Core in Keymaster
+        NetworkService core = new NetworkService();
+        core.setType(NetworkService.Type.CORE);
+        core.setAddress(SyncopeSRATestCoreStartup.ADDRESS);
+        serviceOps.register(core);
+    }
+
+    public class StubGatewayRouteService implements GatewayRouteService {
+
+        @Override
+        public List<GatewayRouteTO> list() {
+            return ROUTES.values().stream().
+                    sorted(Comparator.comparing(GatewayRouteTO::getKey)).
+                    collect(Collectors.toList());
+        }
+
+        @Override
+        public Response create(final GatewayRouteTO routeTO) {
+            ROUTES.putIfAbsent(routeTO.getKey(), routeTO);
+            return Response.noContent().build();
+        }
+
+        @Override
+        public GatewayRouteTO read(final String key) {
+            GatewayRouteTO route = ROUTES.get(key);
+            if (route == null) {
+                throw new NotFoundException();
+            }
+            return route;
+        }
+
+        @Override
+        public void update(final GatewayRouteTO routeTO) {
+            read(routeTO.getKey());
+            ROUTES.put(routeTO.getKey(), routeTO);
+        }
+
+        @Override
+        public void delete(final String key) {
+            ROUTES.remove(key);
+        }
+
+        @Override
+        public void pushToSRA() {
+            routeRefresher.refresh();
+        }
+    }
+}
diff --git a/sra/src/test/java/org/apache/syncope/sra/SyncopeSRATestKeymasterStartup.java b/sra/src/test/java/org/apache/syncope/sra/SyncopeSRATestKeymasterStartup.java
new file mode 100644
index 0000000..c5649f6
--- /dev/null
+++ b/sra/src/test/java/org/apache/syncope/sra/SyncopeSRATestKeymasterStartup.java
@@ -0,0 +1,92 @@
+/*
+ * 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;
+
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
+import org.apache.curator.test.InstanceSpec;
+import org.apache.curator.test.TestingServer;
+import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.event.ContextRefreshedEvent;
+import org.springframework.core.Ordered;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SyncopeSRATestKeymasterStartup extends SyncopeSRAStartStop
+        implements ApplicationListener<ContextRefreshedEvent>, Ordered {
+
+    @Override
+    public int getOrder() {
+        return Ordered.HIGHEST_PRECEDENCE;
+    }
+
+    @Override
+    public void onApplicationEvent(final ContextRefreshedEvent event) {
+        // 1. start Zookeeper for Keymaster
+        AtomicReference<String> username = new AtomicReference<>();
+        AtomicReference<String> password = new AtomicReference<>();
+        try (InputStream propStream = getClass().getResourceAsStream("/keymaster.properties")) {
+            Properties props = new Properties();
+            props.load(propStream);
+
+            username.set(props.getProperty("keymaster.username"));
+            password.set(props.getProperty("keymaster.password"));
+        } catch (Exception e) {
+            throw new IllegalStateException("Could not load /keymaster.properties", e);
+        }
+
+        Configuration.setConfiguration(new Configuration() {
+
+            private final AppConfigurationEntry[] entries = {
+                new AppConfigurationEntry(
+                "org.apache.zookeeper.server.auth.DigestLoginModule",
+                AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
+                Map.of(
+                "user_" + username.get(), password.get()
+                ))
+            };
+
+            @Override
+            public AppConfigurationEntry[] getAppConfigurationEntry(final String name) {
+                return entries;
+            }
+        });
+
+        Map<String, Object> customProperties = new HashMap<>();
+        customProperties.put("authProvider.1", "org.apache.zookeeper.server.auth.SASLAuthenticationProvider");
+        InstanceSpec spec = new InstanceSpec(null, 2181, -1, -1, true, 1, -1, -1, customProperties);
+        try {
+            new TestingServer(spec, true).start();
+        } catch (Exception e) {
+            throw new IllegalStateException("Could not start Zookeeper", e);
+        }
+
+        // 2. register Core in Keymaster
+        NetworkService core = new NetworkService();
+        core.setType(NetworkService.Type.CORE);
+        core.setAddress(SyncopeSRATestCoreStartup.ADDRESS);
+        serviceOps.register(core);
+    }
+}
diff --git a/sra/src/main/resources/keymaster.properties b/sra/src/test/resources/keymaster.properties
similarity index 93%
rename from sra/src/main/resources/keymaster.properties
rename to sra/src/test/resources/keymaster.properties
index 033fe3b..f374d8c 100644
--- a/sra/src/main/resources/keymaster.properties
+++ b/sra/src/test/resources/keymaster.properties
@@ -14,6 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-keymaster.address=http://localhost:9080/syncope/rest/keymaster
+keymaster.address=127.0.0.1:2181
 keymaster.username=${anonymousUser}
 keymaster.password=${anonymousKey}