Repository: SAP/cloud-security-xsuaa-integration
Pull request #118 (2019-08-01 12:25:43)

Deprecates SecurityContext to avoid issues with Security Container lib
- preparation for 2.0.0

Considers this issue:
- https://github.com/SAP/cloud-security-xsuaa-integration/issues/119


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/.travis.yml b/.travis.yml
index 0acb766d..08df84f0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,3 +2,9 @@ language: java
 
 script:
   - mvn clean verify
+  - cd samples/spring-security-basic-auth
+  - mvn clean verify
+  - cd ../../samples/spring-security-xsuaa-usage
+  - mvn clean verify
+  - cd ../../samples/spring-webflux-security-xsuaa-usage
+  - mvn clean verify
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4414f825..a1eb2a28 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,10 +7,11 @@ All notable changes to this project will be documented in this file.
 * Supports reactive ServerHttpSecurity (Spring webflux). Have a look at the (webflux sample application)[samples/spring-webflux-security-xsuaa-usage/README.md]
 * Some enhancements for XSUAA integration
 * To make sure that the Spring SecurityContext is always initialized with a validated token use `SpringSecurityContext.init()` method as documented [here](spring-xsuaa/README.md)
+* Use `SpringSecurityContext` instead of `SecurityContext`, which gets deprecated in this version. With version `2.0.0` we want to get rid of package `com.sap.xs2.security.container` in order to avoid Class Loader issues, when an application makes use of SAP-libraries using the SAP-internal container lib.
 
 ### Incompatible changes
-* As of version `1.6.0` you need to make use of XSUAA Spring Boot Starter in order to leverage auto-configuration (see Troubleshoot section [here](spring-xsuaa/README.md))
-* To avoid issues, when an application makes use of SAP-libraries using the SAP-internal container lib, use `SpringSecurityContext` instead of `SecurityContext`.
+* As of version `1.6.0` you need to make use of XSUAA Spring Boot Starter in order to leverage auto-configuration (see "Troubleshoot" section [here](spring-xsuaa/README.md#troubleshoot))
+
 
 ## 1.5.0
 * Supports `jku` URI which is provided as part of the JSON Web Signature (JWS). The `jku` of the Jwt token header references the public key URI of the Xsuaa OAuth Authorization Server, and needs to match to the `xsuaa.uaadomain`.
diff --git a/api/pom.xml b/api/pom.xml
index f03c51d1..16d53d33 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -9,7 +9,7 @@
 	<parent>
 		<groupId>com.sap.cloud.security.xsuaa</groupId>
 		<artifactId>parent</artifactId>
-		<version>1.5.0</version>
+		<version>1.6.0</version>
 	</parent>
 
 	<packaging>jar</packaging>
diff --git a/pom.xml b/pom.xml
index d6e71391..666514b0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -61,6 +61,7 @@
 
 	<modules>
 		<module>api</module>
+		<module>token-client</module>
 		<module>spring-xsuaa</module>
 		<module>spring-xsuaa-it</module>
 		<module>spring-xsuaa-test</module>
diff --git a/samples/sap-java-buildpack-api-usage/README.md b/samples/sap-java-buildpack-api-usage/README.md
index e6080424..0a060def 100644
--- a/samples/sap-java-buildpack-api-usage/README.md
+++ b/samples/sap-java-buildpack-api-usage/README.md
@@ -116,7 +116,7 @@ Further up-to-date information you can get on sap.help.com:
 - [Maintain Roles for Applications](https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/7596a0bdab4649ac8a6f6721dc72db19.html).
 
 ## Access the application
-After deployment, the application router will trigger authentication. If you have assigned the role-collection provided in the [xs-security.json](./xs-security.json) to your user, you will see an output like:
+After deployment, the application router will trigger authentication. If you have assigned the role-collection provided in the [xs-security.json](./xs-security.json) to your user, you will see an output like when calling `https://approuter-sap-java-buildpack-api-usage-<<ID>>.<<LANDSCAPE_APPS_DOMAIN>>`:
 
 ```
 Client ID: sap-java-buildpack-api-usage!t5721
@@ -130,3 +130,11 @@ If not you should get a `403` status code (Forbidden).
 
 > Note: you can find the route of your approuter application using `cf app approuter-sap-java-buildpack-api-usage`.
 
+## Clean-Up
+
+Finally delete your application and your service instances using the following commands:
+```
+cf delete -f sap-java-buildpack-api-usage
+cf delete -f approuter-sap-java-buildpack-api-usage
+cf delete-service -f xsuaa-buildpack
+```
\ No newline at end of file
diff --git a/samples/spring-security-basic-auth/README.md b/samples/spring-security-basic-auth/README.md
index e2caf334..9ed5f9fe 100644
--- a/samples/spring-security-basic-auth/README.md
+++ b/samples/spring-security-basic-auth/README.md
@@ -125,4 +125,11 @@ You will get a response like:
 }
 ```
 
+## Clean-Up
+
+Finally delete your application and your service instances using the following commands:
+```
+cf delete -f spring-security-basic-auth
+cf delete-service -f xsuaa-basic
+```
 
diff --git a/samples/spring-security-xsuaa-usage/README.md b/samples/spring-security-xsuaa-usage/README.md
index e5a73d6c..43027664 100644
--- a/samples/spring-security-xsuaa-usage/README.md
+++ b/samples/spring-security-xsuaa-usage/README.md
@@ -56,7 +56,7 @@ After deployment, the AppRouter will trigger authentication automatically when y
 
 * `https://spring-security-xsuaa-usage-web-<ID>.<LANDSCAPE_APPS_DOMAIN>/v1/sayHello` - GET request that provides XSUAA user token details, but only if token matches.
 * `https://spring-security-xsuaa-usage-web-<ID>.<LANDSCAPE_APPS_DOMAIN>/v1/method` - GET request executes a method secured with Spring Global Method Security.
-* `https://spring-security-xsuaa-usage-web-<ID>.<LANDSCAPE_APPS_DOMAIN>/v1/readData` - GET request to read sensitive data via Global Method Security.
+* `https://spring-security-xsuaa-usage-web-<ID>.<LANDSCAPE_APPS_DOMAIN>/v1/getAdminData` - GET request to read sensitive data via Global Method Security. You will get a `403` (UNAUTHORIZED), in case you do not have `Admin` scope.
 * `https://spring-security-xsuaa-usage-web-<ID>.<LANDSCAPE_APPS_DOMAIN>/v2/sayHello` - GET request that logs generic Jwt info, but only if token matches. 
 
 Have a look into the logs with:
@@ -65,3 +65,12 @@ cf logs spring-security-xsuaa-usage --recent
 ``
 
 > Note: https://spring-security-xsuaa-usage-web-<ID>.<LANDSCAPE_APPS_DOMAIN> points to the url of the AppRouter. Get all app routes with `cf apps`.
+
+## Clean-Up
+
+Finally delete your application and your service instances using the following commands:
+```
+cf delete -f spring-security-xsuaa-usage
+cf delete -f approuter-spring-security-xsuaa-usage
+cf delete-service -f xsuaa-authentication
+```
diff --git a/samples/spring-security-xsuaa-usage/pom.xml b/samples/spring-security-xsuaa-usage/pom.xml
index b5db23f2..3a3d54bb 100644
--- a/samples/spring-security-xsuaa-usage/pom.xml
+++ b/samples/spring-security-xsuaa-usage/pom.xml
@@ -8,13 +8,13 @@
 	<parent>
 		<groupId>org.springframework.boot</groupId>
 		<artifactId>spring-boot-starter-parent</artifactId>
-		<version>2.1.5.RELEASE</version>
+		<version>2.1.6.RELEASE</version>
 		<relativePath/>
 	</parent>
 
 	<groupId>com.sap.cloud.security.samples</groupId>
 	<artifactId>spring-security-xsuaa-usage</artifactId>
-	<version>1.6.0</version>
+	<version>1.3.0</version>
 	<name>spring-security-xsuaa-usage</name>
 
 	<properties>
@@ -31,7 +31,7 @@
         <dependency>
             <groupId>com.sap.cloud.security.xsuaa</groupId>
             <artifactId>xsuaa-spring-boot-starter</artifactId>
-            <version>${project.version}</version>
+            <version>1.6.0</version>
         </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
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..8c6eafae
--- /dev/null
+++ b/samples/spring-webflux-security-xsuaa-usage/README.md
@@ -0,0 +1,67 @@
+# 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
+```
+
+
+## Cockpit administration tasks: Assign Role to your User
+Finally, as part of your Identity Provider, e.g. SAP ID Service, assign the deployed Role Collection(s) such as `Webflux_API_Viewer` to your user as depicted in the screenshot below and as documented [here](https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/9e1bf57130ef466e8017eab298b40e5e.html).
+
+![](../images/SAP_CP_Cockpit_AssignRoleCollectionToUser.png)
+
+Further up-to-date information you can get on sap.help.com:
+- [Maintain Role Collections](https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/d5f1612d8230448bb6c02a7d9c8ac0d1.html)
+- [Maintain Roles for Applications](https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/7596a0bdab4649ac8a6f6721dc72db19.html).
+
+
+## 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.
+
+## Clean-Up
+
+Finally delete your application and your service instances using the following commands:
+```
+cf delete -f spring-webflux-security-xsuaa-usage
+cf delete -f approuter-spring-webflux-security-xsuaa-usage
+cf delete-service -f xsuaa-webflux
+```
\ 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..0a40d9be
--- /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..593dd19e
--- /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..f32b8659
--- /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..22e6fd91
--- /dev/null
+++ b/samples/spring-webflux-security-xsuaa-usage/pom.xml
@@ -0,0 +1,88 @@
+<?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.6.RELEASE</version>
+        <relativePath/>
+    </parent>
+
+    <groupId>com.sap.cloud.security.samples</groupId>
+    <artifactId>spring-webflux-security-xsuaa-usage</artifactId>
+    <version>1.6.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>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.sap.cloud.security.xsuaa</groupId>
+            <artifactId>spring-xsuaa-test</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.sap.cloud.security.xsuaa</groupId>
+            <artifactId>spring-xsuaa-mock</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </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..de469c95
--- /dev/null
+++ b/samples/spring-webflux-security-xsuaa-usage/src/main/java/sample/spring/webflux/xsuaa/SecurityConfiguration.java
@@ -0,0 +1,59 @@
+package sample.spring.webflux.xsuaa;
+
+import com.sap.cloud.security.xsuaa.XsuaaServiceConfiguration;
+import com.sap.cloud.security.xsuaa.XsuaaServiceConfigurationDefault;
+import com.sap.cloud.security.xsuaa.XsuaaServicePropertySourceFactory;
+import com.sap.cloud.security.xsuaa.token.ReactiveTokenAuthenticationConverter;
+import com.sap.cloud.security.xsuaa.token.TokenAuthenticationConverter;
+import com.sap.cloud.security.xsuaa.token.authentication.XsuaaJwtDecoderBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.PropertySource;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+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.oauth2.jwt.Jwt;
+import org.springframework.security.web.server.SecurityWebFilterChain;
+import reactor.core.publisher.Mono;
+
+@EnableWebFluxSecurity
+@EnableReactiveMethodSecurity
+@PropertySource(factory = XsuaaServicePropertySourceFactory.class, value = { "" })
+public class SecurityConfiguration {
+
+	private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+	@Autowired
+	XsuaaServiceConfiguration xsuaaServiceConfiguration;
+
+	@Bean
+	public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
+		http.authorizeExchange()
+				.pathMatchers("/v1/sayHello").hasAuthority("Read")
+				.and().oauth2ResourceServer().jwt()
+				.jwtAuthenticationConverter(getJwtAuthenticationConverter())
+				.jwtDecoder(new XsuaaJwtDecoderBuilder(xsuaaServiceConfiguration)
+						.withPostValidationActions(token -> logger.info("post validation action performed"))
+						.buildAsReactive());
+		return http.build();
+	}
+
+	@Bean
+	public XsuaaServiceConfiguration xsuaaServiceConfiguration() {
+		return new XsuaaServiceConfigurationDefault();
+	}
+
+	/**
+	 * Customizes how GrantedAuthority are derived from a Jwt
+	 */
+	Converter<Jwt, Mono<AbstractAuthenticationToken>> getJwtAuthenticationConverter() {
+		ReactiveTokenAuthenticationConverter converter = new ReactiveTokenAuthenticationConverter(xsuaaServiceConfiguration);
+		converter.setLocalScopeAsAuthorities(true);
+		return converter;
+	}
+
+}
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..72dde177
--- /dev/null
+++ b/samples/spring-webflux-security-xsuaa-usage/xs-security.json
@@ -0,0 +1,28 @@
+{
+  "xsappname": "spring-webflux-security-xsuaa-usage",
+  "tenant-mode": "dedicated",
+  "scopes": [
+    {
+      "name": "$XSAPPNAME.Read",
+      "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.Read"
+      ]
+    }
+  ],
+  "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/samples/vars.yml b/samples/vars.yml
index a611d9d6..0a3d5f51 100644
--- a/samples/vars.yml
+++ b/samples/vars.yml
@@ -1,6 +1,6 @@
 ---
 # some data to make the urls unique
-# change to another value this if the hostname is already taken
+# change to another value, e.g. your User ID
 ID: 00-00-00
 
 # Choose cfapps.eu10.hana.ondemand.com for the EU10 landscape, cfapps.us10.hana.ondemand.com for US10
diff --git a/spring-xsuaa-it/pom.xml b/spring-xsuaa-it/pom.xml
index 617c43a9..4fa41723 100644
--- a/spring-xsuaa-it/pom.xml
+++ b/spring-xsuaa-it/pom.xml
@@ -144,6 +144,14 @@
 					</execution>
 				</executions>
 			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<configuration>
+					<source>8</source>
+					<target>8</target>
+				</configuration>
+			</plugin>
 		</plugins>
 	</build>
 </project>
diff --git a/spring-xsuaa-it/src/main/resources/application-uaamock.yml b/spring-xsuaa-it/src/main/resources/application-uaamock.yml
deleted file mode 100644
index e1cd3416..00000000
--- a/spring-xsuaa-it/src/main/resources/application-uaamock.yml
+++ /dev/null
@@ -1,3 +0,0 @@
-spring.security.oauth2.resourceserver.jwt.jwk-set-uri: ${mockxsuaaserver.url}/token_keys
-xsuaa:
-  url: '${mockxsuaaserver.url}'
\ No newline at end of file
diff --git a/spring-xsuaa-mock/README.md b/spring-xsuaa-mock/README.md
index c74b05d0..75c3106d 100644
--- a/spring-xsuaa-mock/README.md
+++ b/spring-xsuaa-mock/README.md
@@ -62,7 +62,7 @@ org.springframework.boot.env.EnvironmentPostProcessor=<<your package>>.XsuaaMock
 
 ### XSUAA Service Configuration
 
-From version `1.5.0` on the [`MockXsuaaServiceConfiguration`](src/main/java/com/sap/cloud/security/xsuaa/mock/MockXsuaaServiceConfiguration.java) is auto-configured. This class overwrites Xsuaa url and uaadomain to point to the Xsuaa Mock Web Server. This is relevant for validating the `jku` URI that is provided as part of the JSON Web Signature (JWS). The `jku` of the Jwt token issued by the `JwtGenerator` references the public key URI of the `XsuaaMockWebServer` used for generating the signature.
+From version `1.5.0` on the [`MockXsuaaServiceConfiguration`](src/main/java/com/sap/cloud/security/xsuaa/mock/MockXsuaaServiceConfiguration.java) is auto-configured [here](src/main/java/com/sap/cloud/security/xsuaa/mock/autoconfiguration/XsuaaMockAutoConfiguration.java). This class overwrites Xsuaa url and uaadomain to point to the Xsuaa Mock Web Server. This is relevant for validating the `jku` URI that is provided as part of the JSON Web Signature (JWS). The `jku` of the Jwt token issued by the `JwtGenerator` references the public key URI of the `XsuaaMockWebServer` used for generating the signature.
 
 ### Extendability
 Note: it is possible to extend the dispatcher and pass this to the `XsuaaMockWebServer` constructor. An example `XsuaaMockPostProcessor` implementation can be found [here](src/test/java/com/sap/cloud/security/xsuaa/mock/XsuaaMockPostProcessor.java).
diff --git a/spring-xsuaa-starter/pom.xml b/spring-xsuaa-starter/pom.xml
index 3d6e6803..a4ce5dcd 100644
--- a/spring-xsuaa-starter/pom.xml
+++ b/spring-xsuaa-starter/pom.xml
@@ -22,7 +22,7 @@
 			<dependency>
 				<groupId>org.springframework.boot</groupId>
 				<artifactId>spring-boot-dependencies</artifactId>
-				<version>2.1.5.RELEASE</version>
+				<version>${spring-boot.version}</version>
 				<type>pom</type>
 				<scope>import</scope>
 			</dependency>
@@ -39,6 +39,7 @@
 		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 		<java.version>1.8</java.version>
+		<spring-boot.version>2.1.5.RELEASE</spring-boot.version>
 	</properties>
 
 	<dependencies>
diff --git a/spring-xsuaa/README.md b/spring-xsuaa/README.md
index a50c8507..a4da0a30 100644
--- a/spring-xsuaa/README.md
+++ b/spring-xsuaa/README.md
@@ -48,8 +48,8 @@ Then, xsuaa integration libraries auto-configures beans, that are required to in
 
 Auto-configuration class | Description
 ---- | --------
-XsuaaAutoConfiguration | Adds `xsuaa.*` properties to Spring's Environment. The properties are by default parsed from `VCAP_SERVICES` system environment variables and can be overwritten by properties such as `xsuaa.xsappname` e.g. for testing purposes. Furthermore it exposes a `XsuaaServiceConfiguration` bean that can be used to access xsuaa service information.  Alternatively you can access them with `@Value` annotation e.g. `@Value("${xsuaa.xsappname:}") String appId`.
-XsuaaResourceServerJwkAutoConfiguration | Configures a `JwtDecoder` bean with a JWK (JSON Web Keys) endpoint from where to download the tenant (subdomain) specific public key.
+[XsuaaAutoConfiguration](/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaAutoConfiguration.java) | Adds `xsuaa.*` properties to Spring's Environment. The properties are by default parsed from `VCAP_SERVICES` system environment variables and can be overwritten by properties such as `xsuaa.xsappname` e.g. for testing purposes. Furthermore it exposes a `XsuaaServiceConfiguration` bean that can be used to access xsuaa service information.  Alternatively you can access them with `@Value` annotation e.g. `@Value("${xsuaa.xsappname:}") String appId`.
+[XsuaaResourceServerJwkAutoConfiguration](/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/autoconfiguration/XsuaaResourceServerJwkAutoConfiguration.java) | Configures a `JwtDecoder` bean with a JWK (JSON Web Keys) endpoint from where to download the tenant (subdomain) specific public key.
 
 You can gradually replace auto-configurations as explained [here](https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-auto-configuration.html).
 
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/XsuaaServiceConfigurationDefault.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServiceConfigurationDefault.java
index 679f9f01..277ff20e 100644
--- a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServiceConfigurationDefault.java
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/XsuaaServiceConfigurationDefault.java
@@ -41,7 +41,7 @@ public class XsuaaServiceConfigurationDefault implements XsuaaServiceConfigurati
 	public String getTokenKeyUrl(String zid, String subdomain) {
 		// Please note this method is deprecated and will be deleted with version 2.0
 		if ("uaa".equals(zid)) {
-			return uaaUrl + "/token_keys";
+			return getUaaUrl() + "/token_keys";
 		} else {
 			return String.format("https://%s.%s/token_keys", subdomain, uaadomain);
 		}
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..d33f8043
--- /dev/null
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/ReactiveTokenAuthenticationConverter.java
@@ -0,0 +1,39 @@
+package com.sap.cloud.security.xsuaa.token;
+
+import com.sap.cloud.security.xsuaa.XsuaaServiceConfiguration;
+import com.sap.cloud.security.xsuaa.extractor.DefaultAuthoritiesExtractor;
+import com.sap.cloud.security.xsuaa.extractor.LocalAuthoritiesExtractor;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.util.Assert;
+import reactor.core.publisher.Mono;
+
+/**
+ * Encapsulates the {@link TokenAuthenticationConverter} that extracts
+ * authorization related information from the Jwt token. For example
+ * the{@link LocalAuthoritiesExtractor} can remove the ugly application id
+ * prefix (e.g.my-application-demo!t1229) from the scopes in the JWT.
+ */
+public class ReactiveTokenAuthenticationConverter implements Converter<Jwt, Mono<AbstractAuthenticationToken>> {
+	TokenAuthenticationConverter converter;
+
+	public ReactiveTokenAuthenticationConverter(XsuaaServiceConfiguration xsuaaServiceConfiguration) {
+		this.converter = new TokenAuthenticationConverter(xsuaaServiceConfiguration.getAppId());
+	}
+
+	/**
+	 * This method allows to overwrite the default behavior of the
+	 * {@link Token#getAuthorities()} implementation.
+	 */
+	public ReactiveTokenAuthenticationConverter setLocalScopeAsAuthorities(boolean extractLocalScopesOnly) {
+		this.converter.setLocalScopeAsAuthorities(extractLocalScopesOnly);
+		return this;
+	}
+
+	@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/TokenAuthenticationConverter.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/TokenAuthenticationConverter.java
index 5daf3bbd..c48a0a8d 100644
--- a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/TokenAuthenticationConverter.java
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/TokenAuthenticationConverter.java
@@ -61,16 +61,16 @@ public class TokenAuthenticationConverter implements Converter<Jwt, AbstractAuth
 
 	/**
 	 * This method allows to overwrite the default behavior of the
-	 * {@link Token#getAuthorities()} implementation. Creates a new converter with a
-	 * new {@link LocalAuthoritiesExtractor}
+	 * {@link Token#getAuthorities()} implementation.
 	 *
 	 * @param extractLocalScopesOnly
 	 *            true when {@link Token#getAuthorities()} should only extract local
 	 *            scopes. Local scopes means that non-application specific scopes
 	 *            are filtered out and scopes are returned without appId prefix,
-	 *            e.g. "Display".
+	 *            e.g. "Display". Creates a new converter with a new
+	 *            {@link LocalAuthoritiesExtractor}
 	 */
-	public void setLocalScopeAsAuthorities(boolean extractLocalScopesOnly) {
+	public TokenAuthenticationConverter setLocalScopeAsAuthorities(boolean extractLocalScopesOnly) {
 		if (extractLocalScopesOnly) {
 			Assert.state(appId != null,
 					"For local Scope extraction 'appId' must be provided to `TokenAuthenticationConverter`");
@@ -78,6 +78,7 @@ public class TokenAuthenticationConverter implements Converter<Jwt, AbstractAuth
 		} else {
 			authoritiesExtractor = new DefaultAuthoritiesExtractor();
 		}
+		return this;
 	}
 
 }
\ No newline at end of file
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..5d78feb1
--- /dev/null
+++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/ReactiveXsuaaJwtDecoder.java
@@ -0,0 +1,84 @@
+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..f5aa0d54 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;
 
@@ -36,8 +37,9 @@ public class XsuaaJwtDecoder implements JwtDecoder {
 	private TokenInfoExtractor tokenInfoExtractor;
 
 	XsuaaJwtDecoder(XsuaaServiceConfiguration xsuaaServiceConfiguration, int cacheValidityInSeconds, int cacheSize,
-			OAuth2TokenValidator<Jwt> tokenValidators) {
-		cache = Caffeine.newBuilder().expireAfterWrite(cacheValidityInSeconds, TimeUnit.SECONDS).maximumSize(cacheSize)
+			OAuth2TokenValidator<Jwt> tokenValidators, Collection<PostValidationAction> postValidationActions) {
+
+		this.cache = Caffeine.newBuilder().expireAfterWrite(cacheValidityInSeconds, TimeUnit.SECONDS).maximumSize(cacheSize)
 				.build();
 		this.tokenValidators = tokenValidators;
 
@@ -57,12 +59,8 @@ public class XsuaaJwtDecoder implements JwtDecoder {
 				return xsuaaServiceConfiguration.getUaaDomain();
 			}
 		};
-	}
 
-	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 +83,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 +97,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;
 	}
diff --git a/spring-xsuaa/src/main/java/com/sap/xs2/security/container/SecurityContext.java b/spring-xsuaa/src/main/java/com/sap/xs2/security/container/SecurityContext.java
index 6185a3a0..c0649bd9 100644
--- a/spring-xsuaa/src/main/java/com/sap/xs2/security/container/SecurityContext.java
+++ b/spring-xsuaa/src/main/java/com/sap/xs2/security/container/SecurityContext.java
@@ -11,6 +11,13 @@ import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.oauth2.jwt.Jwt;
 import org.springframework.security.oauth2.jwt.JwtDecoder;
 
+/**
+ * As part of this Spring xsuaa library, we need to get rid of {@code com.sap.xs2.security.container} package.
+ * It will be removed with version {@code 2.0.0}
+ *
+ * @deprecated use {@link SpringSecurityContext} class instead.
+ */
+@Deprecated
 public class SecurityContext {
 	/**
 	 * Obtain the UserInfo object from the Spring Security Context
@@ -20,7 +27,6 @@ public class SecurityContext {
 	 *             in case of error
 	 * @deprecated use {@link #getToken()} instead.
 	 */
-	@Deprecated
 	static public UserInfo getUserInfo() throws UserInfoException {
 		if (SecurityContextHolder.getContext().getAuthentication() != null) {
 			if (SecurityContextHolder.getContext().getAuthentication().getPrincipal() instanceof UserInfo) {
@@ -43,7 +49,6 @@ public class SecurityContext {
 	 *             Note: This method is introduced with xsuaa spring client lib.
 	 * @deprecated method is moved to {@link SpringSecurityContext#getToken()}
 	 */
-	@Deprecated
 	static public Token getToken() {
 		return SpringSecurityContext.getToken();
 	}
@@ -64,7 +69,6 @@ public class SecurityContext {
 	 *             {@link SpringSecurityContext#init(String, JwtDecoder, AuthoritiesExtractor)}
 	 *             instead
 	 */
-	@Deprecated
 	static public void init(String appId, Jwt token, boolean extractLocalScopesOnly) {
 		TokenAuthenticationConverter authenticationConverter = new TokenAuthenticationConverter(
 				new LocalAuthoritiesExtractor(appId));
@@ -80,7 +84,6 @@ public class SecurityContext {
 	 *
 	 * @deprecated method is moved to {@link SpringSecurityContext#clear()}
 	 */
-	@Deprecated
 	static public void clear() {
 		SpringSecurityContext.clear();
 	}
diff --git a/token-client/pom.xml b/token-client/pom.xml
new file mode 100644
index 00000000..11f31a37
--- /dev/null
+++ b/token-client/pom.xml
@@ -0,0 +1,156 @@
+<?xml version="1.0"?>
+<project
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+	xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+	<modelVersion>4.0.0</modelVersion>
+
+	<parent>
+		<groupId>com.sap.cloud.security.xsuaa</groupId>
+		<artifactId>parent</artifactId>
+		<version>1.6.0</version>
+	</parent>
+
+	<artifactId>token-client</artifactId>
+	<name>token-client</name>
+	<packaging>jar</packaging>
+
+	<dependencies>
+		<dependency>
+			<groupId>org.apache.logging.log4j</groupId>
+			<artifactId>log4j-to-slf4j</artifactId>
+			<version>2.11.2</version>
+		</dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-web</artifactId>
+            <version>${spring.security.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<version>${junit.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.hamcrest</groupId>
+			<artifactId>hamcrest-all</artifactId>
+			<version>1.3</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>commons-io</groupId>
+			<artifactId>commons-io</artifactId>
+			<version>2.6</version>
+			<scope>compile</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.mockito</groupId>
+			<artifactId>mockito-core</artifactId>
+			<version>2.23.0</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.assertj</groupId>
+			<artifactId>assertj-core</artifactId>
+			<version>3.13.2</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+		    <groupId>ch.qos.logback</groupId>
+		    <artifactId>logback-classic</artifactId>
+		    <version>1.2.3</version>
+            <scope>test</scope>
+		</dependency>		
+	</dependencies>
+
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-javadoc-plugin</artifactId>
+				<executions>
+					<execution>
+						<id>attach-javadocs</id>
+						<goals>
+							<goal>jar</goal>
+						</goals>
+					</execution>
+				</executions>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-source-plugin</artifactId>
+				<executions>
+					<execution>
+						<id>attach-sources</id>
+						<goals>
+							<goal>jar</goal>
+						</goals>
+					</execution>
+				</executions>
+			</plugin>
+			<plugin>
+				<groupId>org.jacoco</groupId>
+				<artifactId>jacoco-maven-plugin</artifactId>
+				<version>0.8.2</version>
+				<executions>
+					<execution>
+						<id>default-prepare-agent</id>
+						<goals>
+							<goal>prepare-agent</goal>
+						</goals>
+					</execution>
+					<execution>
+						<id>default-report</id>
+						<phase>prepare-package</phase>
+						<goals>
+							<goal>report</goal>
+						</goals>
+					</execution>
+					<execution>
+						<id>default-check</id>
+						<goals>
+							<goal>check</goal>
+						</goals>
+						<configuration>
+							<rules>
+							</rules>
+						</configuration>
+					</execution>
+				</executions>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-pmd-plugin</artifactId>
+				<version>3.10.0</version>
+				<executions>
+					<execution>
+						<phase>verify</phase>
+						<goals>
+							<goal>pmd</goal>
+							<goal>cpd</goal>
+						</goals>
+					</execution>
+				</executions>
+			</plugin>
+			<plugin>
+				<groupId>org.codehaus.mojo</groupId>
+				<artifactId>findbugs-maven-plugin</artifactId>
+				<version>3.0.5</version>
+				<configuration>
+					<skip>${skipTests}</skip>
+				</configuration>
+				<executions>
+					<execution>
+						<phase>verify</phase>
+						<goals>
+							<goal>findbugs</goal>
+						</goals>
+					</execution>
+				</executions>
+			</plugin>
+		</plugins>
+	</build>
+</project>
diff --git a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/ClientCredentials.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/ClientCredentials.java
new file mode 100644
index 00000000..4a6b19e3
--- /dev/null
+++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/ClientCredentials.java
@@ -0,0 +1,43 @@
+package com.sap.cloud.security.xsuaa.client;
+
+import org.springframework.lang.NonNull;
+import org.springframework.util.Assert;
+
+import java.util.Objects;
+
+public class ClientCredentials {
+
+	private final String clientSecret;
+	private final String clientId;
+
+	public ClientCredentials(@NonNull String clientId, @NonNull String clientSecret) {
+		Assert.notNull(clientId, "clientId is required");
+		Assert.notNull(clientSecret, "clientSecret is required");
+		this.clientId = clientId;
+		this.clientSecret = clientSecret;
+	}
+
+	public String getSecret() {
+		return clientSecret;
+	}
+
+	public String getId() {
+		return clientId;
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if (this == o)
+			return true;
+		if (o == null || getClass() != o.getClass())
+			return false;
+		ClientCredentials that = (ClientCredentials) o;
+		return Objects.equals(clientSecret, that.clientSecret) &&
+				Objects.equals(clientId, that.clientId);
+	}
+
+	@Override
+	public int hashCode() {
+		return Objects.hash(clientSecret, clientId);
+	}
+}
diff --git a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2AccessToken.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2AccessToken.java
new file mode 100644
index 00000000..e23791b7
--- /dev/null
+++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2AccessToken.java
@@ -0,0 +1,34 @@
+package com.sap.cloud.security.xsuaa.client;
+
+import java.util.Date;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+
+public class OAuth2AccessToken {
+	private String refreshToken;
+	private String accessToken;
+	private long expiredTimeMillis;
+
+	public OAuth2AccessToken(String accessToken, long expiredInSeconds, @Nullable String refreshToken) {
+		Assert.hasText(accessToken, "accessToken is required");
+
+		this.accessToken = accessToken;
+		this.expiredTimeMillis = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(expiredInSeconds);
+		this.refreshToken = refreshToken;
+	}
+
+	public String getValue() {
+		return accessToken;
+	}
+
+	public Date getExpiredAtDate() {
+		return new Date(expiredTimeMillis);
+	}
+
+	public Optional<String> getRefreshToken() {
+		return Optional.ofNullable(refreshToken);
+	}
+}
diff --git a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2ServiceEndpointsProvider.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2ServiceEndpointsProvider.java
new file mode 100644
index 00000000..b3b07f38
--- /dev/null
+++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2ServiceEndpointsProvider.java
@@ -0,0 +1,29 @@
+package com.sap.cloud.security.xsuaa.client;
+
+import java.net.URI;
+
+public interface OAuth2ServiceEndpointsProvider {
+
+	/**
+	 * Returns token endpoint URI.
+	 * 
+	 * @return token endpoint, e.g. {@code https://oauth.server.com/oauth/token}
+	 */
+	URI getTokenEndpoint();
+
+	/**
+	 * Returns authorize endpoint URI.
+	 * 
+	 * @return authorize endpoint, e.g.
+	 *         {@code https://oauth.server.com/oauth/authorize}
+	 */
+	URI getAuthorizeEndpoint();
+
+	/**
+	 * Returns Jwt Key Set URI (JWKS) as specified in
+	 * /.well-known/openid-configuration.
+	 * 
+	 * @return jwks_uri , e.g. {@code https://oauth.server.com/token_keys}
+	 */
+	URI getJwksUri();
+}
diff --git a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2ServiceException.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2ServiceException.java
new file mode 100644
index 00000000..fa51f225
--- /dev/null
+++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2ServiceException.java
@@ -0,0 +1,13 @@
+package com.sap.cloud.security.xsuaa.client;
+
+/**
+ * Exception thrown to signal issues during communication with OAuth2 service.
+ */
+public class OAuth2ServiceException extends RuntimeException {
+
+	private static final long serialVersionUID = 1L;
+
+	public OAuth2ServiceException(String message) {
+		super(message);
+	}
+}
\ No newline at end of file
diff --git a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2TokenService.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2TokenService.java
new file mode 100644
index 00000000..278fc8de
--- /dev/null
+++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2TokenService.java
@@ -0,0 +1,86 @@
+package com.sap.cloud.security.xsuaa.client;
+
+import java.net.URI;
+import java.util.Map;
+
+import org.springframework.lang.Nullable;
+
+/**
+ * Retrieves OAuth2 Access Tokens as documented here:
+ * https://docs.cloudfoundry.org/api/uaa/version/4.31.0/index.html#token
+ */
+public interface OAuth2TokenService {
+
+	/**
+	 * Requests access token from OAuth Server with client credentials.
+	 *
+	 * @param tokenEndpointUri
+	 *            the token endpoint URI.
+	 * @param clientCredentials
+	 *            the client id and secret of the OAuth client, the recipient of the
+	 *            token.
+	 * @param subdomain
+	 *            optionally indicates what Identity Zone this request goes to by
+	 *            supplying a subdomain (tenant).
+	 * @param optionalParameters
+	 *            optional request parameters, can be null.
+	 *
+	 * @return the OAuth2AccessToken.
+	 * @throws OAuth2ServiceException
+	 *             in case of an error during the http request.
+	 */
+	OAuth2AccessToken retrieveAccessTokenViaClientCredentialsGrant(URI tokenEndpointUri,
+			ClientCredentials clientCredentials, @Nullable String subdomain,
+			@Nullable Map<String, String> optionalParameters) throws OAuth2ServiceException;
+
+	/**
+	 * Exchanges user access token from OAuth Server with user access token. This
+	 * endpoint returns only opaque access token, so that another call using {link
+	 * #retrieveAccessTokenViaRefreshToken} is required.
+	 *
+	 * @param tokenEndpointUri
+	 *            the token endpoint URI.
+	 * @param clientCredentials
+	 *            the client id and secret of the OAuth client, the recipient of the
+	 *            token.
+	 * @param token
+	 *            the user bearer token, that represents an authenticated user that
+	 *            must have uaa.user scope.
+	 * @param subdomain
+	 *            optionally indicates what Identity Zone this request goes to by
+	 *            supplying a subdomain (tenant).
+	 * @param optionalParameters
+	 *            optional request parameters, can be null.
+	 *
+	 * @return the OAuth2AccessToken.
+	 * @throws OAuth2ServiceException
+	 *             in case of an error during the http request.
+	 * @deprecated instead use jwt bearer.
+	 */
+	@Deprecated
+	OAuth2AccessToken retrieveAccessTokenViaUserTokenGrant(URI tokenEndpointUri,
+			ClientCredentials clientCredentials, String token, @Nullable String subdomain,
+			@Nullable Map<String, String> optionalParameters)
+			throws OAuth2ServiceException;
+
+	/**
+	 * Requests access token from OAuth Server with refresh-token
+	 *
+	 * @param tokenEndpointUri
+	 *            the token endpoint URI.
+	 * @param clientCredentials
+	 *            the client id and secret of the OAuth client, the recipient of the
+	 *            token.
+	 * @param refreshToken
+	 *            the refresh token that was returned along with the access token
+	 *            {link #OAuth2AccessToken}.
+	 * @param subdomain
+	 *            * optionally indicates what Identity Zone this request goes to by
+	 *            supplying a subdomain (tenant).
+	 * @return the OAuth2AccessToken
+	 * @throws OAuth2ServiceException
+	 *             in case of an error during the http request.
+	 */
+	OAuth2AccessToken retrieveAccessTokenViaRefreshToken(URI tokenEndpointUri, ClientCredentials clientCredentials,
+			String refreshToken, @Nullable String subdomain) throws OAuth2ServiceException;
+}
diff --git a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2TokenServiceConstants.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2TokenServiceConstants.java
new file mode 100644
index 00000000..3fcd5bd4
--- /dev/null
+++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/OAuth2TokenServiceConstants.java
@@ -0,0 +1,22 @@
+package com.sap.cloud.security.xsuaa.client;
+
+public class OAuth2TokenServiceConstants {
+
+	private OAuth2TokenServiceConstants() {
+		throw new IllegalStateException("Utility class");
+	}
+
+	public static final String ACCESS_TOKEN = "access_token";
+	public static final String EXPIRES_IN = "expires_in";
+	public static final String REFRESH_TOKEN = "refresh_token";
+	public static final String CLIENT_ID = "client_id";
+	public static final String CLIENT_SECRET = "client_secret";
+
+	public static final String GRANT_TYPE = "grant_type";
+	public static final String GRANT_TYPE_USER_TOKEN = "user_token";
+	public static final String GRANT_TYPE_REFRESH_TOKEN = "refresh_token";
+	public static final String GRANT_TYPE_CLIENT_CREDENTIALS = "client_credentials";
+	public static final String TOKEN_TYPE_OPAQUE = "opaque";
+
+	public static final String PARAMETER_CLIENT_ID = "client_id";
+}
diff --git a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/XsuaaDefaultEndpoints.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/XsuaaDefaultEndpoints.java
new file mode 100644
index 00000000..01ed9802
--- /dev/null
+++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/XsuaaDefaultEndpoints.java
@@ -0,0 +1,58 @@
+package com.sap.cloud.security.xsuaa.client;
+
+import java.net.URI;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+import org.springframework.web.util.UriBuilder;
+import org.springframework.web.util.UriComponentsBuilder;
+
+public class XsuaaDefaultEndpoints implements OAuth2ServiceEndpointsProvider {
+	private final URI baseUri;
+	private final static String TOKEN_ENDPOINT = "/oauth/token";
+	private final static String AUTHORIZE_ENDPOINT = "/oauth/authorize";
+	private final static String KEYSET_ENDPOINT = "/token_keys";
+
+	/**
+	 * Creates a new XsuaaRestClient.
+	 *
+	 * @param baseUri
+	 *            - the base URI of XSUAA. Based on the base URI the tokenEndpoint,
+	 *            authorize and key set URI (JWKS) will be derived.
+	 */
+	public XsuaaDefaultEndpoints(URI baseUri) {
+		Assert.notNull(baseUri, "XSUAA base URI must not be null.");
+		this.baseUri = baseUri;
+	}
+
+	/**
+	 * Creates a new XsuaaRestClient.
+	 *
+	 * @param baseUri
+	 *            - the base URI of XSUAA. Based on the base URI the tokenEndpoint,
+	 *            authorize and key set URI (JWKS) will be derived.
+	 */
+	public XsuaaDefaultEndpoints(String baseUri) {
+		this(URI.create(baseUri));
+	}
+
+	@Override
+	public URI getTokenEndpoint() {
+		return UriComponentsBuilder.fromUri(baseUri).path(TOKEN_ENDPOINT).build().toUri();
+	}
+
+	@Override
+	public URI getAuthorizeEndpoint() {
+		return UriComponentsBuilder.fromUri(baseUri).path(AUTHORIZE_ENDPOINT).build().toUri();
+	}
+
+	@Override
+	public URI getJwksUri() {
+		return UriComponentsBuilder.fromUri(baseUri).path(KEYSET_ENDPOINT).build().toUri();
+	}
+
+}
diff --git a/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenService.java b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenService.java
new file mode 100644
index 00000000..9201cd2d
--- /dev/null
+++ b/token-client/src/main/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenService.java
@@ -0,0 +1,214 @@
+package com.sap.cloud.security.xsuaa.client;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.*;
+import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.RestOperations;
+import org.springframework.web.util.UriBuilder;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.*;
+import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.GRANT_TYPE;
+import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.GRANT_TYPE_CLIENT_CREDENTIALS;
+
+public class XsuaaOAuth2TokenService implements OAuth2TokenService {
+
+	private RestOperations restOperations;
+	private static Logger logger = LoggerFactory.getLogger(XsuaaOAuth2TokenService.class);
+
+	public XsuaaOAuth2TokenService(@NonNull RestOperations restOperations) {
+		Assert.notNull(restOperations, "restOperations is required");
+		this.restOperations = restOperations;
+	}
+
+	@Override
+	public OAuth2AccessToken retrieveAccessTokenViaClientCredentialsGrant(@NonNull URI tokenEndpointUri,
+			@NonNull ClientCredentials clientCredentials,
+			@Nullable String subdomain, @Nullable Map<String, String> optionalParameters)
+			throws OAuth2ServiceException {
+		Assert.notNull(tokenEndpointUri, "tokenEndpointUri is required");
+		Assert.notNull(clientCredentials, "clientCredentials is required");
+
+		Map<String, String> parameters = copy(optionalParameters);
+		parameters.put(GRANT_TYPE, GRANT_TYPE_CLIENT_CREDENTIALS);
+		parameters.put(CLIENT_ID, clientCredentials.getId());
+		parameters.put(CLIENT_SECRET, clientCredentials.getSecret());
+
+		// build header
+		HttpHeaders headers = createHeadersWithoutAuthorization();
+
+		return requestAccessToken(replaceSubdomain(tokenEndpointUri, subdomain), headers, parameters);
+	}
+
+	@Override
+	public OAuth2AccessToken retrieveAccessTokenViaUserTokenGrant(@NonNull URI tokenEndpointUri,
+			@NonNull ClientCredentials clientCredentials, @NonNull String token, @Nullable String subdomain,
+			@Nullable Map<String, String> optionalParameters)
+			throws OAuth2ServiceException {
+		Assert.notNull(tokenEndpointUri, "tokenEndpointUri is required");
+		Assert.notNull(clientCredentials, "clientCredentials is required");
+		Assert.notNull(token, "token is required");
+
+		Map<String, String> parameters = copy(optionalParameters);
+		parameters.put(GRANT_TYPE, GRANT_TYPE_USER_TOKEN);
+		parameters.put(PARAMETER_CLIENT_ID, clientCredentials.getId());
+
+		// build header
+		HttpHeaders headers = createHeadersWithAuthorization(token);
+
+		return requestAccessToken(replaceSubdomain(tokenEndpointUri, subdomain), headers, parameters);
+	}
+
+	@Override
+	public OAuth2AccessToken retrieveAccessTokenViaRefreshToken(@NonNull URI tokenEndpointUri,
+			@NonNull ClientCredentials clientCredentials,
+			@NonNull String refreshToken, String subdomain) throws OAuth2ServiceException {
+		Assert.notNull(tokenEndpointUri, "tokenEndpointUri is required");
+		Assert.notNull(clientCredentials, "clientCredentials is required");
+		Assert.notNull(refreshToken, "refreshToken is required");
+
+		Map<String, String> parameters = new HashMap<>();
+		parameters.put(GRANT_TYPE, GRANT_TYPE_REFRESH_TOKEN);
+		parameters.put(REFRESH_TOKEN, refreshToken);
+		parameters.put(CLIENT_ID, clientCredentials.getId());
+		parameters.put(CLIENT_SECRET, clientCredentials.getSecret());
+
+		// build header
+		HttpHeaders headers = createHeadersWithoutAuthorization();
+
+		return requestAccessToken(replaceSubdomain(tokenEndpointUri, subdomain), headers, parameters);
+	}
+
+	/**
+	 * Utility method that replaces the subdomain of the URI with the given
+	 * subdomain.
+	 *
+	 * @param uri
+	 *            the URI to be replaced.
+	 * @param subdomain
+	 *            of the tenant.
+	 * @return the URI with the replaced subdomain or the passed URI in case a
+	 *         replacement was not possible.
+	 */
+	static URI replaceSubdomain(@NonNull URI uri, @Nullable String subdomain) {
+		Assert.notNull(uri, "the uri parameter must not be null");
+		if (StringUtils.hasText(subdomain) && uri.getHost().contains(".")) {
+			UriBuilder builder = UriComponentsBuilder.newInstance().scheme(uri.getScheme())
+					.host(subdomain + uri.getHost().substring(uri.getHost().indexOf("."))).port(uri.getPort())
+					.path(uri.getPath());
+			return uri.resolve(builder.build());
+		}
+		logger.warn("the subdomain of the URI '{}' is not replaced by subdomain '{}'", uri, subdomain);
+		return uri;
+	}
+
+	private OAuth2AccessToken requestAccessToken(URI tokenEndpointUri, HttpHeaders headers,
+			Map<String, String> parameters) {
+
+		// Create URI
+		UriComponentsBuilder builder = UriComponentsBuilder.fromUri(tokenEndpointUri);
+		parameters.forEach(builder::queryParam);
+		URI requestUri = builder.build().encode().toUri();
+
+		// Create entity
+		HttpEntity<Void> requestEntity = new HttpEntity<>(headers);
+
+		@SuppressWarnings("rawtypes")
+		ResponseEntity<Map> responseEntity = null;
+		try {
+			responseEntity = restOperations.postForEntity(requestUri, requestEntity, Map.class);
+		} catch (HttpClientErrorException ex) {
+			HttpStatus responseStatusCode = ex.getStatusCode();
+			if (responseStatusCode == HttpStatus.UNAUTHORIZED) {
+				throw new OAuth2ServiceException(String.format(
+						"Error retrieving JWT token. Received status code %s. Call to XSUAA was not successful. Client credentials invalid.",
+						responseStatusCode));
+			}
+			if (!responseStatusCode.is2xxSuccessful()) {
+				throw new OAuth2ServiceException(String.format(
+						"Error retrieving JWT token. Received status code %s. Call to XSUAA was not successful.",
+						responseStatusCode));
+			}
+			throw new OAuth2ServiceException(String.format(
+					"Error retrieving JWT token. Call to XSUAA was not successful: %s",
+					ex.getMessage()));
+		}
+
+		@SuppressWarnings("unchecked")
+		Map<String, String> accessTokenMap = responseEntity.getBody();
+
+		String accessToken = accessTokenMap.get(ACCESS_TOKEN);
+		long expiresIn = Long.parseLong(String.valueOf(accessTokenMap.get(EXPIRES_IN)));
+		String refreshToken = accessTokenMap.get(REFRESH_TOKEN);
+		return new OAuth2AccessToken(accessToken, expiresIn, refreshToken);
+	}
+
+	/**
+	 * Create a copy of the given map or an new empty map
+	 * 
+	 * @return a new Map that contains all entries of the optional map
+	 */
+	private static Map<String, String> copy(Map<String, String> map) {
+		return map == null ? new HashMap<>() : new HashMap<>(map);
+	}
+
+	/**
+	 * Creates the set of HTTP headers with client-credentials basic authentication
+	 * header.
+	 *
+	 * @return the HTTP headers.
+	 */
+	private static HttpHeaders createHeadersWithoutAuthorization() {
+		HttpHeaders headers = new HttpHeaders();
+		addAcceptHeader(headers);
+		return headers;
+	}
+
+	/**
+	 * Creates the set of HTTP headers with Authorization Bearer header.
+	 *
+	 * @return the HTTP headers.
+	 */
+	private static HttpHeaders createHeadersWithAuthorization(String token) {
+		HttpHeaders headers = new HttpHeaders();
+		addAcceptHeader(headers);
+		addAuthorizationBearerHeader(headers, token);
+		return headers;
+	}
+
+	/** common utilities **/
+
+	/**
+	 * Adds the {@code  Accept: application/json} header to the set of headers.
+	 *
+	 * @param headers
+	 *            - the set of headers to add the header to.
+	 */
+	static void addAcceptHeader(HttpHeaders headers) {
+		headers.add(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
+	}
+
+	/**
+	 * Adds the {@code  Authorization: Bearer <token>} header to the set of headers.
+	 *
+	 * @param headers
+	 *            - the set of headers to add the header to.
+	 * @param token
+	 *            - the token which should be part of the header.
+	 */
+	static void addAuthorizationBearerHeader(HttpHeaders headers, String token) {
+		final String AUTHORIZATION_BEARER_TOKEN_FORMAT = "Bearer %s";
+		headers.add(HttpHeaders.AUTHORIZATION, String.format(AUTHORIZATION_BEARER_TOKEN_FORMAT, token));
+	}
+}


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..6a1b85af 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;
@@ -96,7 +95,7 @@ public class TestController {
      *
      * @see {@link DataService}.
      */
-    @GetMapping(value = "/v1/readData")
+    @GetMapping(value = "/v1/getAdminData")
     public String readFromDataService() {
         return dataService.readSensitiveData();
     }
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..d5942bc5
--- /dev/null
+++ b/samples/spring-webflux-security-xsuaa-usage/src/main/java/sample/spring/webflux/xsuaa/TestController.java
@@ -0,0 +1,23 @@
+package sample.spring.webflux.xsuaa;
+
+import com.sap.cloud.security.xsuaa.token.ReactiveSecurityContext;
+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))
+				.map(token -> ResponseEntity.ok().contentType(MediaType.TEXT_PLAIN)
+						.body("Authorities: " + token.getAuthorities()));
+	}
+}
diff --git a/samples/spring-webflux-security-xsuaa-usage/src/test/java/sample/spring/webflux/xsuaa/ApplicationTest.java b/samples/spring-webflux-security-xsuaa-usage/src/test/java/sample/spring/webflux/xsuaa/ApplicationTest.java
new file mode 100644
index 00000000..c24bf981
--- /dev/null
+++ b/samples/spring-webflux-security-xsuaa-usage/src/test/java/sample/spring/webflux/xsuaa/ApplicationTest.java
@@ -0,0 +1,17 @@
+package sample.spring.webflux.xsuaa;
+
+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/samples/spring-webflux-security-xsuaa-usage/src/test/java/sample/spring/webflux/xsuaa/TestControllerTest.java b/samples/spring-webflux-security-xsuaa-usage/src/test/java/sample/spring/webflux/xsuaa/TestControllerTest.java
new file mode 100644
index 00000000..9b97895c
--- /dev/null
+++ b/samples/spring-webflux-security-xsuaa-usage/src/test/java/sample/spring/webflux/xsuaa/TestControllerTest.java
@@ -0,0 +1,53 @@
+package sample.spring.webflux.xsuaa;
+
+import com.sap.cloud.security.xsuaa.XsuaaServiceConfiguration;
+import com.sap.cloud.security.xsuaa.test.JwtGenerator;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.reactive.server.WebTestClient;
+import org.springframework.util.Assert;
+
+import static org.hamcrest.CoreMatchers.startsWith;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+@AutoConfigureWebTestClient(timeout = "2500000")
+public class TestControllerTest {
+
+	@Autowired
+	private WebTestClient webClient;
+
+	@Autowired
+	private XsuaaServiceConfiguration xsuaaServiceConfiguration;
+
+	@Test
+	public void unauthorizedRequest() {
+		JwtGenerator jwtGenerator = new JwtGenerator("WrongClientId");
+
+		webClient.method(HttpMethod.GET).uri("/v1/sayHello").contentType(MediaType.APPLICATION_JSON_UTF8)
+				.header(HttpHeaders.AUTHORIZATION, jwtGenerator.getTokenForAuthorizationHeader()).exchange()
+				.expectStatus().isUnauthorized();
+	}
+
+	@Test
+	public void authorizedRequest() {
+		JwtGenerator jwtGenerator = new JwtGenerator().addScopes(getGlobalScope("Read"));
+
+		webClient.method(HttpMethod.GET).uri("/v1/sayHello").contentType(MediaType.APPLICATION_JSON_UTF8)
+				.header(HttpHeaders.AUTHORIZATION, jwtGenerator.getTokenForAuthorizationHeader()).exchange()
+				.expectStatus().is2xxSuccessful().expectBody(String.class).value(startsWith("Authorities:"));
+	}
+
+	private String getGlobalScope(String localScope) {
+		Assert.hasText(xsuaaServiceConfiguration.getAppId(), "make sure that xsuaa.xsappname is configured properly.");
+		return xsuaaServiceConfiguration.getAppId() + "." + localScope;
+	}
+
+}
diff --git a/samples/spring-webflux-security-xsuaa-usage/src/test/java/sample/spring/webflux/xsuaa/mock/XsuaaMockPostProcessor.java b/samples/spring-webflux-security-xsuaa-usage/src/test/java/sample/spring/webflux/xsuaa/mock/XsuaaMockPostProcessor.java
new file mode 100644
index 00000000..637821a1
--- /dev/null
+++ b/samples/spring-webflux-security-xsuaa-usage/src/test/java/sample/spring/webflux/xsuaa/mock/XsuaaMockPostProcessor.java
@@ -0,0 +1,25 @@
+package sample.spring.webflux.xsuaa.mock;
+
+import com.sap.cloud.security.xsuaa.mock.XsuaaMockWebServer;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.env.EnvironmentPostProcessor;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.Profiles;
+
+public class XsuaaMockPostProcessor implements EnvironmentPostProcessor, DisposableBean {
+
+	private final XsuaaMockWebServer mockAuthorizationServer = new XsuaaMockWebServer();
+
+	@Override
+	public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
+		if (environment.acceptsProfiles(Profiles.of("uaamock"))) {
+			environment.getPropertySources().addFirst(this.mockAuthorizationServer);
+		}
+	}
+
+	@Override
+	public void destroy() throws Exception {
+		this.mockAuthorizationServer.destroy();
+	}
+}
\ No newline at end of file
diff --git a/samples/spring-webflux-security-xsuaa-usage/src/test/resources/META-INF/spring.factories b/samples/spring-webflux-security-xsuaa-usage/src/test/resources/META-INF/spring.factories
new file mode 100644
index 00000000..c4de7025
--- /dev/null
+++ b/samples/spring-webflux-security-xsuaa-usage/src/test/resources/META-INF/spring.factories
@@ -0,0 +1,1 @@
+org.springframework.boot.env.EnvironmentPostProcessor=sample.spring.webflux.xsuaa.mock.XsuaaMockPostProcessor
\ No newline at end of file
diff --git a/samples/spring-webflux-security-xsuaa-usage/src/test/resources/application.properties b/samples/spring-webflux-security-xsuaa-usage/src/test/resources/application.properties
new file mode 100644
index 00000000..e0c48500
--- /dev/null
+++ b/samples/spring-webflux-security-xsuaa-usage/src/test/resources/application.properties
@@ -0,0 +1,2 @@
+spring.profiles.active=uaamock
+xsuaa.xsappname = xsapplication!t895
\ No newline at end of file
diff --git a/spring-xsuaa-it/src/main/java/testservice/api/basic/SecurityConfiguration.java b/spring-xsuaa-it/src/main/java/testservice/api/basic/SecurityConfiguration.java
index 4705d1ea..c21906f0 100644
--- a/spring-xsuaa-it/src/main/java/testservice/api/basic/SecurityConfiguration.java
+++ b/spring-xsuaa-it/src/main/java/testservice/api/basic/SecurityConfiguration.java
@@ -26,12 +26,14 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 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.extractor.AuthenticationMethod;
 import com.sap.cloud.security.xsuaa.extractor.TokenBrokerResolver;
 import com.sap.cloud.security.xsuaa.mock.MockXsuaaServiceConfiguration;
 import com.sap.cloud.security.xsuaa.token.TokenAuthenticationConverter;
+import com.sap.cloud.security.xsuaa.token.authentication.XsuaaJwtDecoderBuilder;
 
 @EnableWebSecurity
 @EnableCaching
@@ -62,6 +64,11 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
 		return converter;
 	}
 
+	@Bean
+	public JwtDecoder xsuaaJwtDecoder(XsuaaServiceConfiguration xsuaaServiceConfiguration) {
+		return new XsuaaJwtDecoderBuilder(xsuaaServiceConfiguration).build();
+	}
+
 	@Bean
 	XsuaaServiceConfiguration getXsuaaServiceConfiguration() {
 		return new MySecurityConfiguration();
diff --git a/spring-xsuaa-it/src/main/java/testservice/api/basic/TestController.java b/spring-xsuaa-it/src/main/java/testservice/api/basic/TestController.java
index 4a135b58..67b37fc2 100644
--- a/spring-xsuaa-it/src/main/java/testservice/api/basic/TestController.java
+++ b/spring-xsuaa-it/src/main/java/testservice/api/basic/TestController.java
@@ -25,7 +25,7 @@ import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 
-import org.springframework.beans.factory.annotation.Value;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Profile;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.annotation.AuthenticationPrincipal;
@@ -34,6 +34,7 @@ import org.springframework.security.oauth2.jwt.Jwt;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import com.sap.cloud.security.xsuaa.XsuaaServiceConfiguration;
 import com.sap.cloud.security.xsuaa.token.Token;
 import com.sap.xs2.security.container.XSTokenRequestImpl;
 import com.sap.xsa.security.container.XSTokenRequest;
@@ -42,8 +43,8 @@ import com.sap.xsa.security.container.XSTokenRequest;
 @Profile({ "test.api.basic" })
 public class TestController {
 
-	@Value("${mockxsuaaserver.url}")
-	String mockServerUrl;
+	@Autowired
+	XsuaaServiceConfiguration serviceConfiguration;
 
 	@GetMapping("/")
 	public String index(@AuthenticationPrincipal Jwt jwt) {
@@ -66,7 +67,7 @@ public class TestController {
 
 	@GetMapping("/requesttoken")
 	public String requestToken(@AuthenticationPrincipal Token token) throws URISyntaxException {
-		XSTokenRequestImpl tokenRequest = new XSTokenRequestImpl(mockServerUrl);
+		XSTokenRequestImpl tokenRequest = new XSTokenRequestImpl(serviceConfiguration.getUaaUrl());
 		tokenRequest.setClientId("c1").setClientSecret("s1").setType(XSTokenRequest.TYPE_CLIENT_CREDENTIALS_TOKEN);
 		Map<String, String> azMape = new HashMap<>();
 		azMape.put("a", "b");
diff --git a/spring-xsuaa-it/src/main/java/testservice/api/nohttp/MyEventHandler.java b/spring-xsuaa-it/src/main/java/testservice/api/nohttp/MyEventHandler.java
index 35b15fa9..7b8eb336 100644
--- a/spring-xsuaa-it/src/main/java/testservice/api/nohttp/MyEventHandler.java
+++ b/spring-xsuaa-it/src/main/java/testservice/api/nohttp/MyEventHandler.java
@@ -29,6 +29,7 @@ public class MyEventHandler {
 	@Autowired
 	JwtDecoder jwtDecoder;
 
+	@Deprecated
 	public void onEvent_deprecated(String myEncodedJwtToken) {
 		if (myEncodedJwtToken != null) {
 			Jwt jwtToken = jwtDecoder.decode(myEncodedJwtToken);
diff --git a/spring-xsuaa-it/src/main/java/testservice/api/v1/SecurityConfiguration.java b/spring-xsuaa-it/src/main/java/testservice/api/v1/SecurityConfiguration.java
index c50716c7..97ce4c31 100644
--- a/spring-xsuaa-it/src/main/java/testservice/api/v1/SecurityConfiguration.java
+++ b/spring-xsuaa-it/src/main/java/testservice/api/v1/SecurityConfiguration.java
@@ -23,10 +23,12 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 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.mock.MockXsuaaServiceConfiguration;
 import com.sap.cloud.security.xsuaa.token.TokenAuthenticationConverter;
+import com.sap.cloud.security.xsuaa.token.authentication.XsuaaJwtDecoderBuilder;
 
 @EnableWebSecurity
 @Profile({ "test.api.v1" })
@@ -55,4 +57,8 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
 		return new MockXsuaaServiceConfiguration();
 	}
 
+	@Bean
+	public JwtDecoder xsuaaJwtDecoder(XsuaaServiceConfiguration xsuaaServiceConfiguration) {
+		return new XsuaaJwtDecoderBuilder(xsuaaServiceConfiguration).build();
+	}
 }
diff --git a/spring-xsuaa-it/src/main/java/testservice/api/v1/TestController.java b/spring-xsuaa-it/src/main/java/testservice/api/v1/TestController.java
index d7994101..958afbc8 100644
--- a/spring-xsuaa-it/src/main/java/testservice/api/v1/TestController.java
+++ b/spring-xsuaa-it/src/main/java/testservice/api/v1/TestController.java
@@ -9,7 +9,7 @@ import java.util.HashMap;
 import java.util.Map;
 
 import org.junit.Assert;
-import org.springframework.beans.factory.annotation.Value;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Profile;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.annotation.AuthenticationPrincipal;
@@ -18,6 +18,7 @@ import org.springframework.security.oauth2.jwt.Jwt;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import com.sap.cloud.security.xsuaa.XsuaaServiceConfiguration;
 import com.sap.cloud.security.xsuaa.token.Token;
 import com.sap.xs2.security.container.XSTokenRequestImpl;
 import com.sap.xsa.security.container.XSTokenRequest;
@@ -26,8 +27,8 @@ import com.sap.xsa.security.container.XSTokenRequest;
 @Profile({ "test.api.v1" })
 public class TestController {
 
-	@Value("${mockxsuaaserver.url}")
-	String mockServerUrl;
+	@Autowired
+	XsuaaServiceConfiguration serviceConfiguration;
 
 	@GetMapping("/")
 	public String index(@AuthenticationPrincipal Jwt jwt) {
@@ -74,7 +75,7 @@ public class TestController {
 
 	@GetMapping("/requesttoken")
 	public String requestToken(@AuthenticationPrincipal Token token) throws URISyntaxException {
-		XSTokenRequestImpl tokenRequest = new XSTokenRequestImpl(mockServerUrl);
+		XSTokenRequestImpl tokenRequest = new XSTokenRequestImpl(serviceConfiguration.getUaaUrl());
 		tokenRequest.setClientId("c1").setClientSecret("s1").setType(XSTokenRequest.TYPE_CLIENT_CREDENTIALS_TOKEN);
 		Map<String, String> azMape = new HashMap<>();
 		azMape.put("a", "b");
diff --git a/spring-xsuaa-it/src/test/java/com/sap/cloud/security/xsuaa/extractor/BasicAuthenticationValidationIT.java b/spring-xsuaa-it/src/test/java/com/sap/cloud/security/xsuaa/extractor/BasicAuthenticationValidationTest.java
similarity index 96%
rename from spring-xsuaa-it/src/test/java/com/sap/cloud/security/xsuaa/extractor/BasicAuthenticationValidationIT.java
rename to spring-xsuaa-it/src/test/java/com/sap/cloud/security/xsuaa/extractor/BasicAuthenticationValidationTest.java
index 131383b2..b97fcb11 100644
--- a/spring-xsuaa-it/src/test/java/com/sap/cloud/security/xsuaa/extractor/BasicAuthenticationValidationIT.java
+++ b/spring-xsuaa-it/src/test/java/com/sap/cloud/security/xsuaa/extractor/BasicAuthenticationValidationTest.java
@@ -27,12 +27,11 @@ import testservice.api.basic.TestController;
 @RunWith(SpringRunner.class)
 @SpringBootTest(properties = {
 		"xsuaa.xsappname=java-hello-world",
-		"xsuaa.clientid=sb-java-hello-world",
-		"xsuaa.url=${mockxsuaaserver.url}" }, classes = { XsuaaITApplication.class, SecurityConfiguration.class,
+		"xsuaa.clientid=sb-java-hello-world" }, classes = { XsuaaITApplication.class, SecurityConfiguration.class,
 				TestController.class })
 @AutoConfigureMockMvc
 @ActiveProfiles("test.api.basic")
-public class BasicAuthenticationValidationIT {
+public class BasicAuthenticationValidationTest {
 
 	@Autowired
 	MockMvc mvc;
diff --git a/spring-xsuaa-it/src/test/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaTokenValidationIT.java b/spring-xsuaa-it/src/test/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaTokenValidationTest.java
similarity index 91%
rename from spring-xsuaa-it/src/test/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaTokenValidationIT.java
rename to spring-xsuaa-it/src/test/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaTokenValidationTest.java
index 3135c26f..e5e3ca33 100644
--- a/spring-xsuaa-it/src/test/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaTokenValidationIT.java
+++ b/spring-xsuaa-it/src/test/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaTokenValidationTest.java
@@ -8,7 +8,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.mock.web.MockHttpServletRequest;
@@ -25,19 +24,15 @@ import testservice.api.v1.TestController;
 @RunWith(SpringRunner.class)
 @SpringBootTest(properties = {
 		"xsuaa.xsappname=java-hello-world",
-		"xsuaa.clientid=sb-java-hello-world",
-		"xsuaa.url=${mockxsuaaserver.url}" }, classes = { XsuaaITApplication.class,
+		"xsuaa.clientid=sb-java-hello-world" }, classes = { XsuaaITApplication.class,
 				testservice.api.v1.SecurityConfiguration.class, TestController.class })
 @AutoConfigureMockMvc
 @ActiveProfiles("test.api.v1")
-public class XsuaaTokenValidationIT {
+public class XsuaaTokenValidationTest {
 
 	@Autowired
 	MockMvc mvc;
 
-	@Value("${mockxsuaaserver.url}")
-	String mockServerUrl;
-
 	@Test
 	public void testToken_testdomain() throws Exception {
 		this.mvc.perform(get("/user").with(bearerToken(JWTUtil.createJWT("/saml.txt", "testdomain"))))
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..c0d8ec17 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,7 +312,21 @@ 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);
 	}
 
 	/**
@@ -307,7 +334,7 @@ public class JwtGenerator {
 	 *
 	 * @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/main/java/com/sap/cloud/security/xsuaa/DummyXsuaaServiceConfiguration.java b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/DummyXsuaaServiceConfiguration.java
similarity index 100%
rename from spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/DummyXsuaaServiceConfiguration.java
rename to spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/DummyXsuaaServiceConfiguration.java
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/cloud/security/xsuaa/token/SpringSecurityContextTest.java
similarity index 91%
rename from spring-xsuaa/src/test/java/com/sap/xs2/security/container/SpringSecurityContextTest.java
rename to spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/token/SpringSecurityContextTest.java
index a6557f6f..af6019fd 100644
--- a/spring-xsuaa/src/test/java/com/sap/xs2/security/container/SpringSecurityContextTest.java
+++ b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/token/SpringSecurityContextTest.java
@@ -1,9 +1,7 @@
-package com.sap.xs2.security.container;
+package com.sap.cloud.security.xsuaa.token;
 
 import com.sap.cloud.security.xsuaa.extractor.DefaultAuthoritiesExtractor;
 import com.sap.cloud.security.xsuaa.test.JwtGenerator;
-import com.sap.cloud.security.xsuaa.token.SpringSecurityContext;
-import com.sap.cloud.security.xsuaa.token.Token;
 import com.sap.cloud.security.xsuaa.token.authentication.XsuaaJwtDecoder;
 import org.junit.Before;
 import org.junit.Test;
@@ -33,8 +31,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();
@@ -43,7 +39,7 @@ public class SpringSecurityContextTest {
 
 	@Test(expected = AccessDeniedException.class) // Access forbidden: not authenticated
 	public void getSecurityContextRaiseAccessDeniedExceptionIfNotInitialized() {
-		SecurityContext.getToken();
+		SpringSecurityContext.getToken();
 	}
 
 	@Test(expected = IllegalArgumentException.class) // Passed JwtDecoder instance must be of type 'XsuaaJwtDecoder'
@@ -70,13 +66,15 @@ 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();
+			return SpringSecurityContext.getToken();
 		});
 		Future<Token> future_2 = executor.submit(() -> {
 			initSecurityContextWithToken(token_2);
-			return SecurityContext.getToken();
+			return SpringSecurityContext.getToken();
 		});
 
 		while (!future_1.isDone() || !future_2.isDone()) {
diff --git a/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/ClientCredentialsTest.java b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/ClientCredentialsTest.java
new file mode 100644
index 00000000..8bdd7963
--- /dev/null
+++ b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/ClientCredentialsTest.java
@@ -0,0 +1,24 @@
+package com.sap.cloud.security.xsuaa.client;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import org.junit.Test;
+
+public class ClientCredentialsTest {
+	ClientCredentials cut = new ClientCredentials("clientId", "clientSecret");
+
+	@Test
+	public void clientCredentials_equals() {
+		assertThat(cut.equals(cut), is(true));
+		assertThat(cut.equals(new ClientCredentials("clientId", "clientSecret")), is(true));
+	}
+
+	@Test
+	public void clientCredentials_notequals() {
+		assertThat(cut.equals(new ClientCredentials("clientId2", "clientSecret")), is(false));
+		assertThat(cut.equals(new ClientCredentials("clientId", "clientSecret2")), is(false));
+		assertThat(cut.equals(null), is(false));
+		assertThat(cut.equals(new Object()), is(false));
+	}
+}
diff --git a/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/OAuth2AccessTokenTest.java b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/OAuth2AccessTokenTest.java
new file mode 100644
index 00000000..eed0d373
--- /dev/null
+++ b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/OAuth2AccessTokenTest.java
@@ -0,0 +1,25 @@
+package com.sap.cloud.security.xsuaa.client;
+
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.junit.Assert.assertThat;
+
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+
+import org.hamcrest.number.OrderingComparison;
+import org.junit.Test;
+
+public class OAuth2AccessTokenTest {
+
+	@Test
+	public void getExpiredDateFromAccessToken() {
+		long expireInSeconds = 43199;
+		Date minExpireDate = new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(expireInSeconds));
+		OAuth2AccessToken accessToken = new OAuth2AccessToken("e9511922b5e64c49ba0eedcc8d772e76", expireInSeconds,
+				null);
+		Date maxExpireDate = new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(expireInSeconds));
+
+		assertThat(accessToken.getExpiredAtDate(), allOf(OrderingComparison.greaterThanOrEqualTo(minExpireDate),
+				OrderingComparison.lessThanOrEqualTo(maxExpireDate)));
+	}
+}
diff --git a/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaDefaultEndpointsTest.java b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaDefaultEndpointsTest.java
new file mode 100644
index 00000000..efa15960
--- /dev/null
+++ b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaDefaultEndpointsTest.java
@@ -0,0 +1,28 @@
+package com.sap.cloud.security.xsuaa.client;
+
+import org.junit.Test;
+
+import java.net.URI;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class XsuaaDefaultEndpointsTest {
+	XsuaaDefaultEndpoints cut = new XsuaaDefaultEndpoints("https://subdomain.myauth.com");
+
+	@Test
+	public void getTokenEndpoint() {
+		assertThat(cut.getTokenEndpoint().toString(), is("https://subdomain.myauth.com/oauth/token"));
+	}
+
+	@Test
+	public void getAuthorizeEndpoint() {
+		assertThat(cut.getAuthorizeEndpoint().toString(), is("https://subdomain.myauth.com/oauth/authorize"));
+	}
+
+	@Test
+	public void getJwksUri() {
+		assertThat(cut.getJwksUri().toString(), is("https://subdomain.myauth.com/token_keys"));
+	}
+
+}
diff --git a/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServiceClientCredentialsTest.java b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServiceClientCredentialsTest.java
new file mode 100644
index 00000000..ffdfa95c
--- /dev/null
+++ b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServiceClientCredentialsTest.java
@@ -0,0 +1,142 @@
+package com.sap.cloud.security.xsuaa.client;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.hamcrest.CoreMatchers.is;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.RestOperations;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.ACCESS_TOKEN;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.any;
+
+@RunWith(MockitoJUnitRunner.class)
+public class XsuaaOAuth2TokenServiceClientCredentialsTest {
+
+	OAuth2TokenService cut;
+	ClientCredentials clientCredentials;
+	URI tokenEndpoint;
+	Map<String, String> responseMap;
+
+	@Mock
+	RestOperations mockRestOperations;
+
+	@Before
+	public void setup() {
+		cut = new XsuaaOAuth2TokenService(mockRestOperations);
+		clientCredentials = new ClientCredentials("clientid", "mysecretpassword");
+		tokenEndpoint = URI.create("https://subdomain.myauth.server.com/oauth/token");
+
+		responseMap = new HashMap<>();
+		responseMap.putIfAbsent(ACCESS_TOKEN, "f529.dd6e30.d454677322aaabb0");
+		responseMap.putIfAbsent(OAuth2TokenServiceConstants.EXPIRES_IN, "43199");
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void initialize_throwsIfRestOperationsIsNull() {
+		new XsuaaOAuth2TokenService(null);
+	}
+
+	@Test
+	public void retrieveToken_throwsOnNullValues() {
+		assertThatThrownBy(() -> {
+			cut.retrieveAccessTokenViaClientCredentialsGrant(null, clientCredentials, null, null);
+		}).isInstanceOf(IllegalArgumentException.class).hasMessageStartingWith("tokenEndpointUri");
+
+		assertThatThrownBy(() -> {
+			cut.retrieveAccessTokenViaClientCredentialsGrant(tokenEndpoint, null, null, null);
+		}).isInstanceOf(IllegalArgumentException.class).hasMessageStartingWith("clientCredentials");
+	}
+
+	@Test(expected = OAuth2ServiceException.class)
+	public void retrieveToken_throwsIfHttpStatusUnauthorized() {
+		Mockito.when(mockRestOperations.postForEntity(any(URI.class), any(HttpEntity.class), eq(Map.class)))
+				.thenThrow(new HttpClientErrorException(HttpStatus.UNAUTHORIZED));
+		cut.retrieveAccessTokenViaClientCredentialsGrant(tokenEndpoint, clientCredentials,
+				null, null);
+	}
+
+	@Test(expected = OAuth2ServiceException.class)
+	public void retrieveToken_throwsIfHttpStatusNotOk() {
+		Mockito.when(mockRestOperations.postForEntity(any(URI.class), any(HttpEntity.class), eq(Map.class)))
+				.thenThrow(new HttpClientErrorException(HttpStatus.BAD_REQUEST));
+		cut.retrieveAccessTokenViaClientCredentialsGrant(tokenEndpoint, clientCredentials,
+				null, null);
+	}
+
+	@Test
+	public void retrieveToken() {
+		HttpHeaders expectedHeaders = new HttpHeaders();
+		expectedHeaders.add(HttpHeaders.ACCEPT, "application/json");
+		HttpEntity expectedRequest = new HttpEntity(expectedHeaders);
+
+		Mockito.when(mockRestOperations
+				.postForEntity(
+						eq(createUriWithParameters(
+								"grant_type=client_credentials&client_secret=mysecretpassword&client_id=clientid")),
+						eq(expectedRequest),
+						eq(Map.class)))
+				.thenReturn(new ResponseEntity<>(responseMap, HttpStatus.OK));
+
+		OAuth2AccessToken accessToken = cut.retrieveAccessTokenViaClientCredentialsGrant(tokenEndpoint,
+				clientCredentials,
+				null, null);
+		assertThat(accessToken.getValue(), is(responseMap.get(ACCESS_TOKEN)));
+		assertNotNull(accessToken.getExpiredAtDate());
+	}
+
+	@Test
+	public void retrieveToken_withOptionalParamaters() {
+		Mockito.when(mockRestOperations.postForEntity(
+				eq(createUriWithParameters(
+						"add-param-1=value1&add-param-2=value2&client_secret=mysecretpassword&grant_type=client_credentials&client_id=clientid")),
+				any(HttpEntity.class), eq(Map.class)))
+				.thenReturn(new ResponseEntity<>(responseMap, HttpStatus.OK));
+
+		Map<String, String> additionalParameters = new HashMap<>();
+		additionalParameters.put("add-param-1", "value1");
+		additionalParameters.put("add-param-2", "value2");
+
+		OAuth2AccessToken accessToken = cut.retrieveAccessTokenViaClientCredentialsGrant(tokenEndpoint,
+				clientCredentials, null,
+				additionalParameters);
+		assertThat(accessToken.getValue(), is(responseMap.get(ACCESS_TOKEN)));
+	}
+
+	@Test
+	public void retrieveToken_requiredParametersCanNotBeOverwritten() {
+		Mockito.when(mockRestOperations.postForEntity(
+				eq(createUriWithParameters(
+						"grant_type=client_credentials&client_id=clientid&client_secret=mysecretpassword")),
+				any(HttpEntity.class), eq(Map.class)))
+				.thenReturn(new ResponseEntity<>(responseMap, HttpStatus.OK));
+
+		Map<String, String> overwrittenGrantType = new HashMap<>();
+		overwrittenGrantType.put(OAuth2TokenServiceConstants.GRANT_TYPE, "overwrite-obligatory-param");
+
+		OAuth2AccessToken accessToken = cut.retrieveAccessTokenViaClientCredentialsGrant(tokenEndpoint,
+				clientCredentials, null,
+				overwrittenGrantType);
+		assertThat(accessToken.getValue(), is(responseMap.get(ACCESS_TOKEN)));
+	}
+
+	private URI createUriWithParameters(String queryParameterList) {
+		return URI.create(tokenEndpoint.toString() + "?" + queryParameterList);
+	}
+}
\ No newline at end of file
diff --git a/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServiceRefreshTokenTest.java b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServiceRefreshTokenTest.java
new file mode 100644
index 00000000..b7af6240
--- /dev/null
+++ b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServiceRefreshTokenTest.java
@@ -0,0 +1,116 @@
+package com.sap.cloud.security.xsuaa.client;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.RestOperations;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.ACCESS_TOKEN;
+import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.REFRESH_TOKEN;
+import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.EXPIRES_IN;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+
+@RunWith(MockitoJUnitRunner.class)
+public class XsuaaOAuth2TokenServiceRefreshTokenTest {
+
+	private static String refreshToken = "d2faefe7ea834ba895d20730f106128c-r";
+
+	OAuth2TokenService cut;
+	ClientCredentials clientCredentials;
+	URI tokenEndpoint;
+	Map<String, String> responseMap;
+
+	@Mock
+	RestOperations mockRestOperations;
+
+	@Before
+	public void setup() {
+		cut = new XsuaaOAuth2TokenService(mockRestOperations);
+		clientCredentials = new ClientCredentials("clientid", "mysecretpassword");
+		tokenEndpoint = URI.create("https://subdomain.myauth.server.com/oauth/token");
+
+		responseMap = new HashMap<>();
+		responseMap.put(REFRESH_TOKEN, "2170b564228448c6aed8b1ddfdb8bf53-r");
+		responseMap.put(ACCESS_TOKEN, "f529.dd6e30.d454677322aaabb0");
+		responseMap.put(EXPIRES_IN, "43199");
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void initialize_throwsIfRestOperationsIsNull() {
+		new XsuaaOAuth2TokenService(null);
+	}
+
+	@Test
+	public void retrieveToken_throwsOnNullValues() {
+		assertThatThrownBy(() -> {
+			cut.retrieveAccessTokenViaRefreshToken(null, clientCredentials, refreshToken, null);
+		}).isInstanceOf(IllegalArgumentException.class).hasMessageStartingWith("tokenEndpointUri");
+
+		assertThatThrownBy(() -> {
+			cut.retrieveAccessTokenViaRefreshToken(tokenEndpoint, null, refreshToken, null);
+		}).isInstanceOf(IllegalArgumentException.class).hasMessageStartingWith("clientCredentials");
+
+		assertThatThrownBy(() -> {
+			cut.retrieveAccessTokenViaRefreshToken(tokenEndpoint, clientCredentials, null, null);
+		}).isInstanceOf(IllegalArgumentException.class).hasMessageStartingWith("refreshToken");
+	}
+
+	@Test(expected = OAuth2ServiceException.class)
+	public void retrieveToken_throwsIfHttpStatusUnauthorized() {
+		Mockito.when(mockRestOperations.postForEntity(any(URI.class), any(HttpEntity.class), eq(Map.class)))
+				.thenThrow(new HttpClientErrorException(HttpStatus.UNAUTHORIZED));
+		cut.retrieveAccessTokenViaRefreshToken(tokenEndpoint, clientCredentials,
+				refreshToken, null);
+	}
+
+	@Test(expected = OAuth2ServiceException.class)
+	public void retrieveToken_throwsIfHttpStatusNotOk() {
+		Mockito.when(mockRestOperations.postForEntity(any(URI.class), any(HttpEntity.class), eq(Map.class)))
+				.thenThrow(new HttpClientErrorException(HttpStatus.BAD_REQUEST));
+		cut.retrieveAccessTokenViaRefreshToken(tokenEndpoint, clientCredentials,
+				refreshToken, null);
+	}
+
+	@Test
+	public void retrieveToken() {
+		HttpHeaders expectedHeaders = new HttpHeaders();
+		expectedHeaders.add(HttpHeaders.ACCEPT, "application/json");
+		HttpEntity expectedRequest = new HttpEntity(expectedHeaders);
+
+		Mockito.when(mockRestOperations
+				.postForEntity(
+						eq(createUriWithParameters(
+								"refresh_token=" + refreshToken
+										+ "&grant_type=refresh_token&client_secret=mysecretpassword&client_id=clientid")),
+						eq(expectedRequest),
+						eq(Map.class)))
+				.thenReturn(new ResponseEntity<>(responseMap, HttpStatus.OK));
+
+		OAuth2AccessToken accessToken = cut.retrieveAccessTokenViaRefreshToken(tokenEndpoint, clientCredentials,
+				refreshToken, null);
+		assertThat(accessToken.getRefreshToken().get(), is(responseMap.get(REFRESH_TOKEN)));
+		assertThat(accessToken.getValue(), is(responseMap.get(ACCESS_TOKEN)));
+		assertNotNull(accessToken.getExpiredAtDate());
+	}
+
+	private URI createUriWithParameters(String queryParameterList) {
+		return URI.create(tokenEndpoint.toString() + "?" + queryParameterList);
+	}
+}
\ No newline at end of file
diff --git a/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServiceTest.java b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServiceTest.java
new file mode 100644
index 00000000..8aa3575d
--- /dev/null
+++ b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServiceTest.java
@@ -0,0 +1,38 @@
+package com.sap.cloud.security.xsuaa.client;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import java.net.URI;
+
+import org.junit.Test;
+import org.springframework.web.client.RestTemplate;
+
+public class XsuaaOAuth2TokenServiceTest {
+	private XsuaaOAuth2TokenService cut = new XsuaaOAuth2TokenService(new RestTemplate());
+	private URI tokenEndpointUri = URI.create("https://subdomain.myauth.com/mypath");
+
+	@Test
+	public void replaceSubdomain_replacesNothingWhenSubdomainIsNull() {
+		URI replacedURI = XsuaaOAuth2TokenService.replaceSubdomain(tokenEndpointUri, null);
+		assertThat(replacedURI, is(tokenEndpointUri));
+	}
+
+	@Test
+	public void replaceSubdomain() {
+		URI replacedURI = XsuaaOAuth2TokenService.replaceSubdomain(tokenEndpointUri, "newsubdomain");
+		assertThat(replacedURI.toString(), is("https://newsubdomain.myauth.com/mypath"));
+	}
+
+	@Test
+	public void replaceSubdomain_replacesNothingWhenSubdomainIsEmpty() {
+		URI replacedURI = XsuaaOAuth2TokenService.replaceSubdomain(tokenEndpointUri, "");
+		assertThat(replacedURI, is(tokenEndpointUri));
+	}
+
+	@Test
+	public void replaceSubdomain_replacesNothingWhenUrlContainsNoSubdomain() {
+		URI replacedURI = XsuaaOAuth2TokenService.replaceSubdomain(URI.create("http://localhost"), "newsubdomain");
+		assertThat(replacedURI.toString(), is("http://localhost"));
+	}
+}
diff --git a/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServiceUserTokenTest.java b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServiceUserTokenTest.java
new file mode 100644
index 00000000..7a372d60
--- /dev/null
+++ b/token-client/src/test/java/com/sap/cloud/security/xsuaa/client/XsuaaOAuth2TokenServiceUserTokenTest.java
@@ -0,0 +1,146 @@
+package com.sap.cloud.security.xsuaa.client;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.RestOperations;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.ACCESS_TOKEN;
+import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.REFRESH_TOKEN;
+import static com.sap.cloud.security.xsuaa.client.OAuth2TokenServiceConstants.EXPIRES_IN;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+
+@RunWith(MockitoJUnitRunner.class)
+public class XsuaaOAuth2TokenServiceUserTokenTest {
+
+	OAuth2TokenService cut;
+	ClientCredentials clientCredentials;
+	URI tokenEndpoint;
+	Map<String, String> responseMap;
+	private static final String userTokenToBeExchanged = "65a84cd45c554c6993ea26cb8f9cf3a2";
+
+	@Mock
+	RestOperations mockRestOperations;
+
+	@Before
+	public void setup() {
+		cut = new XsuaaOAuth2TokenService(mockRestOperations);
+		clientCredentials = new ClientCredentials("clientid", "mysecretpassword");
+		tokenEndpoint = URI.create("https://subdomain.myauth.server.com/oauth/token");
+
+		responseMap = new HashMap<>();
+		responseMap.put(REFRESH_TOKEN, "2170b564228448c6aed8b1ddfdb8bf53-r");
+		responseMap.put(ACCESS_TOKEN, "4d841646fcc340f59b1b7b43df4b050d"); // opaque access token
+		responseMap.put(EXPIRES_IN, "43199");
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void initialize_throwsIfRestOperationsIsNull() {
+		new XsuaaOAuth2TokenService(null);
+	}
+
+	@Test
+	public void retrieveToken_throwsOnNullValues() {
+		assertThatThrownBy(() -> {
+			cut.retrieveAccessTokenViaUserTokenGrant(null, clientCredentials, userTokenToBeExchanged, null, null);
+		}).isInstanceOf(IllegalArgumentException.class).hasMessageStartingWith("tokenEndpointUri");
+
+		assertThatThrownBy(() -> {
+			cut.retrieveAccessTokenViaUserTokenGrant(tokenEndpoint, null, userTokenToBeExchanged, null, null);
+		}).isInstanceOf(IllegalArgumentException.class).hasMessageStartingWith("clientCredentials");
+
+		assertThatThrownBy(() -> {
+			cut.retrieveAccessTokenViaUserTokenGrant(tokenEndpoint, clientCredentials, null, null, null);
+		}).isInstanceOf(IllegalArgumentException.class).hasMessageStartingWith("token");
+	}
+
+	@Test(expected = OAuth2ServiceException.class)
+	public void retrieveToken_throwsIfHttpStatusUnauthorized() {
+		Mockito.when(mockRestOperations.postForEntity(any(URI.class), any(HttpEntity.class), eq(Map.class)))
+				.thenThrow(new HttpClientErrorException(HttpStatus.UNAUTHORIZED));
+		cut.retrieveAccessTokenViaUserTokenGrant(tokenEndpoint, clientCredentials,
+				userTokenToBeExchanged, null, null);
+	}
+
+	@Test(expected = OAuth2ServiceException.class)
+	public void retrieveToken_throwsIfHttpStatusNotOk() {
+		Mockito.when(mockRestOperations.postForEntity(any(URI.class), any(HttpEntity.class), eq(Map.class)))
+				.thenThrow(new HttpClientErrorException(HttpStatus.BAD_REQUEST));
+		cut.retrieveAccessTokenViaUserTokenGrant(tokenEndpoint, clientCredentials,
+				userTokenToBeExchanged, null, null);
+	}
+
+	@Test
+	public void retrieveToken() {
+		HttpHeaders expectedHeaders = new HttpHeaders();
+		expectedHeaders.add(HttpHeaders.ACCEPT, "application/json");
+		expectedHeaders.add(HttpHeaders.AUTHORIZATION, "Bearer " + userTokenToBeExchanged);
+		HttpEntity expectedRequest = new HttpEntity(expectedHeaders);
+
+		Mockito.when(mockRestOperations
+				.postForEntity(eq(createUriWithParameters("grant_type=user_token&client_id=clientid")),
+						eq(expectedRequest),
+						eq(Map.class)))
+				.thenReturn(new ResponseEntity<>(responseMap, HttpStatus.OK));
+
+		OAuth2AccessToken accessToken = cut.retrieveAccessTokenViaUserTokenGrant(tokenEndpoint, clientCredentials,
+				userTokenToBeExchanged, null, null);
+		assertThat(accessToken.getRefreshToken().get(), is(responseMap.get(REFRESH_TOKEN)));
+		assertThat(accessToken.getValue(), is(responseMap.get(ACCESS_TOKEN)));
+		assertNotNull(accessToken.getExpiredAtDate());
+	}
+
+	@Test
+	public void retrieveToken_withOptionalParamaters() {
+		Mockito.when(mockRestOperations.postForEntity(
+				eq(createUriWithParameters(
+						"add-param-1=value1&add-param-2=value2&grant_type=user_token&client_id=clientid")),
+				any(HttpEntity.class), eq(Map.class)))
+				.thenReturn(new ResponseEntity<>(responseMap, HttpStatus.OK));
+
+		Map<String, String> additionalParameters = new HashMap<>();
+		additionalParameters.put("add-param-1", "value1");
+		additionalParameters.put("add-param-2", "value2");
+
+		OAuth2AccessToken accessToken = cut.retrieveAccessTokenViaUserTokenGrant(tokenEndpoint, clientCredentials,
+				userTokenToBeExchanged, null, additionalParameters);
+		assertThat(accessToken.getRefreshToken().get(), is(responseMap.get(REFRESH_TOKEN)));
+	}
+
+	@Test
+	public void retrieveToken_requiredParametersCanNotBeOverwritten() {
+		Mockito.when(
+				mockRestOperations.postForEntity(
+						eq(createUriWithParameters("grant_type=user_token&client_id=clientid")),
+						any(HttpEntity.class), eq(Map.class)))
+				.thenReturn(new ResponseEntity<>(responseMap, HttpStatus.OK));
+
+		Map<String, String> overwrittenGrantType = new HashMap<>();
+		overwrittenGrantType.put(OAuth2TokenServiceConstants.GRANT_TYPE, "overwrite-obligatory-param");
+
+		OAuth2AccessToken accessToken = cut.retrieveAccessTokenViaUserTokenGrant(tokenEndpoint, clientCredentials,
+				userTokenToBeExchanged, null, overwrittenGrantType);
+		assertThat(accessToken.getRefreshToken().get(), is(responseMap.get(REFRESH_TOKEN)));
+	}
+
+	private URI createUriWithParameters(String queryParameterList) {
+		return URI.create(tokenEndpoint.toString() + "?" + queryParameterList);
+	}
+}
\ No newline at end of file