diff --git a/spring-5-reactive-security/pom.xml b/spring-5-reactive-security/pom.xml
index 2024cb5138..2f4a31241b 100644
--- a/spring-5-reactive-security/pom.xml
+++ b/spring-5-reactive-security/pom.xml
@@ -128,6 +128,7 @@
1.0
4.1
3.1.6.RELEASE
+ 2.2.1.RELEASE
diff --git a/spring-5-reactive-security/src/main/java/com/baeldung/reactive/authresolver/AuthResolverApplication.java b/spring-5-reactive-security/src/main/java/com/baeldung/reactive/authresolver/AuthResolverApplication.java
new file mode 100644
index 0000000000..bad5768c20
--- /dev/null
+++ b/spring-5-reactive-security/src/main/java/com/baeldung/reactive/authresolver/AuthResolverApplication.java
@@ -0,0 +1,15 @@
+package com.baeldung.reactive.authresolver;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.web.reactive.config.EnableWebFlux;
+
+@EnableWebFlux
+@SpringBootApplication
+public class AuthResolverApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(AuthResolverApplication.class, args);
+ }
+
+}
diff --git a/spring-5-reactive-security/src/main/java/com/baeldung/reactive/authresolver/AuthResolverController.java b/spring-5-reactive-security/src/main/java/com/baeldung/reactive/authresolver/AuthResolverController.java
new file mode 100644
index 0000000000..1d70ded5e4
--- /dev/null
+++ b/spring-5-reactive-security/src/main/java/com/baeldung/reactive/authresolver/AuthResolverController.java
@@ -0,0 +1,25 @@
+package com.baeldung.reactive.authresolver;
+
+import java.security.Principal;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+import reactor.core.publisher.Mono;
+
+@RestController
+public class AuthResolverController {
+
+ @GetMapping("/customer/welcome")
+ public Mono sayWelcomeToCustomer(Mono principal) {
+ return principal
+ .map(Principal::getName)
+ .map(name -> String.format("Welcome to our site, %s!", name));
+ }
+
+ @GetMapping("/employee/welcome")
+ public Mono sayWelcomeToEmployee(Mono principal) {
+ return principal
+ .map(Principal::getName)
+ .map(name -> String.format("Welcome to our company, %s!", name));
+ }
+
+}
diff --git a/spring-5-reactive-security/src/main/java/com/baeldung/reactive/authresolver/CustomWebSecurityConfig.java b/spring-5-reactive-security/src/main/java/com/baeldung/reactive/authresolver/CustomWebSecurityConfig.java
new file mode 100644
index 0000000000..d07a991089
--- /dev/null
+++ b/spring-5-reactive-security/src/main/java/com/baeldung/reactive/authresolver/CustomWebSecurityConfig.java
@@ -0,0 +1,96 @@
+package com.baeldung.reactive.authresolver;
+
+import java.util.Collections;
+import org.springframework.context.annotation.Bean;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.security.authentication.ReactiveAuthenticationManager;
+import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+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.SecurityWebFiltersOrder;
+import org.springframework.security.config.web.server.ServerHttpSecurity;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.web.server.SecurityWebFilterChain;
+import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
+import reactor.core.publisher.Mono;
+
+@EnableWebFluxSecurity
+@EnableReactiveMethodSecurity
+public class CustomWebSecurityConfig {
+
+ @Bean
+ public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
+ return http
+ .authorizeExchange()
+ .pathMatchers("/**")
+ .authenticated()
+ .and()
+ .httpBasic()
+ .disable()
+ .addFilterAfter(authenticationWebFilter(), SecurityWebFiltersOrder.REACTOR_CONTEXT)
+ .build();
+ }
+
+ public AuthenticationWebFilter authenticationWebFilter() {
+ return new AuthenticationWebFilter(resolver());
+ }
+
+ public ReactiveAuthenticationManagerResolver resolver() {
+ return request -> {
+ if (request
+ .getPath()
+ .subPath(0)
+ .value()
+ .startsWith("/employee")) {
+ return Mono.just(employeesAuthenticationManager());
+ }
+ return Mono.just(customersAuthenticationManager());
+ };
+ }
+
+ public ReactiveAuthenticationManager customersAuthenticationManager() {
+ return authentication -> customer(authentication)
+ .switchIfEmpty(Mono.error(new UsernameNotFoundException(authentication
+ .getPrincipal()
+ .toString())))
+ .map(b -> new UsernamePasswordAuthenticationToken(authentication.getPrincipal(),
+ authentication.getCredentials(),
+ Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))
+ )
+ );
+ }
+
+ public ReactiveAuthenticationManager employeesAuthenticationManager() {
+ return authentication -> employee(authentication)
+ .switchIfEmpty(Mono.error(new UsernameNotFoundException(authentication
+ .getPrincipal()
+ .toString())))
+ .map(
+ b -> new UsernamePasswordAuthenticationToken(authentication.getPrincipal(),
+ authentication.getCredentials(),
+ Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))
+ )
+ );
+ }
+
+ public Mono customer(Authentication authentication) {
+ return Mono.justOrEmpty(authentication
+ .getPrincipal()
+ .toString()
+ .startsWith("customer") ? authentication
+ .getPrincipal()
+ .toString() : null);
+ }
+
+ public Mono employee(Authentication authentication) {
+ return Mono.justOrEmpty(authentication
+ .getPrincipal()
+ .toString()
+ .startsWith("employee") ? authentication
+ .getPrincipal()
+ .toString() : null);
+ }
+}
diff --git a/spring-5-reactive-security/src/test/java/com/baeldung/reactive/authresolver/AuthResolverIntegrationTest.java b/spring-5-reactive-security/src/test/java/com/baeldung/reactive/authresolver/AuthResolverIntegrationTest.java
new file mode 100644
index 0000000000..691243b3ea
--- /dev/null
+++ b/spring-5-reactive-security/src/test/java/com/baeldung/reactive/authresolver/AuthResolverIntegrationTest.java
@@ -0,0 +1,63 @@
+package com.baeldung.reactive.authresolver;
+
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.reactive.server.WebTestClient;
+import org.springframework.util.Base64Utils;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = AuthResolverApplication.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class AuthResolverIntegrationTest {
+ @Autowired
+ private WebTestClient testClient;
+
+ @Test
+ public void givenCustomerCredential_whenWelcomeCustomer_thenExpectOk() {
+ testClient
+ .get()
+ .uri("/customer/welcome")
+ .header("Authorization", "Basic " + Base64Utils.encodeToString("customer1:pass1".getBytes()))
+ .exchange()
+ .expectStatus()
+ .isOk();
+ }
+
+ @Test
+ public void givenEmployeeCredential_whenWelcomeCustomer_thenExpect401Status() {
+ testClient
+ .get()
+ .uri("/customer/welcome")
+ .header("Authorization", "Basic " + Base64Utils.encodeToString("employee1:pass1".getBytes()))
+ .exchange()
+ .expectStatus()
+ .isUnauthorized();
+ }
+
+ @Test
+ public void givenEmployeeCredential_whenWelcomeEmployee_thenExpectOk() {
+ testClient
+ .get()
+ .uri("/employee/welcome")
+ .header("Authorization", "Basic " + Base64Utils.encodeToString("employee1:pass1".getBytes()))
+ .exchange()
+ .expectStatus()
+ .isOk();
+ }
+
+ @Test
+ public void givenCustomerCredential_whenWelcomeEmployee_thenExpect401Status() {
+ testClient
+ .get()
+ .uri("/employee/welcome")
+ .header("Authorization", "Basic " + Base64Utils.encodeToString("customer1:pass1".getBytes()))
+ .exchange()
+ .expectStatus()
+ .isUnauthorized();
+ }
+}
diff --git a/spring-5-security/pom.xml b/spring-5-security/pom.xml
index c486d5346b..3fd31c8bc5 100644
--- a/spring-5-security/pom.xml
+++ b/spring-5-security/pom.xml
@@ -60,5 +60,8 @@
+
+ 2.2.1.RELEASE
+
diff --git a/spring-5-security/src/main/java/com/baeldung/authresolver/AuthResolverApplication.java b/spring-5-security/src/main/java/com/baeldung/authresolver/AuthResolverApplication.java
new file mode 100644
index 0000000000..96ee674b15
--- /dev/null
+++ b/spring-5-security/src/main/java/com/baeldung/authresolver/AuthResolverApplication.java
@@ -0,0 +1,11 @@
+package com.baeldung.authresolver;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class AuthResolverApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(AuthResolverApplication.class, args);
+ }
+}
diff --git a/spring-5-security/src/main/java/com/baeldung/authresolver/AuthResolverController.java b/spring-5-security/src/main/java/com/baeldung/authresolver/AuthResolverController.java
new file mode 100644
index 0000000000..7dc6900b5a
--- /dev/null
+++ b/spring-5-security/src/main/java/com/baeldung/authresolver/AuthResolverController.java
@@ -0,0 +1,18 @@
+package com.baeldung.authresolver;
+
+import org.springframework.security.core.Authentication;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class AuthResolverController {
+ @GetMapping("/customer/welcome")
+ public String sayWelcomeToCustomer(Authentication authentication) {
+ return String.format("Welcome to our site, %s!", authentication.getPrincipal());
+ }
+
+ @GetMapping("/employee/welcome")
+ public String sayWelcomeToEmployee(Authentication authentication) {
+ return String.format("Welcome to our company, %s!", authentication.getPrincipal());
+ }
+}
diff --git a/spring-5-security/src/main/java/com/baeldung/authresolver/CustomWebSecurityConfigurer.java b/spring-5-security/src/main/java/com/baeldung/authresolver/CustomWebSecurityConfigurer.java
new file mode 100644
index 0000000000..33ef692173
--- /dev/null
+++ b/spring-5-security/src/main/java/com/baeldung/authresolver/CustomWebSecurityConfigurer.java
@@ -0,0 +1,96 @@
+package com.baeldung.authresolver;
+
+import java.util.Collections;
+import javax.servlet.http.HttpServletRequest;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.AuthenticationManagerResolver;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.web.authentication.AuthenticationConverter;
+import org.springframework.security.web.authentication.AuthenticationFilter;
+import org.springframework.security.web.authentication.www.BasicAuthenticationConverter;
+import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
+
+@Configuration
+public class CustomWebSecurityConfigurer extends WebSecurityConfigurerAdapter {
+
+ public AuthenticationConverter authenticationConverter() {
+ return new BasicAuthenticationConverter();
+ }
+
+ public AuthenticationManagerResolver resolver() {
+ return request -> {
+ if (request
+ .getPathInfo()
+ .startsWith("/employee")) {
+ return employeesAuthenticationManager();
+ }
+ return customersAuthenticationManager();
+ };
+ }
+
+ public AuthenticationManager customersAuthenticationManager() {
+ return authentication -> {
+ if (isCustomer(authentication)) {
+ return new UsernamePasswordAuthenticationToken(
+ authentication.getPrincipal(),
+ authentication.getCredentials(),
+ Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))
+ );
+ }
+ throw new UsernameNotFoundException(authentication
+ .getPrincipal()
+ .toString());
+ };
+ }
+
+ private boolean isCustomer(Authentication authentication) {
+ return (authentication
+ .getPrincipal()
+ .toString()
+ .startsWith("customer"));
+ }
+
+ private boolean isEmployee(Authentication authentication) {
+ return (authentication
+ .getPrincipal()
+ .toString()
+ .startsWith("employee"));
+ }
+
+ private AuthenticationFilter authenticationFilter() {
+ AuthenticationFilter filter = new AuthenticationFilter(
+ resolver(), authenticationConverter());
+ filter.setSuccessHandler((request, response, auth) -> {});
+ return filter;
+ }
+
+ private AuthenticationManager employeesAuthenticationManager() {
+ return authentication -> {
+ if (isEmployee(authentication)) {
+ return new UsernamePasswordAuthenticationToken(
+ authentication.getPrincipal(),
+ authentication.getCredentials(),
+ Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))
+ );
+ }
+ throw new UsernameNotFoundException(authentication
+ .getPrincipal()
+ .toString());
+ };
+ }
+
+ @Override
+ protected void configure(HttpSecurity http) {
+ http.addFilterBefore(
+ authenticationFilter(),
+ BasicAuthenticationFilter.class
+ );
+ }
+
+}
diff --git a/spring-5-security/src/test/java/com/baeldung/authresolver/AuthResolverIntegrationTest.java b/spring-5-security/src/test/java/com/baeldung/authresolver/AuthResolverIntegrationTest.java
new file mode 100644
index 0000000000..c87f8c1a3a
--- /dev/null
+++ b/spring-5-security/src/test/java/com/baeldung/authresolver/AuthResolverIntegrationTest.java
@@ -0,0 +1,82 @@
+package com.baeldung.authresolver;
+
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.security.web.FilterChainProxy;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.util.Base64Utils;
+import org.springframework.web.context.WebApplicationContext;
+
+import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = AuthResolverApplication.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class AuthResolverIntegrationTest {
+
+ @Autowired
+ private FilterChainProxy springSecurityFilterChain;
+
+ @Autowired
+ private WebApplicationContext wac;
+
+ private MockMvc mockMvc;
+
+ @Before
+ public void setup() {
+ this.mockMvc = MockMvcBuilders
+ .webAppContextSetup(wac)
+ .apply(springSecurity(springSecurityFilterChain))
+ .build();
+ }
+
+ @Test
+ public void givenCustomerCredential_whenWelcomeCustomer_thenExpectOk() throws Exception {
+ this.mockMvc
+ .perform(get("/customer/welcome")
+ .header(
+ "Authorization", String.format("Basic %s", Base64Utils.encodeToString("customer1:pass1".getBytes()))
+ )
+ )
+ .andExpect(status().is2xxSuccessful());
+ }
+
+ @Test
+ public void givenEmployeeCredential_whenWelcomeCustomer_thenExpect401Status() throws Exception {
+ this.mockMvc
+ .perform(get("/customer/welcome")
+ .header(
+ "Authorization", "Basic " + Base64Utils.encodeToString("employee1:pass1".getBytes()))
+ )
+ .andExpect(status().isUnauthorized());
+ }
+
+ @Test
+ public void givenEmployeeCredential_whenWelcomeEmployee_thenExpectOk() throws Exception {
+ this.mockMvc
+ .perform(get("/employee/welcome")
+ .header(
+ "Authorization", "Basic " + Base64Utils.encodeToString("employee1:pass1".getBytes()))
+ )
+ .andExpect(status().is2xxSuccessful());
+ }
+
+ @Test
+ public void givenCustomerCredential_whenWelcomeEmployee_thenExpect401Status() throws Exception {
+ this.mockMvc
+ .perform(get("/employee/welcome")
+ .header(
+ "Authorization", "Basic " + Base64Utils.encodeToString("customer1:pass1".getBytes()))
+ )
+ .andExpect(status().isUnauthorized());
+ }
+}