Repository: SAP/cloud-security-xsuaa-integration
Pull request #103 (2019-07-05 13:49:56)

Support for reactive ServerHttpSecurity (Spring webflux)
This PR request comes with a sample application that shows how to leverage spring-xsuaa lib within a reactive Spring application: 
https://github.com/SAP/cloud-security-xsuaa-integration/tree/e2bcc71e2f9f305320f561168613f0229c8cbb9e/samples/spring-webflux-security-xsuaa-usage

Referenced issue:
- #75
- #77

Original PR:
- #80 

Todos:
- [x] Provide Jwt token of type `XsuaaToken` (Token) via a class ReactiveSpringContext
- [x] Test interference with auto-config

References:
https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#jc-erms

nenaraab: @mwd-sap 
I've tested everything and merged 1.5.0 version into... 
nenaraab: Hi @RRoggia 
would you mind to add some further test cases to the sample to check the final status code?

From my side its okay then...

nenaraab: @mwdb 
can you please perform a review?

Patch:
diff --git a/.gitignore b/.gitignore
index cd04b5d9..20fd467e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,3 +33,5 @@ bin
 *.iws
 *.iml
 *.ipr
+
+vim.exe.stackdump
diff --git a/samples/spring-webflux-security-xsuaa-usage/README.md b/samples/spring-webflux-security-xsuaa-usage/README.md
new file mode 100644
index 00000000..f372f72e
--- /dev/null
+++ b/samples/spring-webflux-security-xsuaa-usage/README.md
@@ -0,0 +1,47 @@
+# Description
+This is a java Springboot application using the `spring-webflux` as the web framework and protected by the `spring-security-oauth2-resource-server` and the `cloud-security-xsuaa-integration`.
+
+# Deployment To Cloud Foundry or SAP HANA XS Advanced
+To deploy the application, the following steps are required:
+- Configure the Application Router
+- Compile the Java application
+- Create an XSUAA service instance
+- Configure manifest.yml
+- Deploy the application
+- Assign Role to your user
+- Access the application
+
+## Configure the Application Router
+
+The [Application Router](./approuter/package.json) is used to provide a single entry point to a business application that consists of several different apps (microservices). It dispatches requests to backend microservices and acts as a reverse proxy. The rules that determine which request should be forwarded to which _destinations_ are called _routes_. The application router can be configured to authenticate the users and propagate the user information. Finally, the application router can serve static content.
+
+## Compile the Java Application
+Run maven to package the application
+```shell
+mvn clean package
+```
+
+## Create the XSUAA Service Instance
+Use the [xs-security.json](./xs-security.json) to define the authentication settings and create a service instance
+```shell
+cf create-service xsuaa application xsuaa-webflux -c xs-security.json
+```
+
+## Configuration the manifest
+The [vars](../vars.yml) contains hosts and paths that you might need to adopt.
+
+## Deploy the application
+Deploy the application using cf push. It will expect 1 GB of free memory quota.
+
+```shell
+cf push --vars-file ../vars.yml
+```
+
+## Access the application
+After deployment, the AppRouter will trigger authentication automatically when you access one of the following URLs:
+
+* `https://spring-webflux-security-xsuaa-usage-web-<ID>.<LANDSCAPE_APPS_DOMAIN>/v1/sayHello` - produces Http response with content-type `application/json; UTF-8` and the body containing the claims of the JWT or an error message
+
+Direct access to the microservice (without the AppRouter) will return an error:
+
+* `https://spring-webflux-security-xsuaa-usage-<ID>.<LANDSCAPE_APPS_DOMAIN>/v1/sayHello` - produces a error with `401` (unauthenticated) status code, as it calls the service without `Authorization` header.
\ No newline at end of file
diff --git a/samples/spring-webflux-security-xsuaa-usage/approuter/package.json b/samples/spring-webflux-security-xsuaa-usage/approuter/package.json
new file mode 100644
index 00000000..104b375c
--- /dev/null
+++ b/samples/spring-webflux-security-xsuaa-usage/approuter/package.json
@@ -0,0 +1,9 @@
+{
+    "name": "approuter",
+    "dependencies": {
+        "@sap/approuter": "^5.6.4"
+    },
+    "scripts": {
+        "start": "node node_modules/@sap/approuter/approuter.js"
+    }
+}
diff --git a/samples/spring-webflux-security-xsuaa-usage/approuter/resources/index.html b/samples/spring-webflux-security-xsuaa-usage/approuter/resources/index.html
new file mode 100644
index 00000000..596e941d
--- /dev/null
+++ b/samples/spring-webflux-security-xsuaa-usage/approuter/resources/index.html
@@ -0,0 +1,7 @@
+<!DOCTYPE HTML>
+<html>
+	<head></head>
+	<body>
+		Welcome to our Index Page
+	</body>
+</html>
\ No newline at end of file
diff --git a/samples/spring-webflux-security-xsuaa-usage/approuter/sap-developer-license.txt b/samples/spring-webflux-security-xsuaa-usage/approuter/sap-developer-license.txt
new file mode 100644
index 00000000..d521388e
--- /dev/null
+++ b/samples/spring-webflux-security-xsuaa-usage/approuter/sap-developer-license.txt
@@ -0,0 +1,35 @@
+SAP DEVELOPER LICENSE AGREEMENT
+
+Please scroll down and read the following Developer License Agreement carefully ("Developer Agreement").  By clicking "I Accept" or by attempting to download, or install, or use the SAP software and other materials that accompany this Developer Agreement ("SAP Materials"), You agree that this Developer Agreement forms a legally binding agreement between You ("You" or "Your") and SAP SE, for and on behalf of itself and its subsidiaries and affiliates (as defined in Section 15 of the German Stock Corporation Act) and You agree to be bound by all of the terms and conditions stated in this Developer Agreement. If You are trying to access or download the SAP Materials on behalf of Your employer or as a consultant or agent of a third party (either "Your Company"), You represent and warrant that You have the authority to act on behalf of and bind Your Company to the terms of this Developer Agreement and everywhere in this Developer Agreement that refers to 'You' or 'Your' shall also include Your Company. If You do not agree to these terms, do not click "I Accept", and do not attempt to access or use the SAP Materials.
+
+1.  LICENSE: SAP grants You a non-exclusive, non-transferable, non-sublicensable, revocable, limited use license to copy, reproduce and distribute the application programming interfaces ("API"), documentation, plug-ins, templates, scripts and sample code ("Tools") on a desktop, laptop, tablet, smart phone, or other appropriate computer device that You own or control (any, a "Computer") to create new applications ("Customer Applications"). You agree that the Customer Applications will not: (a) unreasonably impair, degrade or reduce the performance or security of any SAP software applications, services or related technology ("Software"); (b) enable the bypassing or circumventing of SAP's license restrictions and/or provide users with access to the Software to which such users are not licensed; (c) render or provide, without prior written consent from SAP, any information concerning SAP software license terms, Software, or any other information related to SAP products; or (d) permit mass data extraction from an SAP product to a non-SAP product, including use, modification, saving or other processing of such data in the non-SAP product. In exchange for the right to develop Customer Applications under this Agreement, You covenant not to assert any Intellectual Property Rights in Customer Applications created by You against any SAP product, service, or future SAP development.
+
+2.  INTELLECTUAL PROPERTY: (a) SAP or its licensors retain all ownership and intellectual property rights in the APIs, Tools and Software. You may not: a) remove or modify any marks or proprietary notices of SAP, b) provide or make the APIs, Tools or Software available to any third party, c) assign this Developer Agreement or give or transfer the APIs, Tools or Software or an interest in them to another individual or entity, d) decompile, disassemble or reverse engineer (except to the extent permitted by applicable law) the APIs Tools or Software, (e) create derivative works of or based on the APIs, Tools or Software, (f) use any SAP name, trademark or logo, or (g) use the APIs or Tools to modify existing Software or other SAP product functionality or to access the Software or other SAP products' source code or metadata.
+(b) Subject to SAP's underlying rights in any part of the APIs, Tools or Software, You retain all ownership and intellectual property rights in Your Customer Applications.
+
+3. FREE AND OPEN SOURCE COMPONENTS: The SAP Materials may include certain third party free or open source components ("FOSS Components"). You may have additional rights in such FOSS Components that are provided by the third party licensors of those components.
+
+4. THIRD PARTY DEPENDENCIES: The SAP Materials may require certain third party software dependencies ("Dependencies") for the use or operation of such SAP Materials. These dependencies may be identified by SAP in Maven POM files, product documentation or by other means. SAP does not grant You any rights in or to such Dependencies under this Developer Agreement. You are solely responsible for the acquisition, installation and use of Dependencies. SAP DOES NOT MAKE ANY REPRESENTATIONS OR WARRANTIES IN RESPECT OF DEPENDENCIES, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES OF MERCHANTABILITY AND OF FITNESS FOR A PARTICULAR PURPOSE. IN PARTICULAR, SAP DOES NOT WARRANT THAT DEPENDENCIES WILL BE AVAILABLE, ERROR FREE, INTEROPERABLE WITH THE SAP MATERIALS, SUITABLE FOR ANY PARTICULAR PURPOSE OR NON-INFRINGING.  YOU ASSUME ALL RISKS ASSOCIATED WITH THE USE OF DEPENDENCIES, INCLUDING WITHOUT LIMITATION RISKS RELATING TO QUALITY, AVAILABILITY, PERFORMANCE, DATA LOSS, UTILITY IN A PRODUCTION ENVIRONMENT, AND NON-INFRINGEMENT. IN NO EVENT WILL SAP BE LIABLE DIRECTLY OR INDIRECTLY IN RESPECT OF ANY USE OF DEPENDENCIES BY YOU.
+
+
+5.  WARRANTY:
+a)  If You are located outside the US or Canada: AS THE API AND TOOLS ARE PROVIDED TO YOU FREE OF CHARGE, SAP DOES NOT GUARANTEE OR WARRANT ANY FEATURES OR QUALITIES OF THE TOOLS OR API OR GIVE ANY UNDERTAKING WITH REGARD TO ANY OTHER QUALITY. NO SUCH WARRANTY OR UNDERTAKING SHALL BE IMPLIED BY YOU FROM ANY DESCRIPTION IN THE API OR TOOLS OR ANY AVAILABLE DOCUMENTATION OR ANY OTHER COMMUNICATION OR ADVERTISEMENT. IN PARTICULAR, SAP DOES NOT WARRANT THAT THE SOFTWARE WILL BE AVAILABLE UNINTERRUPTED, ERROR FREE, OR PERMANENTLY AVAILABLE.  FOR THE TOOLS AND API ALL WARRANTY CLAIMS ARE SUBJECT TO THE LIMITATION OF LIABILITY STIPULATED IN SECTION 4 BELOW.
+b)  If You are located in the US or Canada: THE API AND TOOLS ARE LICENSED TO YOU "AS IS", WITHOUT ANY WARRANTY, ESCROW, TRAINING, MAINTENANCE, OR SERVICE OBLIGATIONS WHATSOEVER ON THE PART OF SAP. SAP MAKES NO EXPRESS OR IMPLIED WARRANTIES OR CONDITIONS OF SALE OF ANY TYPE WHATSOEVER, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES OF MERCHANTABILITY AND OF FITNESS FOR A PARTICULAR PURPOSE. IN PARTICULAR, SAP DOES NOT WARRANT THAT THE SOFTWARE WILL BE AVAILABLE UNINTERRUPTED, ERROR FREE, OR PERMANENTLY AVAILABLE.  YOU ASSUME ALL RISKS ASSOCIATED WITH THE USE OF THE API AND TOOLS, INCLUDING WITHOUT LIMITATION RISKS RELATING TO QUALITY, AVAILABILITY, PERFORMANCE, DATA LOSS, AND UTILITY IN A PRODUCTION ENVIRONMENT.
+
+6.  LIMITATION OF LIABILITY:
+a)  If You are located outside the US or Canada: IRRESPECTIVE OF THE LEGAL REASONS, SAP SHALL ONLY BE LIABLE FOR DAMAGES UNDER THIS AGREEMENT IF SUCH DAMAGE (I) CAN BE CLAIMED UNDER THE GERMAN PRODUCT LIABILITY ACT OR (II) IS CAUSED BY INTENTIONAL MISCONDUCT OF SAP OR (III) CONSISTS OF PERSONAL INJURY. IN ALL OTHER CASES, NEITHER SAP NOR ITS EMPLOYEES, AGENTS AND SUBCONTRACTORS SHALL BE LIABLE FOR ANY KIND OF DAMAGE OR CLAIMS HEREUNDER.
+b)  If You are located in the US or Canada: IN NO EVENT SHALL SAP BE LIABLE TO YOU, YOUR COMPANY OR TO ANY THIRD PARTY FOR ANY DAMAGES IN AN AMOUNT IN EXCESS OF $100 ARISING IN CONNECTION WITH YOUR USE OF OR INABILITY TO USE THE TOOLS OR API OR IN CONNECTION WITH SAP'S PROVISION OF OR FAILURE TO PROVIDE SERVICES PERTAINING TO THE TOOLS OR API, OR AS A RESULT OF ANY DEFECT IN THE API OR TOOLS. THIS DISCLAIMER OF LIABILITY SHALL APPLY REGARDLESS OF THE FORM OF ACTION THAT MAY BE BROUGHT AGAINST SAP, WHETHER IN CONTRACT OR TORT, INCLUDING WITHOUT LIMITATION ANY ACTION FOR NEGLIGENCE. YOUR SOLE REMEDY IN THE EVENT OF BREACH OF THIS DEVELOPER AGREEMENT BY SAP OR FOR ANY OTHER CLAIM RELATED TO THE API OR TOOLS SHALL BE TERMINATION OF THIS AGREEMENT. NOTWITHSTANDING ANYTHING TO THE CONTRARY HEREIN, UNDER NO CIRCUMSTANCES SHALL SAP AND ITS LICENSORS BE LIABLE TO YOU OR ANY OTHER PERSON OR ENTITY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, OR INDIRECT DAMAGES, LOSS OF GOOD WILL OR BUSINESS PROFITS, WORK STOPPAGE, DATA LOSS, COMPUTER FAILURE OR MALFUNCTION, ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSS, OR EXEMPLARY OR PUNITIVE DAMAGES.
+
+7.  INDEMNITY: You will fully indemnify, hold harmless and defend SAP against law suits based on any claim: (a) that any Customer Application created by You infringes or misappropriates any patent, copyright, trademark, trade secrets, or other proprietary rights of a third party, or (b) related to Your alleged violation of the terms of this Developer Agreement.
+
+8.  EXPORT: The Tools and API are subject to German, EU and US export control regulations. You confirm that: a) You will not use the Tools or API for, and will not allow the Tools or API to be used for, any purposes prohibited by German, EU and US law, including, without limitation, for the development, design, manufacture or production of nuclear, chemical or biological weapons of mass destruction; b) You are not located in Cuba, Iran, Sudan, Iraq, North Korea, Syria, nor any other country to which the United States has prohibited export or that has been designated by the U.S. Government as a "terrorist supporting" country (any, an "US Embargoed Country"); c) You are not a citizen, national or resident of, and are not under the control of, a US Embargoed Country; d) You will not download or otherwise export or re-export the API or Tools, directly or indirectly, to a US Embargoed Country nor to citizens, nationals or residents of a US Embargoed Country; e) You are not listed on the United States Department of Treasury lists of Specially Designated Nationals, Specially Designated Terrorists, and Specially Designated Narcotic Traffickers, nor listed on the United States Department of Commerce Table of Denial Orders or any other U.S. government list of prohibited or restricted parties and f) You will not download or otherwise export or re-export the API or Tools , directly or indirectly, to persons on the above-mentioned lists.
+
+9.  SUPPORT: Other than what is made available on the SAP Community Website (SCN) by SAP at its sole discretion and by SCN members, SAP does not offer support for the API or Tools which are the subject of this Developer Agreement.
+
+10.  TERM AND TERMINATION: You may terminate this Developer Agreement by destroying all copies of the API and Tools on Your Computer(s). SAP may terminate Your license to use the API and Tools immediately if You fail to comply with any of the terms of this Developer Agreement, or, for SAP's convenience by providing you with ten (10) day's written notice of termination (including email). In case of termination or expiration of this Developer Agreement, You must destroy all copies of the API and Tools immediately.  In the event Your Company or any of the intellectual property you create using the API, Tools or Software are acquired (by merger, purchase of stock, assets or intellectual property or exclusive license), or You become employed, by a direct competitor of SAP, then this Development Agreement and all licenses granted in this Developer Agreement shall immediately terminate upon the date of such acquisition.
+
+11.  LAW/VENUE:
+a)  If You are located outside the US or Canada: This Developer Agreement is governed by and construed in accordance with the laws of the Germany. You and SAP agree to submit to the exclusive jurisdiction of, and venue in, the courts of Karlsruhe in Germany in any dispute arising out of or relating to this Developer Agreement.
+b)  If You are located in the US or Canada: This Developer Agreement shall be governed by and construed under the Commonwealth of Pennsylvania law without reference to its conflicts of law principles. In the event of any conflicts between foreign law, rules, and regulations, and United States of America law, rules, and regulations, United States of America law, rules, and regulations shall prevail and govern. The United Nations Convention on Contracts for the International Sale of Goods shall not apply to this Developer Agreement. The Uniform Computer Information Transactions Act as enacted shall not apply.
+
+12. MISCELLANEOUS: This Developer Agreement is the complete agreement for the API and Tools licensed (including reference to information/documentation contained in a URL). This Developer Agreement supersedes all prior or contemporaneous agreements or representations with regards to the subject matter of this Developer Agreement. If any term of this Developer Agreement is found to be invalid or unenforceable, the surviving provisions shall remain effective. SAP's failure to enforce any right or provisions stipulated in this Developer Agreement will not constitute a waiver of such provision, or any other provision of this Developer Agreement.
diff --git a/samples/spring-webflux-security-xsuaa-usage/approuter/xs-app.json b/samples/spring-webflux-security-xsuaa-usage/approuter/xs-app.json
new file mode 100644
index 00000000..2efe01fb
--- /dev/null
+++ b/samples/spring-webflux-security-xsuaa-usage/approuter/xs-app.json
@@ -0,0 +1,10 @@
+{
+  "welcomeFile": "index.html",
+  "routes": [
+    {
+      "source": "^/v1",
+      "target": "/v1",
+      "destination": "token-destination"
+    }
+  ]
+}
diff --git a/samples/spring-webflux-security-xsuaa-usage/manifest.yml b/samples/spring-webflux-security-xsuaa-usage/manifest.yml
new file mode 100644
index 00000000..e5fcdd48
--- /dev/null
+++ b/samples/spring-webflux-security-xsuaa-usage/manifest.yml
@@ -0,0 +1,32 @@
+---
+# Configuration:
+# configured for EU10. For other landscapes, please adopt LANDSCAPE_APPS_DOMAIN in ../vars.yml
+# If the route is occupied, you might need to change ID in ../vars.yml
+applications:
+# The sample application.
+- name: spring-webflux-security-xsuaa-usage
+  instances: 1
+  memory: 896M
+  routes:
+    - route: spring-webflux-security-xsuaa-usage-((ID)).((LANDSCAPE_APPS_DOMAIN))
+  path: target/spring-webflux-security-xsuaa-usage.jar
+  services:
+    - xsuaa-webflux
+
+# Application Router as web server
+- name: approuter-spring-webflux-security-xsuaa-usage
+  path: approuter
+  buildpack: nodejs_buildpack
+  memory: 128M
+  routes:
+    - route: spring-webflux-security-xsuaa-usage-web-((ID)).((LANDSCAPE_APPS_DOMAIN))
+  services:
+    - xsuaa-webflux
+  env:
+    destinations: >
+      [
+        {"name":"token-destination",
+         "url":"https://spring-webflux-security-xsuaa-usage-((ID)).((LANDSCAPE_APPS_DOMAIN))",
+         "forwardAuthToken": true}
+      ]
+...
diff --git a/samples/spring-webflux-security-xsuaa-usage/pom.xml b/samples/spring-webflux-security-xsuaa-usage/pom.xml
new file mode 100644
index 00000000..a4d16696
--- /dev/null
+++ b/samples/spring-webflux-security-xsuaa-usage/pom.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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>org.springframework.boot</groupId>
+		<artifactId>spring-boot-starter-parent</artifactId>
+		<version>2.1.5.RELEASE</version>
+		<relativePath/>
+	</parent>
+
+	<groupId>com.sap.cloud.security.samples</groupId>
+	<artifactId>spring-webflux-security-xsuaa-usage</artifactId>
+	<version>1.5.0</version>
+	<name>spring-webflux-security-xsuaa-usage</name>
+
+	<properties>
+		<java.version>1.8</java.version>
+	</properties>
+
+	<dependencies>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-webflux</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-security</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>com.sap.cloud.security.xsuaa</groupId>
+			<artifactId>spring-xsuaa</artifactId>
+			<version>1.5.0</version>
+		</dependency>
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-test</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>io.projectreactor</groupId>
+			<artifactId>reactor-test</artifactId>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
+
+	<build>
+		<finalName>${project.artifactId}</finalName>
+
+		<plugins>
+			<plugin>
+				<groupId>org.springframework.boot</groupId>
+				<artifactId>spring-boot-maven-plugin</artifactId>
+			</plugin>
+		</plugins>
+	</build>
+
+</project>
diff --git a/samples/spring-webflux-security-xsuaa-usage/src/main/java/sample/spring/webflux/xsuaa/Application.java b/samples/spring-webflux-security-xsuaa-usage/src/main/java/sample/spring/webflux/xsuaa/Application.java
new file mode 100644
index 00000000..3677d0da
--- /dev/null
+++ b/samples/spring-webflux-security-xsuaa-usage/src/main/java/sample/spring/webflux/xsuaa/Application.java
@@ -0,0 +1,13 @@
+package sample.spring.webflux.xsuaa;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Application {
+
+	public static void main(String[] args) {
+		SpringApplication.run(Application.class, args);
+	}
+
+}
diff --git a/samples/spring-webflux-security-xsuaa-usage/src/main/java/sample/spring/webflux/xsuaa/SecurityConfiguration.java b/samples/spring-webflux-security-xsuaa-usage/src/main/java/sample/spring/webflux/xsuaa/SecurityConfiguration.java
new file mode 100644
index 00000000..02f70f64
--- /dev/null
+++ b/samples/spring-webflux-security-xsuaa-usage/src/main/java/sample/spring/webflux/xsuaa/SecurityConfiguration.java
@@ -0,0 +1,36 @@
+package sample.spring.webflux.xsuaa;
+
+import com.sap.cloud.security.xsuaa.XsuaaServiceConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
+import org.springframework.security.config.web.server.ServerHttpSecurity;
+import org.springframework.security.web.server.SecurityWebFilterChain;
+
+import com.sap.cloud.security.xsuaa.token.ReactiveTokenAuthenticationConverter;
+import com.sap.cloud.security.xsuaa.token.authentication.XsuaaJwtDecoderBuilder;
+
+@EnableWebFluxSecurity
+@EnableReactiveMethodSecurity
+public class SecurityConfiguration {
+
+	private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+	@Autowired
+	XsuaaServiceConfiguration xsuaaServiceConfiguration;
+
+	@Bean
+	public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
+		http.authorizeExchange().anyExchange().authenticated()
+				.and().oauth2ResourceServer().jwt()
+				.jwtAuthenticationConverter(new ReactiveTokenAuthenticationConverter(xsuaaServiceConfiguration))
+				.jwtDecoder(new XsuaaJwtDecoderBuilder(xsuaaServiceConfiguration)
+						.withPostValidationActions(token -> logger.info("post validation action performed"))
+						.buildAsReactive());
+		return http.build();
+	}
+
+}
diff --git a/samples/spring-webflux-security-xsuaa-usage/src/main/resources/application.yml b/samples/spring-webflux-security-xsuaa-usage/src/main/resources/application.yml
new file mode 100644
index 00000000..187cd471
--- /dev/null
+++ b/samples/spring-webflux-security-xsuaa-usage/src/main/resources/application.yml
@@ -0,0 +1,8 @@
+---
+# Setting Log Levels          
+logging.level:
+  com.sap: DEBUG                      # set SAP-class loggers to DEBUG.
+  org.springframework: ERROR          # set to DEBUG to see all beans loaded and auto-config conditions met.
+  org.springframework.security: DEBUG # set to ERROR for production setups. 
+  org.springframework.webflux: DEBUG      # set to ERROR for production setups.
+  sample.spring.webflux.xsuaa: INFO
\ No newline at end of file
diff --git a/samples/spring-webflux-security-xsuaa-usage/xs-security.json b/samples/spring-webflux-security-xsuaa-usage/xs-security.json
new file mode 100644
index 00000000..f2af3a59
--- /dev/null
+++ b/samples/spring-webflux-security-xsuaa-usage/xs-security.json
@@ -0,0 +1,29 @@
+{
+	"xsappname": "spring-webflux-security-xsuaa-usage",
+	"tenant-mode": "dedicated",
+	"scopes":
+	[{
+		"name": "$XSAPPNAME.Display",
+		"description": "With this scope, information can be read."
+	}
+	],
+
+	"role-templates":
+	[{
+		"name": "AccessRole",
+		"description": "Role to call the spring-webflux-security-xsuaa-usage service",
+		"scope-references":
+		[
+			"$XSAPPNAME.Display"
+		]
+	}
+	],
+	"role-collections": [{
+		"name": "Webflux_API_Viewer",
+		"description": "Role Collection to call the spring-webflux-security-xsuaa-usage service",
+		"role-template-references": [
+			"$XSAPPNAME.AccessRole"
+		]
+	}
+	]
+}
diff --git a/spring-xsuaa/pom.xml b/spring-xsuaa/pom.xml
index 24b85e4b..3a11a675 100644
--- a/spring-xsuaa/pom.xml
+++ b/spring-xsuaa/pom.xml
@@ -60,6 +60,12 @@
 			<artifactId>caffeine</artifactId>
 			<version>${caffeine.version}</version>
 		</dependency>
+		<dependency>
+			<groupId>io.projectreactor</groupId>
+			<artifactId>reactor-core</artifactId>
+			<version>${reactor.version}</version>
+			<optional>true</optional>
+		</dependency>
 		<dependency>
 			<groupId>org.springframework.security.oauth</groupId>
 			<artifactId>spring-security-oauth2</artifactId>
@@ -113,6 +119,13 @@
 			<version>${project.version}</version>
 			<scope>test</scope>
 		</dependency>
+		<dependency>
+			<groupId>io.projectreactor</groupId>
+			<artifactId>reactor-test</artifactId>
+			<version>LATEST</version>
+			<scope>test</scope>
+		</dependency>
+
 	</dependencies>
 
 	<build>
@@ -207,5 +220,6 @@
 	<properties>
 		<caffeine.version>2.6.2</caffeine.version>
 		<servlet.version>3.1.0</servlet.version>
+		<reactor.version>3.2.8.RELEASE</reactor.version>
 	</properties>
 </project>
diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServicePropertySourceFactory.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServicePropertySourceFactory.java
index 6cb3eb35..7b0c8e8e 100644
--- a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServicePropertySourceFactory.java
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServicePropertySourceFactory.java
@@ -50,7 +50,8 @@ public class XsuaaServicePropertySourceFactory implements PropertySourceFactory
 	public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
 		XsuaaServicesParser vcapServicesParser = null;
 		if (xsuaaProperties == null) {
-			if (resource != null && resource.getResource().getFilename().length() > 0) {
+			if (resource != null && resource.getResource().getFilename() != null
+					&& !resource.getResource().getFilename().isEmpty()) {
 				vcapServicesParser = new XsuaaServicesParser(resource.getResource().getInputStream());
 			} else {
 				vcapServicesParser = new XsuaaServicesParser();
diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaAutoConfiguration.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaAutoConfiguration.java
index ec9b1ac4..aafc197d 100644
--- a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaAutoConfiguration.java
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaAutoConfiguration.java
@@ -3,6 +3,8 @@ package com.sap.cloud.security.xsuaa.autoconfiguration;
 import com.sap.cloud.security.xsuaa.XsuaaServiceConfiguration;
 import com.sap.cloud.security.xsuaa.XsuaaServiceConfigurationDefault;
 import com.sap.cloud.security.xsuaa.XsuaaServicePropertySourceFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -28,6 +30,8 @@ import org.springframework.security.oauth2.jwt.Jwt;
 @ConditionalOnProperty(prefix = "spring.xsuaa", name = "auto", havingValue = "true", matchIfMissing = true)
 public class XsuaaAutoConfiguration {
 
+	private static final Logger logger = LoggerFactory.getLogger(XsuaaAutoConfiguration.class);
+
 	@Configuration
 	@PropertySource(factory = XsuaaServicePropertySourceFactory.class, value = { "" })
 	@ConditionalOnProperty(prefix = "spring.xsuaa", name = "multiple-bindings", havingValue = "false", matchIfMissing = true)
@@ -36,6 +40,7 @@ public class XsuaaAutoConfiguration {
 		@Bean
 		@ConditionalOnMissingBean(XsuaaServiceConfiguration.class)
 		public XsuaaServiceConfiguration xsuaaServiceConfiguration() {
+			logger.info("auto-configures XsuaaServiceConfigurationDefault");
 			return new XsuaaServiceConfigurationDefault();
 		}
 	}
diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaResourceServerJwkAutoConfiguration.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaResourceServerJwkAutoConfiguration.java
index 2347d9f5..e73c8c6a 100644
--- a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaResourceServerJwkAutoConfiguration.java
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaResourceServerJwkAutoConfiguration.java
@@ -1,19 +1,19 @@
 package com.sap.cloud.security.xsuaa.autoconfiguration;
 
+import com.sap.cloud.security.xsuaa.XsuaaServiceConfiguration;
+import com.sap.cloud.security.xsuaa.token.authentication.XsuaaJwtDecoderBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.boot.autoconfigure.AutoConfigureBefore;
 import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.condition.*;
 import org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.security.oauth2.jwt.Jwt;
 import org.springframework.security.oauth2.jwt.JwtDecoder;
 
-import com.sap.cloud.security.xsuaa.XsuaaServiceConfiguration;
-import com.sap.cloud.security.xsuaa.token.authentication.XsuaaJwtDecoderBuilder;
+import static org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.*;
 
 /**
  * {@link EnableAutoConfiguration Auto-configuration} that exposes a
@@ -33,11 +33,14 @@ import com.sap.cloud.security.xsuaa.token.authentication.XsuaaJwtDecoderBuilder;
 @AutoConfigureBefore(OAuth2ResourceServerAutoConfiguration.class) // imports OAuth2ResourceServerJwtConfiguration which
 																	// specifies JwtDecoder
 public class XsuaaResourceServerJwkAutoConfiguration {
+	private final Logger logger = LoggerFactory.getLogger(getClass());
 
 	@Bean
 	@ConditionalOnBean(XsuaaServiceConfiguration.class)
+	@ConditionalOnWebApplication(type = Type.SERVLET)
 	@ConditionalOnMissingBean
 	public JwtDecoder xsuaaJwtDecoder(XsuaaServiceConfiguration xsuaaServiceConfiguration) {
+		logger.info("auto-configures JwtDecoder");
 		return new XsuaaJwtDecoderBuilder(xsuaaServiceConfiguration).build();
 	}
 }
diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/ReactiveSecurityContext.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/ReactiveSecurityContext.java
new file mode 100644
index 00000000..b7d8f625
--- /dev/null
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/ReactiveSecurityContext.java
@@ -0,0 +1,31 @@
+package com.sap.cloud.security.xsuaa.token;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.ReactiveSecurityContextHolder;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.oauth2.jwt.Jwt;
+import reactor.core.publisher.Mono;
+
+public class ReactiveSecurityContext {
+	private static Logger logger = LoggerFactory.getLogger(ReactiveSecurityContext.class);
+
+	/**
+	 * Obtain the Token object from the Spring Reactive SecurityContext
+	 *
+	 * @return Mono object of type token or error of class
+	 *         {@link AccessDeniedException} in case there is no token, user is not
+	 *         authenticated.
+	 */
+	static public Mono<XsuaaToken> getToken() {
+		return ReactiveSecurityContextHolder.getContext()
+				.switchIfEmpty(Mono.error(new AccessDeniedException("Access forbidden: not authenticated")))
+				.map(SecurityContext::getAuthentication)
+				.map(Authentication::getCredentials)
+				.map(credentials -> new XsuaaToken((Jwt) credentials))
+				.doOnSuccess(token -> logger.info("Got Jwt token: " + token.toString()))
+				.doOnError(throwable -> logger.error("ERROR to getToken", throwable));
+	}
+}
diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/ReactiveTokenAuthenticationConverter.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/ReactiveTokenAuthenticationConverter.java
new file mode 100644
index 00000000..c816d67e
--- /dev/null
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/ReactiveTokenAuthenticationConverter.java
@@ -0,0 +1,24 @@
+package com.sap.cloud.security.xsuaa.token;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.oauth2.jwt.Jwt;
+
+import com.sap.cloud.security.xsuaa.XsuaaServiceConfiguration;
+
+import reactor.core.publisher.Mono;
+
+public class ReactiveTokenAuthenticationConverter implements Converter<Jwt, Mono<AbstractAuthenticationToken>> {
+	TokenAuthenticationConverter converter;
+
+	public ReactiveTokenAuthenticationConverter(XsuaaServiceConfiguration xsuaaServiceConfiguration) {
+		this.converter = new TokenAuthenticationConverter(xsuaaServiceConfiguration.getAppId());
+		this.converter.setLocalScopeAsAuthorities(false);
+	}
+
+	@Override
+	public Mono<AbstractAuthenticationToken> convert(Jwt jwt) {
+		return Mono.just(converter.convert(jwt));
+	}
+
+}
diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/ReactiveXsuaaJwtDecoder.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/ReactiveXsuaaJwtDecoder.java
new file mode 100644
index 00000000..e1afe628
--- /dev/null
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/ReactiveXsuaaJwtDecoder.java
@@ -0,0 +1,82 @@
+package com.sap.cloud.security.xsuaa.token.authentication;
+
+import java.text.ParseException;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
+import org.springframework.security.oauth2.core.OAuth2TokenValidator;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.JwtException;
+import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
+import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
+import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
+
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTParser;
+import com.sap.cloud.security.xsuaa.XsuaaServiceConfiguration;
+
+import net.minidev.json.JSONObject;
+import reactor.core.publisher.Mono;
+
+public class ReactiveXsuaaJwtDecoder implements ReactiveJwtDecoder {
+
+	Cache<String, ReactiveJwtDecoder> cache;
+	private final XsuaaServiceConfiguration xsuaaServiceConfiguration;
+	private List<OAuth2TokenValidator<Jwt>> tokenValidators = new ArrayList<>();
+	private Collection<PostValidationAction> postValidationActions;
+
+	private static final String EXT_ATTR = "ext_attr";
+	private static final String ZDN = "zdn";
+	private static final String ZID = "zid";
+
+	// var arg it is only being converted to a List<OAuth2TokenValidator<Jwt>>,
+	// therefore its type safe.
+	ReactiveXsuaaJwtDecoder(XsuaaServiceConfiguration xsuaaServiceConfiguration, int cacheValidityInSeconds, int cacheSize,
+			OAuth2TokenValidator<Jwt> tokenValidators, Collection<PostValidationAction> postValidationActions) {
+		cache = Caffeine.newBuilder().expireAfterWrite(cacheValidityInSeconds, TimeUnit.SECONDS).maximumSize(cacheSize).build();
+		this.xsuaaServiceConfiguration = xsuaaServiceConfiguration;
+
+		this.tokenValidators.addAll(Arrays.asList(tokenValidators));
+		this.postValidationActions = postValidationActions != null ? postValidationActions : Collections.EMPTY_LIST;
+	}
+
+	@Override
+	public Mono<Jwt> decode(String token) throws JwtException {
+		return Mono.just(token).map(jwtToken -> {
+			try {
+				return JWTParser.parse(jwtToken);
+			} catch (ParseException e) {
+				throw new JwtException("Error initializing JWT decoder:" + e.getMessage());
+			}
+		}).map(jwtToken -> {
+			try {
+				String subdomain = this.getSubdomain(jwtToken);
+				String zoneId = jwtToken.getJWTClaimsSet().getStringClaim(ZID);
+				return cache.get(subdomain, k -> this.getDecoder(zoneId, subdomain));
+			} catch (ParseException e) {
+				throw new JwtException("Error initializing JWT decoder:" + e.getMessage());
+			}
+		}).flatMap(decoder -> decoder.decode(token))
+				.doOnSuccess(jwt -> postValidationActions.forEach(act -> act.perform(jwt)));
+	}
+
+	protected String getSubdomain(JWT jwt) throws ParseException {
+		String subdomain = "";
+		JSONObject extAttr = jwt.getJWTClaimsSet().getJSONObjectClaim(EXT_ATTR);
+		if (extAttr != null && extAttr.getAsString(ZDN) != null) {
+			subdomain = extAttr.getAsString(ZDN);
+		}
+		return subdomain;
+	}
+
+	private ReactiveJwtDecoder getDecoder(String zid, String subdomain) {
+		String url = xsuaaServiceConfiguration.getTokenKeyUrl(zid, subdomain);
+		NimbusReactiveJwtDecoder decoder = new NimbusReactiveJwtDecoder(url);
+		decoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(tokenValidators));
+		return decoder;
+	}
+
+}
diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaJwtDecoder.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaJwtDecoder.java
index fe05bb34..be305eec 100644
--- a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaJwtDecoder.java
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaJwtDecoder.java
@@ -8,6 +8,7 @@ import java.net.URISyntaxException;
 import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
@@ -62,7 +63,8 @@ public class XsuaaJwtDecoder implements JwtDecoder {
 	XsuaaJwtDecoder(XsuaaServiceConfiguration xsuaaServiceConfiguration, int cacheValidityInSeconds, int cacheSize,
 			OAuth2TokenValidator<Jwt> tokenValidators, Collection<PostValidationAction> postValidationActions) {
 		this(xsuaaServiceConfiguration, cacheValidityInSeconds, cacheSize, tokenValidators);
-		this.postValidationActions = postValidationActions;
+
+		this.postValidationActions = postValidationActions != null ? postValidationActions : Collections.EMPTY_LIST;
 	}
 
 	@Override
@@ -85,9 +87,8 @@ public class XsuaaJwtDecoder implements JwtDecoder {
 			validateJKU(jku, uaaDomain);
 			Jwt verifiedToken = verifyWithOnlineKey(token, jku, kid);
 
-			if (postValidationActions != null) {
-				postValidationActions.forEach(act -> act.perform(verifiedToken));
-			}
+			postValidationActions.forEach(act -> act.perform(verifiedToken));
+
 			return verifiedToken;
 		} catch (JwtValidationException ex) {
 			throw ex;
@@ -100,7 +101,6 @@ public class XsuaaJwtDecoder implements JwtDecoder {
 		if (jku != null && kid != null && uaadomain != null) {
 			return;
 		}
-
 		List<String> nullParams = new ArrayList<>();
 		if (jku == null)
 			nullParams.add("jku");
diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaJwtDecoderBuilder.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaJwtDecoderBuilder.java
index cd76f0b2..2eb555e8 100644
--- a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaJwtDecoderBuilder.java
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaJwtDecoderBuilder.java
@@ -8,6 +8,8 @@ import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
 import org.springframework.security.oauth2.core.OAuth2TokenValidator;
 import org.springframework.security.oauth2.jwt.Jwt;
 import org.springframework.security.oauth2.jwt.JwtDecoder;
+import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
+
 import org.springframework.security.oauth2.jwt.JwtValidators;
 
 public class XsuaaJwtDecoderBuilder {
@@ -46,6 +48,19 @@ public class XsuaaJwtDecoderBuilder {
 				combinedTokenValidators, postValidationActions);
 	}
 
+	/**
+	 * Assembles a ReactiveJwtDecoder
+	 *
+	 * @return ReactiveJwtDecoder
+	 */
+	public ReactiveJwtDecoder buildAsReactive() {
+		DelegatingOAuth2TokenValidator<Jwt> combinedTokenValidators = new DelegatingOAuth2TokenValidator<>(
+				defaultTokenValidators,
+				xsuaaTokenValidators);
+		return new ReactiveXsuaaJwtDecoder(configuration, decoderCacheValidity, decoderCacheSize,
+				combinedTokenValidators, postValidationActions);
+	}
+
 	/**
 	 * Decoders cache the signing keys. Overwrite the cache time (default: 900
 	 * seconds).
@@ -93,12 +108,15 @@ public class XsuaaJwtDecoderBuilder {
 	 *            the token validators
 	 * @return this
 	 */
+	// var arg it is only being assigned to a OAuth2TokenValidator<Jwt>[], therefore
+	// its type safe.
+	@SuppressWarnings("unchecked")
 	public XsuaaJwtDecoderBuilder withTokenValidators(OAuth2TokenValidator<Jwt>... tokenValidators) {
 		this.xsuaaTokenValidators = new DelegatingOAuth2TokenValidator<>(tokenValidators);
 		return this;
 	}
 
-	public XsuaaJwtDecoderBuilder withDefaultValidators(OAuth2TokenValidator<Jwt>... defaultTokenValidators) {
+	XsuaaJwtDecoderBuilder withDefaultValidators(OAuth2TokenValidator<Jwt>... defaultTokenValidators) {
 		this.defaultTokenValidators = new DelegatingOAuth2TokenValidator<>(defaultTokenValidators);
 		return this;
 	}


Test patch:
diff --git a/samples/spring-security-xsuaa-usage/src/main/java/sample/spring/xsuaa/TestController.java b/samples/spring-security-xsuaa-usage/src/main/java/sample/spring/xsuaa/TestController.java
index 1a8f32a9..db974967 100644
--- a/samples/spring-security-xsuaa-usage/src/main/java/sample/spring/xsuaa/TestController.java
+++ b/samples/spring-security-xsuaa-usage/src/main/java/sample/spring/xsuaa/TestController.java
@@ -4,7 +4,6 @@ import java.util.HashMap;
 import java.util.Map;
 
 import com.sap.cloud.security.xsuaa.token.Token;
-import com.sap.cloud.security.xsuaa.token.XsuaaToken;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/samples/spring-webflux-security-xsuaa-usage/src/main/java/sample/spring/webflux/xsuaa/TestController.java b/samples/spring-webflux-security-xsuaa-usage/src/main/java/sample/spring/webflux/xsuaa/TestController.java
new file mode 100644
index 00000000..dc3370bc
--- /dev/null
+++ b/samples/spring-webflux-security-xsuaa-usage/src/main/java/sample/spring/webflux/xsuaa/TestController.java
@@ -0,0 +1,28 @@
+package sample.spring.webflux.xsuaa;
+
+import com.sap.cloud.security.xsuaa.token.ReactiveSecurityContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+import reactor.core.publisher.Mono;
+
+@RestController
+public class TestController {
+
+    @GetMapping("/v1/sayHello")
+    public Mono<ResponseEntity<String>> sayHello() {
+        ResponseEntity.BodyBuilder unAuthenticated = ResponseEntity.status(HttpStatus.UNAUTHORIZED);
+
+        return ReactiveSecurityContext.getToken()
+                .doOnError(throwable -> Mono.just(unAuthenticated))
+                .flatMap(token -> {
+                    return Mono.just(ResponseEntity.ok()
+                            .contentType(MediaType.TEXT_PLAIN)
+                            .body("Authorities: " + token.getAuthorities()));
+                });
+    }
+}
diff --git a/samples/spring-webflux-security-xsuaa-usage/src/test/java/ApplicationTest.java b/samples/spring-webflux-security-xsuaa-usage/src/test/java/ApplicationTest.java
new file mode 100644
index 00000000..726e0e44
--- /dev/null
+++ b/samples/spring-webflux-security-xsuaa-usage/src/test/java/ApplicationTest.java
@@ -0,0 +1,15 @@
+import com.sap.cloud.security.xsuaa.autoconfiguration.XsuaaAutoConfiguration;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = { XsuaaAutoConfiguration.class })
+public class ApplicationTest {
+
+    @Test
+    public void contextLoads() {
+    }
+
+}
\ No newline at end of file
diff --git a/spring-xsuaa-test/src/main/java/com/sap/cloud/security/xsuaa/test/JwtGenerator.java b/spring-xsuaa-test/src/main/java/com/sap/cloud/security/xsuaa/test/JwtGenerator.java
index 709bae56..b98f74f2 100644
--- a/spring-xsuaa-test/src/main/java/com/sap/cloud/security/xsuaa/test/JwtGenerator.java
+++ b/spring-xsuaa-test/src/main/java/com/sap/cloud/security/xsuaa/test/JwtGenerator.java
@@ -43,6 +43,15 @@ public class JwtGenerator {
 		static final String CLAIM_EXTERNAL_ATTR = "ext_attr";
 	}
 
+    public final class TokenHeaders {
+        private TokenHeaders() {
+            throw new IllegalStateException("Utility class");
+        }
+
+        static final String JKU = "jku";
+        static final String KID = "kid";
+    }
+
 	// must match the port defined in XsuaaMockWebServer
 	private static final int MOCK_XSUAA_PORT = 33195;
 
@@ -93,9 +102,13 @@ public class JwtGenerator {
 	 *            derived from that.
 	 */
 	public JwtGenerator(String clientId, String subdomain) {
+		this(clientId, subdomain, subdomain + "-id");
+	}
+
+	public JwtGenerator(String clientId, String subdomain, String identityZoneId) {
 		this.clientId = clientId;
 		this.subdomain = subdomain;
-		this.identityZoneId = subdomain + "-id";
+		this.identityZoneId = identityZoneId;
 		this.jku = createJku(subdomain);
 	}
 
@@ -227,7 +240,7 @@ public class JwtGenerator {
 		for (Map.Entry<String, Object> customClaim : customClaims.entrySet()) {
 			claimsSetBuilder.claim(customClaim.getKey(), customClaim.getValue());
 		}
-		return createFromClaims(claimsSetBuilder.build().toString(), jwtHeaderKeyId, jku);
+		return createFromClaims(claimsSetBuilder.build().toString(), getHeaderMap(jwtHeaderKeyId, jku));
 	}
 
 	private static String createJku(String subdomain) {
@@ -252,7 +265,7 @@ public class JwtGenerator {
 	/**
 	 * Creates a Jwt from a template file, which contains the claims. Optionally,
 	 * configure the "keyId" header via {@link #setJwtHeaderKeyId(String)}
-	 * 
+	 *
 	 * This replaces these placeholders:
 	 * <ul>
 	 * <li>"$exp" with a date, that will not expire</li>
@@ -265,7 +278,7 @@ public class JwtGenerator {
 	 * <li>"$username" with the configured user name {@link #setUserName(String)}
 	 * </li>
 	 * </ul>
-	 * 
+	 *
 	 * @param pathToTemplate
 	 *            classpath resource
 	 * @return a jwt
@@ -275,7 +288,7 @@ public class JwtGenerator {
 	public Jwt createFromTemplate(String pathToTemplate) throws IOException {
 		String claimsFromTemplate = IOUtils.resourceToString(pathToTemplate, StandardCharsets.UTF_8);
 		String claimsWithReplacements = replacePlaceholders(claimsFromTemplate);
-		return createFromClaims(claimsWithReplacements, jwtHeaderKeyId, jku);
+		return createFromClaims(claimsWithReplacements, getHeaderMap(jwtHeaderKeyId, jku));
 	}
 
 	/**
@@ -299,15 +312,29 @@ public class JwtGenerator {
 	 * @return a jwt
 	 */
 	public static Jwt createFromClaims(JWTClaimsSet claimsSet) {
-		return createFromClaims(claimsSet.toString(), null, null);
+		return createFromClaims(claimsSet.toString(), Collections.EMPTY_MAP);
 	}
 
+
+    /**
+     * Creates an individual Jwt based on the provided set of claims.
+     *
+     * @param claimsSet
+     *            that can be created with Nimbus JOSE + JWT JWTClaimsSet.Builder
+     * @param tokenHeaders
+     *            that contains a set of headers that should be included in the final jwt.
+     * @return a jwt
+     */
+    public static Jwt createFromClaims(JWTClaimsSet claimsSet, Map<String, String> tokenHeaders) {
+        return createFromClaims(claimsSet.toString(), tokenHeaders);
+    }
+
 	/**
 	 * Builds a basic set of claims
 	 *
 	 * @return a basic set of claims
 	 */
-	private JWTClaimsSet.Builder getBasicClaimSet() {
+	public JWTClaimsSet.Builder getBasicClaimSet() {
 		return new JWTClaimsSet.Builder()
 				.issueTime(new Date())
 				.expirationTime(JwtGenerator.NO_EXPIRE_DATE)
@@ -321,11 +348,31 @@ public class JwtGenerator {
 				.claim(TokenClaims.CLAIM_GRANT_TYPE, GRANT_TYPE);
 	}
 
-	private static Jwt createFromClaims(String claims, String jwtHeaderKeyId, String jku) {
-		String token = signAndEncodeToken(claims, jwtHeaderKeyId, jku);
+    /**
+     * Builds a basic set of claims
+     *
+     * @return a basic set of claims
+     */
+    public Map<String, String> getBasicHeaders() {
+        return getHeaderMap(jwtHeaderKeyId, createJku(subdomain));
+    }
+
+	private static Jwt createFromClaims(String claims, Map<String, String> headers) {
+		String token = signAndEncodeToken(claims, headers);
 		return convertTokenToOAuthJwt(token);
 	}
 
+	private static Map<String, String> getHeaderMap(String jwtHeaderKeyId, String jku){
+        Map<String, String> headers = new HashMap<>();
+        if (jwtHeaderKeyId != null) {
+            headers.put("kid", jwtHeaderKeyId);
+        }
+        if (jku != null) {
+            headers.put("jku", jku);
+        }
+        return headers;
+    }
+
 	private String replacePlaceholders(String claims) {
 		claims = claims.replace("$exp", String.valueOf(NO_EXPIRE));
 		claims = claims.replace("$clientid", clientId);
@@ -336,18 +383,10 @@ public class JwtGenerator {
 		return claims;
 	}
 
-	private static String signAndEncodeToken(String claims, String keyId, String jku) {
+	private static String signAndEncodeToken(String claims, Map<String, String> tokenHeaders) {
 		RsaSigner signer = new RsaSigner(readPrivateKeyFromFile());
 
-		Map<String, String> headers = new HashMap<>();
-		if (keyId != null) {
-			headers.put("kid", keyId);
-		}
-		if (jku != null) {
-			headers.put("jku", jku);
-		}
-
-		org.springframework.security.jwt.Jwt jwt = JwtHelper.encode(claims, signer, headers);
+		org.springframework.security.jwt.Jwt jwt = JwtHelper.encode(claims, signer, tokenHeaders);
 
 		return jwt.getEncoded();
 	}
@@ -380,4 +419,4 @@ public class JwtGenerator {
 	class ExternalAttrClaim {
 		public String zdn = subdomain;
 	}
-}
\ No newline at end of file
+}
diff --git a/spring-xsuaa-test/src/test/java/com/sap/cloud/security/xsuaa/test/JwtGeneratorTest.java b/spring-xsuaa-test/src/test/java/com/sap/cloud/security/xsuaa/test/JwtGeneratorTest.java
index 5264d2af..0404bf47 100644
--- a/spring-xsuaa-test/src/test/java/com/sap/cloud/security/xsuaa/test/JwtGeneratorTest.java
+++ b/spring-xsuaa-test/src/test/java/com/sap/cloud/security/xsuaa/test/JwtGeneratorTest.java
@@ -1,16 +1,16 @@
 package com.sap.cloud.security.xsuaa.test;
 
 import static com.sap.cloud.security.xsuaa.test.TestConstants.*;
-import static org.hamcrest.CoreMatchers.*;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.contains;
-import static org.hamcrest.Matchers.isEmptyString;
-import static org.hamcrest.Matchers.startsWith;
+import static org.hamcrest.Matchers.*;
 
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
 
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.sap.cloud.security.xsuaa.test.JwtGenerator.TokenClaims;
+import com.sap.cloud.security.xsuaa.test.JwtGenerator.TokenHeaders;
 import org.junit.Before;
 import org.junit.Test;
 import org.springframework.security.oauth2.jwt.Jwt;
@@ -129,6 +129,33 @@ public class JwtGeneratorTest {
 		assertThat(jwt.getAudience(), hasItem("app2"));
 	}
 
+	@Test
+	public void testTokenWithCustomClaimsAndHeaders() {
+		JwtGenerator jwtGenerator = new JwtGenerator("clientId", "subdomain", "tenantId");
+		JWTClaimsSet.Builder builder = jwtGenerator.getBasicClaimSet();
+		builder.claim(TokenClaims.CLAIM_USER_NAME, "new_testuser");
+
+		Map<String, String> map = jwtGenerator.getBasicHeaders();
+
+		Jwt jwt = JwtGenerator.createFromClaims(builder.build(), map);
+
+		assertThat(jwt.getHeaders(), hasEntry(TokenHeaders.JKU, "http://localhost:33195/subdomain/token_keys"));
+		assertThat(jwt.getHeaders(), hasEntry(TokenHeaders.KID, "legacy-token-key"));
+		assertThat(jwt.getClaims(), hasEntry(TokenClaims.CLAIM_USER_NAME, "new_testuser"));
+	}
+
+	@Test
+	public void testBasicJwtTokenWithIdentityZoneId() {
+		JwtGenerator jwtGenerator = new JwtGenerator("clientId", "subdomain", "tenantId");
+		Jwt jwt = jwtGenerator.getToken();
+
+		assertThat(jwt.getHeaders(), hasEntry(TokenHeaders.JKU, "http://localhost:33195/subdomain/token_keys"));
+		assertThat(jwt.getHeaders(), hasEntry(TokenHeaders.KID, "legacy-token-key"));
+		assertThat(jwt.getClaims(), hasEntry(TokenClaims.CLAIM_CLIENT_ID, "clientId"));
+		assertThat(jwt.getClaims(), hasEntry(TokenClaims.CLAIM_ZDN, "subdomain"));
+		assertThat(jwt.getClaims(), hasEntry(TokenClaims.CLAIM_ZONE_ID, "tenantId"));
+	}
+
 	private String getExternalAttributeFromClaim(Jwt jwt, String attributeName) {
 		Map<String, Object> externalAttribute = jwt.getClaimAsMap("ext_attr");
 		return externalAttribute == null ? null : (String) externalAttribute.get(attributeName);
diff --git a/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaResourceServerJwkAutoConfigurationTest.java b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaResourceServerJwkAutoConfigurationTest.java
index 8c6e85b4..47bacbd1 100644
--- a/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaResourceServerJwkAutoConfigurationTest.java
+++ b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaResourceServerJwkAutoConfigurationTest.java
@@ -7,13 +7,15 @@ import static org.hamcrest.Matchers.nullValue;
 import static org.junit.Assert.assertThat;
 
 import com.sap.cloud.security.xsuaa.token.authentication.XsuaaJwtDecoder;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.autoconfigure.AutoConfigurations;
 import org.springframework.boot.test.context.FilteredClassLoader;
 import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
+import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -28,13 +30,19 @@ public class XsuaaResourceServerJwkAutoConfigurationTest {
 
 	// create an ApplicationContextRunner that will create a context with the
 	// configuration under test.
-	private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
-			.withConfiguration(
-					AutoConfigurations.of(XsuaaResourceServerJwkAutoConfiguration.class, XsuaaAutoConfiguration.class));
+	private WebApplicationContextRunner contextRunner;
 
 	@Autowired
 	private ApplicationContext context;
 
+	@Before
+	public void setup() {
+		contextRunner = new WebApplicationContextRunner()
+				.withConfiguration(
+						AutoConfigurations.of(XsuaaResourceServerJwkAutoConfiguration.class,
+								XsuaaAutoConfiguration.class));
+	}
+
 	@Test
 	public final void autoConfigurationActive() {
 		contextRunner.run((context) -> {
@@ -65,7 +73,7 @@ public class XsuaaResourceServerJwkAutoConfigurationTest {
 	@Test
 	public final void autoConfigurationWithoutXsuaaServiceConfigurationOnClasspathInactive() {
 		contextRunner.withClassLoader(
-				new FilteredClassLoader(Jwt.class)) // make sure XsuaaServiceConfiguration.class is not on the classpath
+				new FilteredClassLoader(Jwt.class)) // make sure Jwt.class is not on the classpath
 				.run((context) -> {
 					assertThat(context.containsBean("xsuaaJwtDecoder"), is(false));
 				});
@@ -82,6 +90,18 @@ public class XsuaaResourceServerJwkAutoConfigurationTest {
 				});
 	}
 
+	@Test
+	public void autoConfigurationDisabledWhenSpringReactorIsActive() {
+		ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
+				.withConfiguration(
+						AutoConfigurations.of(XsuaaResourceServerJwkAutoConfiguration.class,
+								XsuaaAutoConfiguration.class));
+
+		contextRunner.run((context) -> {
+			assertThat(context.containsBean("xsuaaJwtDecoder"), is(false));
+		});
+	}
+
 	@Configuration
 	public static class UserConfiguration {
 
diff --git a/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/token/ReactiveSecurityContextTest.java b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/token/ReactiveSecurityContextTest.java
new file mode 100644
index 00000000..96ef8cd3
--- /dev/null
+++ b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/token/ReactiveSecurityContextTest.java
@@ -0,0 +1,42 @@
+package com.sap.cloud.security.xsuaa.token;
+
+import com.sap.cloud.security.xsuaa.test.JwtGenerator;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.core.context.ReactiveSecurityContextHolder;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextImpl;
+import org.springframework.security.oauth2.jwt.Jwt;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+
+public class ReactiveSecurityContextTest {
+
+	@Test
+	public void unauthenticated() {
+		SecurityContext expectedContext = new SecurityContextImpl();
+		ReactiveSecurityContextHolder.withSecurityContext(Mono.just(expectedContext));
+		Mono<XsuaaToken> tokenMono = ReactiveSecurityContext.getToken();
+
+		StepVerifier.create(tokenMono)
+				.expectError(AccessDeniedException.class)
+				.verify();
+	}
+
+	@Test
+	@Ignore
+	public void authenticated() {
+		XsuaaToken jwt = new XsuaaToken(new JwtGenerator().setUserName("user").getToken());
+		SecurityContext expectedContext = new SecurityContextImpl(
+				new TestingAuthenticationToken("user", jwt, "ROLE_USER"));
+		ReactiveSecurityContextHolder.withSecurityContext(Mono.just(expectedContext));
+		Mono<XsuaaToken> tokenMono = ReactiveSecurityContext.getToken();
+
+		StepVerifier.create(tokenMono)
+				.expectNext(jwt)
+				.verifyComplete();
+	}
+
+}
diff --git a/spring-xsuaa/src/test/java/com/sap/xs2/security/container/SpringSecurityContextTest.java b/spring-xsuaa/src/test/java/com/sap/xs2/security/container/SpringSecurityContextTest.java
index a6557f6f..8b33d1e5 100644
--- a/spring-xsuaa/src/test/java/com/sap/xs2/security/container/SpringSecurityContextTest.java
+++ b/spring-xsuaa/src/test/java/com/sap/xs2/security/container/SpringSecurityContextTest.java
@@ -33,8 +33,6 @@ public class SpringSecurityContextTest {
 	private static final String SUBDOMAIN_2 = "subdomain-2";
 	private static final String CLIENT_ID = "sb-xsappname!t123";
 
-	ExecutorService executor = Executors.newFixedThreadPool(10);
-
 	@Before
 	public void setup() {
 		token_1 = new JwtGenerator(CLIENT_ID, SUBDOMAIN_1).getToken();
@@ -70,6 +68,8 @@ public class SpringSecurityContextTest {
 	public void setSecurityContext() throws InterruptedException, ExecutionException {
 		SpringSecurityContextTest.countThreads = 0;
 
+		ExecutorService executor = Executors.newFixedThreadPool(10);
+
 		Future<Token> future_1 = executor.submit(() -> {
 			initSecurityContextWithToken(token_1);
 			return SecurityContext.getToken();