diff --git a/pom.xml b/pom.xml
index 0ec9e9efdd..345cd7979a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1023,6 +1023,7 @@
spring-kafka
spring-native
+ spring-security-modules/spring-security-oauth2-testing
spring-protobuf
spring-quartz
diff --git a/spring-security-modules/spring-security-oauth2-testing/README.md b/spring-security-modules/spring-security-oauth2-testing/README.md
new file mode 100644
index 0000000000..82728ff450
--- /dev/null
+++ b/spring-security-modules/spring-security-oauth2-testing/README.md
@@ -0,0 +1,7 @@
+## Spring-Security 6 OAuth2 testing samples
+
+This module contains articles about testing spring-security 6 access-control with OAuth2
+
+## Relevant articles:
+
+- [Testing Spring OAuth2 Access-Control](https://drafts.baeldung.com/?p=154767&preview=true)
diff --git a/spring-security-modules/spring-security-oauth2-testing/pom.xml b/spring-security-modules/spring-security-oauth2-testing/pom.xml
new file mode 100644
index 0000000000..f634b6105c
--- /dev/null
+++ b/spring-security-modules/spring-security-oauth2-testing/pom.xml
@@ -0,0 +1,32 @@
+
+
+ 4.0.0
+ spring-security-oauth2-testing
+ spring-security-oauth2-testing
+ pom
+ spring-security 6 oauth testing sample project
+
+ com.baeldung
+ parent-boot-3
+ 0.0.1-SNAPSHOT
+ ../../parent-boot-3
+
+
+ 6.0.14
+
+
+
+
+ com.c4-soft.springaddons
+ spring-addons-oauth2-test
+ ${spring-addons.version}
+
+
+
+
+ reactive-resource-server
+ servlet-resource-server
+
+
\ No newline at end of file
diff --git a/spring-security-modules/spring-security-oauth2-testing/reactive-resource-server/pom.xml b/spring-security-modules/spring-security-oauth2-testing/reactive-resource-server/pom.xml
new file mode 100644
index 0000000000..c1fa6b7c6d
--- /dev/null
+++ b/spring-security-modules/spring-security-oauth2-testing/reactive-resource-server/pom.xml
@@ -0,0 +1,58 @@
+
+
+ 4.0.0
+
+ com.baeldung
+ spring-security-oauth2-testing
+ 0.0.1-SNAPSHOT
+
+ com.baeldung.spring-security-modules.testing
+ reactive-resource-server
+ reactive-resource-server
+ Demo project for Spring Boot reactive resource-server
+
+ 17
+
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-resource-server
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ com.c4-soft.springaddons
+ spring-addons-oauth2-test
+ test
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-security-modules/spring-security-oauth2-testing/reactive-resource-server/src/main/java/com/baeldung/ReactiveResourceServerApplication.java b/spring-security-modules/spring-security-oauth2-testing/reactive-resource-server/src/main/java/com/baeldung/ReactiveResourceServerApplication.java
new file mode 100644
index 0000000000..608038331a
--- /dev/null
+++ b/spring-security-modules/spring-security-oauth2-testing/reactive-resource-server/src/main/java/com/baeldung/ReactiveResourceServerApplication.java
@@ -0,0 +1,124 @@
+package com.baeldung;
+
+import java.nio.charset.Charset;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.core.io.buffer.DataBufferUtils;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.authentication.AnonymousAuthenticationToken;
+import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
+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.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.context.ReactiveSecurityContextHolder;
+import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
+import org.springframework.security.web.server.SecurityWebFilterChain;
+import org.springframework.security.web.server.context.NoOpServerSecurityContextRepository;
+import org.springframework.stereotype.Service;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import lombok.RequiredArgsConstructor;
+import reactor.core.publisher.Mono;
+
+@SpringBootApplication
+public class ReactiveResourceServerApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(ReactiveResourceServerApplication.class, args);
+ }
+
+ @Configuration
+ @EnableWebFluxSecurity
+ @EnableReactiveMethodSecurity
+ public class SecurityConfig {
+ @Bean
+ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, Converter>> authoritiesConverter) {
+ http.oauth2ResourceServer().jwt()
+ .jwtAuthenticationConverter(jwt -> authoritiesConverter.convert(jwt).map(authorities -> new JwtAuthenticationToken(jwt, authorities)));
+ http.securityContextRepository(NoOpServerSecurityContextRepository.getInstance()).csrf().disable();
+ http.exceptionHandling().accessDeniedHandler((var exchange, var ex) -> exchange.getPrincipal().flatMap(principal -> {
+ final var response = exchange.getResponse();
+ response.setStatusCode(principal instanceof AnonymousAuthenticationToken ? HttpStatus.UNAUTHORIZED : HttpStatus.FORBIDDEN);
+ response.getHeaders().setContentType(MediaType.TEXT_PLAIN);
+ final var dataBufferFactory = response.bufferFactory();
+ final var buffer = dataBufferFactory.wrap(ex.getMessage().getBytes(Charset.defaultCharset()));
+ return response.writeWith(Mono.just(buffer)).doOnError(error -> DataBufferUtils.release(buffer));
+ }));
+
+ http.authorizeExchange().pathMatchers("/secured-route").hasRole("AUTHORIZED_PERSONNEL").anyExchange().authenticated();
+
+ return http.build();
+ }
+
+ static interface AuthoritiesConverter extends Converter>> {
+ }
+
+ @Bean
+ AuthoritiesConverter realmRoles2AuthoritiesConverter() {
+ return (Jwt jwt) -> {
+ final var realmRoles = Optional.of(jwt.getClaimAsMap("realm_access")).orElse(Map.of());
+ @SuppressWarnings("unchecked")
+ final var roles = (List) realmRoles.getOrDefault("roles", List.of());
+ return Mono.just(roles.stream().map(SimpleGrantedAuthority::new).map(GrantedAuthority.class::cast).toList());
+ };
+ }
+ }
+
+ @Service
+ public static class MessageService {
+
+ public Mono greet() {
+ return ReactiveSecurityContextHolder.getContext().map(ctx -> {
+ final var who = (JwtAuthenticationToken) ctx.getAuthentication();
+ final var claims = who.getTokenAttributes();
+ return "Hello %s! You are granted with %s.".formatted(
+ claims.getOrDefault(StandardClaimNames.PREFERRED_USERNAME,claims.get(StandardClaimNames.SUB)),
+ who.getAuthorities());
+ }).switchIfEmpty(Mono.error(new AuthenticationCredentialsNotFoundException("Security context is empty")));
+ }
+
+ @PreAuthorize("hasRole('AUTHORIZED_PERSONNEL')")
+ public Mono getSecret() {
+ return Mono.just("Only authorized personnel can read that");
+ }
+ }
+
+ @RestController
+ @RequiredArgsConstructor
+ public class GreetingController {
+ private final MessageService messageService;
+
+ @GetMapping("/greet")
+ public Mono> greet() {
+ return messageService.greet().map(ResponseEntity::ok);
+ }
+
+ @GetMapping("/secured-route")
+ public Mono> securedRoute() {
+ return messageService.getSecret().map(ResponseEntity::ok);
+ }
+
+ @GetMapping("/secured-method")
+ @PreAuthorize("hasRole('AUTHORIZED_PERSONNEL')")
+ public Mono> securedMethod() {
+ return messageService.getSecret().map(ResponseEntity::ok);
+ }
+ }
+
+}
diff --git a/spring-security-modules/spring-security-oauth2-testing/reactive-resource-server/src/main/resources/application.yaml b/spring-security-modules/spring-security-oauth2-testing/reactive-resource-server/src/main/resources/application.yaml
new file mode 100644
index 0000000000..01e655e1b3
--- /dev/null
+++ b/spring-security-modules/spring-security-oauth2-testing/reactive-resource-server/src/main/resources/application.yaml
@@ -0,0 +1,6 @@
+spring:
+ security:
+ oauth2:
+ resourceserver:
+ jwt:
+ issuer-uri: https://localhost:8443/realms/master
diff --git a/spring-security-modules/spring-security-oauth2-testing/reactive-resource-server/src/test/java/com/baeldung/MessageServiceUnitTest.java b/spring-security-modules/spring-security-oauth2-testing/reactive-resource-server/src/test/java/com/baeldung/MessageServiceUnitTest.java
new file mode 100644
index 0000000000..92896754ef
--- /dev/null
+++ b/spring-security-modules/spring-security-oauth2-testing/reactive-resource-server/src/test/java/com/baeldung/MessageServiceUnitTest.java
@@ -0,0 +1,79 @@
+package com.baeldung;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Import;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
+import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
+import org.springframework.security.test.context.support.WithAnonymousUser;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import com.baeldung.ReactiveResourceServerApplication.MessageService;
+import com.c4_soft.springaddons.security.oauth2.test.annotations.OpenIdClaims;
+import com.c4_soft.springaddons.security.oauth2.test.annotations.WithMockJwtAuth;
+
+@Import({ MessageService.class })
+@ExtendWith(SpringExtension.class)
+@EnableReactiveMethodSecurity
+class MessageServiceUnitTest {
+ @Autowired
+ MessageService messageService;
+
+ /*----------------------------------------------------------------------------*/
+ /* greet() */
+ /* Expects a JwtAuthenticationToken to be retrieved from the security-context */
+ /*----------------------------------------------------------------------------*/
+
+ @Test
+ void givenSecurityContextIsEmpty_whenGreet_thenThrowsAuthenticationCredentialsNotFoundException() {
+ assertThrows(AuthenticationCredentialsNotFoundException.class, () -> messageService.greet().block());
+ }
+
+ @Test
+ @WithAnonymousUser
+ void givenUserIsNotAuthenticated_whenGreet_thenThrowsClassCastException() {
+ assertThrows(ClassCastException.class, () -> messageService.greet().block());
+ }
+
+ @Test
+ @WithMockJwtAuth(authorities = { "admin", "ROLE_AUTHORIZED_PERSONNEL" }, claims = @OpenIdClaims(preferredUsername = "ch4mpy"))
+ void givenSecurityContextIsPopulatedWithJwtAuthenticationToken_whenGreet_thenReturnGreetingWithPreferredUsernameAndAuthorities() {
+ assertEquals("Hello ch4mpy! You are granted with [admin, ROLE_AUTHORIZED_PERSONNEL].", messageService.greet().block());
+ }
+
+ @Test
+ @WithMockUser(authorities = { "admin", "ROLE_AUTHORIZED_PERSONNEL" }, username = "ch4mpy")
+ void givenSecurityContextIsPopulatedWithUsernamePasswordAuthenticationToken_whenGreet_thenThrowsClassCastException() {
+ assertThrows(ClassCastException.class, () -> messageService.greet().block());
+ }
+
+ /*--------------------------------------------------------------------*/
+ /* getSecret() */
+ /* is secured with "@PreAuthorize("hasRole('AUTHORIZED_PERSONNEL')")" */
+ /*--------------------------------------------------------------------*/
+
+ @Test
+ @WithAnonymousUser
+ void givenUserIsNotAuthenticated_whenGetSecret_thenThrowsAccessDeniedException() {
+ assertThrows(AccessDeniedException.class, () -> messageService.getSecret().block());
+ }
+
+ @Test
+ @WithMockJwtAuth(authorities = { "admin", "ROLE_AUTHORIZED_PERSONNEL" }, claims = @OpenIdClaims(preferredUsername = "ch4mpy"))
+ void givenUserIsGrantedWithRoleAuthorizedPersonnel_whenGetSecret_thenReturnSecret() {
+ assertEquals("Only authorized personnel can read that", messageService.getSecret().block());
+ }
+
+ @Test
+ @WithMockJwtAuth(authorities = { "admin" }, claims = @OpenIdClaims(preferredUsername = "ch4mpy"))
+ void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecret_thenThrowsAccessDeniedException() {
+ assertThrows(AccessDeniedException.class, () -> messageService.getSecret().block());
+ }
+
+}
diff --git a/spring-security-modules/spring-security-oauth2-testing/reactive-resource-server/src/test/java/com/baeldung/ReactiveResourceServerApplicationIntegrationTest.java b/spring-security-modules/spring-security-oauth2-testing/reactive-resource-server/src/test/java/com/baeldung/ReactiveResourceServerApplicationIntegrationTest.java
new file mode 100644
index 0000000000..e3c45883c2
--- /dev/null
+++ b/spring-security-modules/spring-security-oauth2-testing/reactive-resource-server/src/test/java/com/baeldung/ReactiveResourceServerApplicationIntegrationTest.java
@@ -0,0 +1,109 @@
+package com.baeldung;
+
+import org.junit.jupiter.api.Test;
+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.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.security.test.context.support.WithAnonymousUser;
+import org.springframework.test.web.reactive.server.WebTestClient;
+
+import com.c4_soft.springaddons.security.oauth2.test.annotations.OpenIdClaims;
+import com.c4_soft.springaddons.security.oauth2.test.annotations.WithMockJwtAuth;
+
+@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
+@AutoConfigureWebTestClient
+class ReactiveResourceServerApplicationIntegrationTest {
+ @Autowired
+ WebTestClient api;
+
+ /*-----------------------------------------------------------------------------*/
+ /* /greet */
+ /* This end-point secured with ".anyRequest().authenticated()" in SecurityConf */
+ /*-----------------------------------------------------------------------------*/
+
+ @Test
+ @WithAnonymousUser
+ void givenUserIsNotAuthenticated_whenGetGreet_thenUnauthorized() throws Exception {
+ // @formatter:off
+ api.get().uri("/greet").exchange()
+ .expectStatus().isUnauthorized();
+ // @formatter:on
+ }
+
+ @Test
+ @WithMockJwtAuth(authorities = { "admin", "ROLE_AUTHORIZED_PERSONNEL" }, claims = @OpenIdClaims(preferredUsername = "ch4mpy"))
+ void givenUserIsAuthenticated_whenGetGreet_thenOk() throws Exception {
+ // @formatter:off
+ api.get().uri("/greet").exchange()
+ .expectStatus().isOk()
+ .expectBody(String.class).isEqualTo("Hello ch4mpy! You are granted with [admin, ROLE_AUTHORIZED_PERSONNEL].");
+ // @formatter:on
+ }
+
+ /*---------------------------------------------------------------------------------------------------------------------*/
+ /* /secured-route */
+ /* This end-point is secured with ".requestMatchers("/secured-route").hasRole("AUTHORIZED_PERSONNEL")" in SecurityConf */
+ /*---------------------------------------------------------------------------------------------------------------------*/
+
+ @Test
+ @WithAnonymousUser
+ void givenUserIsNotAuthenticated_whenGetSecuredRoute_thenUnauthorized() throws Exception {
+ // @formatter:off
+ api.get().uri("/secured-route").exchange()
+ .expectStatus().isUnauthorized();
+ // @formatter:on
+ }
+
+ @Test
+ @WithMockJwtAuth("ROLE_AUTHORIZED_PERSONNEL")
+ void givenUserIsGrantedWithRoleAuthorizedPersonnel_whenGetSecuredRoute_thenOk() throws Exception {
+ // @formatter:off
+ api.get().uri("/secured-route").exchange()
+ .expectStatus().isOk()
+ .expectBody(String.class).isEqualTo("Only authorized personnel can read that");
+ // @formatter:on
+ }
+
+ @Test
+ @WithMockJwtAuth("admin")
+ void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception {
+ // @formatter:off
+ api.get().uri("/secured-route").exchange()
+ .expectStatus().isForbidden();
+ // @formatter:on
+ }
+
+ /*---------------------------------------------------------------------------------------------------------*/
+ /* /secured-method */
+ /* This end-point is secured with "@PreAuthorize("hasRole('AUTHORIZED_PERSONNEL')")" on @Controller method */
+ /*---------------------------------------------------------------------------------------------------------*/
+
+ @Test
+ @WithAnonymousUser
+ void givenUserIsNotAuthenticated_whenGetSecuredMethod_thenUnauthorized() throws Exception {
+ // @formatter:off
+ api.get().uri("/secured-method").exchange()
+ .expectStatus().isUnauthorized();
+ // @formatter:on
+ }
+
+ @Test
+ @WithMockJwtAuth("ROLE_AUTHORIZED_PERSONNEL")
+ void givenUserIsGrantedWithRoleAuthorizedPersonnel_whenGetSecuredMethod_thenOk() throws Exception {
+ // @formatter:off
+ api.get().uri("/secured-method").exchange()
+ .expectStatus().isOk()
+ .expectBody(String.class).isEqualTo("Only authorized personnel can read that");
+ // @formatter:on
+ }
+
+ @Test
+ @WithMockJwtAuth("admin")
+ void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() throws Exception {
+ // @formatter:off
+ api.get().uri("/secured-method").exchange()
+ .expectStatus().isForbidden();
+ // @formatter:on
+ }
+}
diff --git a/spring-security-modules/spring-security-oauth2-testing/reactive-resource-server/src/test/java/com/baeldung/SpringAddonsGreetingControllerUnitTest.java b/spring-security-modules/spring-security-oauth2-testing/reactive-resource-server/src/test/java/com/baeldung/SpringAddonsGreetingControllerUnitTest.java
new file mode 100644
index 0000000000..e28136dd25
--- /dev/null
+++ b/spring-security-modules/spring-security-oauth2-testing/reactive-resource-server/src/test/java/com/baeldung/SpringAddonsGreetingControllerUnitTest.java
@@ -0,0 +1,123 @@
+package com.baeldung;
+
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.security.test.context.support.WithAnonymousUser;
+import org.springframework.test.web.reactive.server.WebTestClient;
+
+import com.baeldung.ReactiveResourceServerApplication.GreetingController;
+import com.baeldung.ReactiveResourceServerApplication.MessageService;
+import com.c4_soft.springaddons.security.oauth2.test.annotations.OpenIdClaims;
+import com.c4_soft.springaddons.security.oauth2.test.annotations.WithMockJwtAuth;
+
+import reactor.core.publisher.Mono;
+
+@WebFluxTest(controllers = GreetingController.class, properties = { "server.ssl.enabled=false" })
+class SpringAddonsGreetingControllerUnitTest {
+
+ @MockBean
+ MessageService messageService;
+
+ @Autowired
+ WebTestClient api;
+
+ /*-----------------------------------------------------------------------------*/
+ /* /greet */
+ /* This end-point secured with ".anyRequest().authenticated()" in SecurityConf */
+ /*-----------------------------------------------------------------------------*/
+
+ @Test
+ @WithAnonymousUser
+ void givenUserIsNotAuthenticated_whenGetGreet_thenUnauthorized() throws Exception {
+ api.get().uri("/greet").exchange().expectStatus().isUnauthorized();
+ }
+
+ @Test
+ @WithMockJwtAuth(claims = @OpenIdClaims(preferredUsername = "Tonton Pirate"))
+ void givenUserIsAuthenticated_whenGetGreet_thenOk() throws Exception {
+ final var greeting = "Whatever the service returns";
+ when(messageService.greet()).thenReturn(Mono.just(greeting));
+
+ // @formatter:off
+ api.get().uri("/greet").exchange()
+ .expectStatus().isOk()
+ .expectBody(String.class).isEqualTo(greeting);
+ // @formatter:on
+
+ verify(messageService, times(1)).greet();
+ }
+
+ /*---------------------------------------------------------------------------------------------------------------------*/
+ /* /secured-route */
+ /* This end-point is secured with ".requestMatchers("/secured-route").hasRole("AUTHORIZED_PERSONNEL")" in SecurityConf */
+ /*---------------------------------------------------------------------------------------------------------------------*/
+
+ @Test
+ @WithAnonymousUser
+ void givenUserIsNotAuthenticated_whenGetSecuredRoute_thenUnauthorized() throws Exception {
+ api.get().uri("/secured-route").exchange().expectStatus().isUnauthorized();
+ }
+
+ @Test
+ @WithMockJwtAuth("ROLE_AUTHORIZED_PERSONNEL")
+ void givenUserIsGrantedWithRoleAuthorizedPersonnel_whenGetSecuredRoute_thenOk() throws Exception {
+ final var secret = "Secret!";
+ when(messageService.getSecret()).thenReturn(Mono.just(secret));
+
+ // @formatter:off
+ api.get().uri("/secured-route").exchange()
+ .expectStatus().isOk()
+ .expectBody(String.class).isEqualTo(secret);
+ // @formatter:on
+ }
+
+ @Test
+ @WithMockJwtAuth("admin")
+ void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception {
+ // @formatter:off
+ api.get().uri("/secured-route").exchange()
+ .expectStatus().isForbidden();
+ // @formatter:on
+ }
+
+ /*---------------------------------------------------------------------------------------------------------*/
+ /* /secured-method */
+ /* This end-point is secured with "@PreAuthorize("hasRole('AUTHORIZED_PERSONNEL')")" on @Controller method */
+ /*---------------------------------------------------------------------------------------------------------*/
+
+ @Test
+ @WithAnonymousUser
+ void givenUserIsNotAuthenticated_whenGetSecuredMethod_thenUnauthorized() throws Exception {
+ api.get().uri("/secured-method").exchange().expectStatus().isUnauthorized();
+ }
+
+ @Test
+ @WithMockJwtAuth("ROLE_AUTHORIZED_PERSONNEL")
+ void givenUserIsGrantedWithRoleAuthorizedPersonnel_whenGetSecuredMethod_thenOk() throws Exception {
+ final var secret = "Secret!";
+ when(messageService.getSecret()).thenReturn(Mono.just(secret));
+
+ // @formatter:off
+ api.get().uri("/secured-method").exchange()
+ .expectStatus().isOk()
+ .expectBody(String.class).isEqualTo(secret);
+ // @formatter:on
+ }
+
+ @Test
+ @WithMockJwtAuth("admin")
+ void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() throws Exception {
+ // @formatter:off
+ api.get().uri("/secured-method").exchange()
+ .expectStatus().isForbidden();
+ // @formatter:on
+ }
+
+}
+
diff --git a/spring-security-modules/spring-security-oauth2-testing/reactive-resource-server/src/test/java/com/baeldung/SpringSecurityTestGreetingControllerUnitTest.java b/spring-security-modules/spring-security-oauth2-testing/reactive-resource-server/src/test/java/com/baeldung/SpringSecurityTestGreetingControllerUnitTest.java
new file mode 100644
index 0000000000..291a405e29
--- /dev/null
+++ b/spring-security-modules/spring-security-oauth2-testing/reactive-resource-server/src/test/java/com/baeldung/SpringSecurityTestGreetingControllerUnitTest.java
@@ -0,0 +1,138 @@
+package com.baeldung;
+
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockAuthentication;
+import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockJwt;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.security.authentication.AnonymousAuthenticationToken;
+import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.test.web.reactive.server.WebTestClient;
+
+import com.baeldung.ReactiveResourceServerApplication.GreetingController;
+import com.baeldung.ReactiveResourceServerApplication.MessageService;
+
+import reactor.core.publisher.Mono;
+
+@WebFluxTest(controllers = GreetingController.class, properties = { "server.ssl.enabled=false" })
+class SpringSecurityTestGreetingControllerUnitTest {
+ private static final AnonymousAuthenticationToken ANONYMOUS_AUTHENTICATION = new AnonymousAuthenticationToken(
+ "anonymous",
+ "anonymousUser",
+ AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
+
+ @MockBean
+ MessageService messageService;
+
+ @Autowired
+ WebTestClient api;
+
+ /*-----------------------------------------------------------------------------*/
+ /* /greet */
+ /* This end-point secured with ".anyRequest().authenticated()" in SecurityConf */
+ /*-----------------------------------------------------------------------------*/
+
+ @Test
+ void givenUserIsNotAuthenticated_whenGetGreet_thenUnauthorized() throws Exception {
+ // @formatter:off
+ api.mutateWith(mockAuthentication(ANONYMOUS_AUTHENTICATION))
+ .get().uri("/greet").exchange()
+ .expectStatus().isUnauthorized();
+ // @formatter:on
+ }
+
+ @Test
+ void givenUserIsAuthenticated_whenGetGreet_thenOk() throws Exception {
+ final var greeting = "Whatever the service returns";
+ when(messageService.greet()).thenReturn(Mono.just(greeting));
+
+ // @formatter:off
+ api.mutateWith(mockJwt())
+ .get().uri("/greet").exchange()
+ .expectStatus().isOk()
+ .expectBody(String.class).isEqualTo(greeting);
+ // @formatter:on
+
+ verify(messageService, times(1)).greet();
+ }
+
+ /*---------------------------------------------------------------------------------------------------------------------*/
+ /* /secured-route */
+ /* This end-point is secured with ".requestMatchers("/secured-route").hasRole("AUTHORIZED_PERSONNEL")" in SecurityConf */
+ /*---------------------------------------------------------------------------------------------------------------------*/
+
+ @Test
+ void givenUserIsNotAuthenticated_whenGetSecuredRoute_thenUnauthorized() throws Exception {
+ // @formatter:off
+ api.mutateWith(mockAuthentication(ANONYMOUS_AUTHENTICATION))
+ .get().uri("/secured-route").exchange()
+ .expectStatus().isUnauthorized();
+ // @formatter:on
+ }
+
+ @Test
+ void givenUserIsGrantedWithRoleAuthorizedPersonnel_whenGetSecuredRoute_thenOk() throws Exception {
+ final var secret = "Secret!";
+ when(messageService.getSecret()).thenReturn(Mono.just(secret));
+
+ // @formatter:off
+ api.mutateWith(mockJwt().authorities(new SimpleGrantedAuthority("ROLE_AUTHORIZED_PERSONNEL")))
+ .get().uri("/secured-route").exchange()
+ .expectStatus().isOk()
+ .expectBody(String.class).isEqualTo(secret);
+ // @formatter:on
+ }
+
+ @Test
+ void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception {
+ // @formatter:off
+ api.mutateWith(mockJwt().authorities(new SimpleGrantedAuthority("admin")))
+ .get().uri("/secured-route").exchange()
+ .expectStatus().isForbidden();
+ // @formatter:on
+ }
+
+ /*---------------------------------------------------------------------------------------------------------*/
+ /* /secured-method */
+ /* This end-point is secured with "@PreAuthorize("hasRole('AUTHORIZED_PERSONNEL')")" on @Controller method */
+ /*---------------------------------------------------------------------------------------------------------*/
+
+ @Test
+ void givenUserIsNotAuthenticated_whenGetSecuredMethod_thenUnauthorized() throws Exception {
+ // @formatter:off
+ api.mutateWith(mockAuthentication(ANONYMOUS_AUTHENTICATION))
+ .get().uri("/secured-method").exchange()
+ .expectStatus().isUnauthorized();
+ // @formatter:on
+ }
+
+ @Test
+ void givenUserIsGrantedWithRoleAuthorizedPersonnel_whenGetSecuredMethod_thenOk() throws Exception {
+ final var secret = "Secret!";
+ when(messageService.getSecret()).thenReturn(Mono.just(secret));
+
+ // @formatter:off
+ api.mutateWith(mockJwt().authorities(new SimpleGrantedAuthority("ROLE_AUTHORIZED_PERSONNEL")))
+ .get().uri("/secured-method").exchange()
+ .expectStatus().isOk()
+ .expectBody(String.class).isEqualTo(secret);
+ // @formatter:on
+ }
+
+ @Test
+ void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() throws Exception {
+ // @formatter:off
+ api.mutateWith(mockJwt().authorities(new SimpleGrantedAuthority("admin")))
+ .get().uri("/secured-method").exchange()
+ .expectStatus().isForbidden();
+ // @formatter:on
+ }
+
+}
+
diff --git a/spring-security-modules/spring-security-oauth2-testing/servlet-resource-server/pom.xml b/spring-security-modules/spring-security-oauth2-testing/servlet-resource-server/pom.xml
new file mode 100644
index 0000000000..8de154b934
--- /dev/null
+++ b/spring-security-modules/spring-security-oauth2-testing/servlet-resource-server/pom.xml
@@ -0,0 +1,58 @@
+
+
+ 4.0.0
+
+ com.baeldung
+ spring-security-oauth2-testing
+ 0.0.1-SNAPSHOT
+
+ com.baeldung.spring-security-modules.testing
+ servlet-resource-server
+ servlet-resource-server
+ Demo project for Spring Boot servlet resource-server
+
+ 17
+
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-resource-server
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ com.c4-soft.springaddons
+ spring-addons-oauth2-test
+ test
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+
+
diff --git a/spring-security-modules/spring-security-oauth2-testing/servlet-resource-server/src/main/java/com/baeldung/ServletResourceServerApplication.java b/spring-security-modules/spring-security-oauth2-testing/servlet-resource-server/src/main/java/com/baeldung/ServletResourceServerApplication.java
new file mode 100644
index 0000000000..ba55944c29
--- /dev/null
+++ b/spring-security-modules/spring-security-oauth2-testing/servlet-resource-server/src/main/java/com/baeldung/ServletResourceServerApplication.java
@@ -0,0 +1,116 @@
+package com.baeldung;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.stereotype.Service;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import lombok.RequiredArgsConstructor;
+
+@SpringBootApplication
+public class ServletResourceServerApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(ServletResourceServerApplication.class, args);
+ }
+
+ @Configuration
+ @EnableMethodSecurity
+ @EnableWebSecurity
+ static class SecurityConf {
+ @Bean
+ SecurityFilterChain filterChain(HttpSecurity http, Converter> authoritiesConverter) throws Exception {
+ // @formatter:off
+ http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwt -> new JwtAuthenticationToken(jwt, authoritiesConverter.convert(jwt)));
+ http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().csrf().disable();
+ http.exceptionHandling().authenticationEntryPoint((request, response, authException) -> {
+ response.addHeader(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Restricted Content\"");
+ response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
+ });
+
+ http.authorizeHttpRequests()
+ .requestMatchers("/secured-route").hasRole("AUTHORIZED_PERSONNEL")
+ .anyRequest().authenticated();
+ // @formatter:on
+
+ return http.build();
+ }
+
+ static interface AuthoritiesConverter extends Converter> {
+ }
+
+ @Bean
+ AuthoritiesConverter realmRoles2AuthoritiesConverter() {
+ return (Jwt jwt) -> {
+ final var realmRoles = Optional.of(jwt.getClaimAsMap("realm_access")).orElse(Map.of());
+ @SuppressWarnings("unchecked")
+ final var roles = (List) realmRoles.getOrDefault("roles", List.of());
+ return roles.stream().map(SimpleGrantedAuthority::new).map(GrantedAuthority.class::cast).toList();
+ };
+ }
+ }
+
+ @Service
+ public static class MessageService {
+
+ public String greet() {
+ final var who = (JwtAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
+ final var claims = who.getTokenAttributes();
+ return "Hello %s! You are granted with %s.".formatted(
+ claims.getOrDefault(StandardClaimNames.PREFERRED_USERNAME, claims.get(StandardClaimNames.SUB)),
+ who.getAuthorities());
+ }
+
+ @PreAuthorize("hasRole('AUTHORIZED_PERSONNEL')")
+ public String getSecret() {
+ return "Only authorized personnel can read that";
+ }
+ }
+
+ @RestController
+ @RequiredArgsConstructor
+ public static class GreetingController {
+ private final MessageService messageService;
+
+ @GetMapping("/greet")
+ public ResponseEntity greet() {
+ return ResponseEntity.ok(messageService.greet());
+ }
+
+ @GetMapping("/secured-route")
+ public ResponseEntity securedRoute() {
+ return ResponseEntity.ok(messageService.getSecret());
+ }
+
+ @GetMapping("/secured-method")
+ @PreAuthorize("hasRole('AUTHORIZED_PERSONNEL')")
+ public ResponseEntity securedMethod() {
+ return ResponseEntity.ok(messageService.getSecret());
+ }
+ }
+
+}
diff --git a/spring-security-modules/spring-security-oauth2-testing/servlet-resource-server/src/main/resources/application.properties b/spring-security-modules/spring-security-oauth2-testing/servlet-resource-server/src/main/resources/application.properties
new file mode 100644
index 0000000000..998f6303aa
--- /dev/null
+++ b/spring-security-modules/spring-security-oauth2-testing/servlet-resource-server/src/main/resources/application.properties
@@ -0,0 +1 @@
+spring.security.oauth2.resourceserver.jwt.issuer-uri=https://localhost:8443/realms/master
diff --git a/spring-security-modules/spring-security-oauth2-testing/servlet-resource-server/src/test/java/com/baeldung/MessageServiceUnitTest.java b/spring-security-modules/spring-security-oauth2-testing/servlet-resource-server/src/test/java/com/baeldung/MessageServiceUnitTest.java
new file mode 100644
index 0000000000..36be2df331
--- /dev/null
+++ b/spring-security-modules/spring-security-oauth2-testing/servlet-resource-server/src/test/java/com/baeldung/MessageServiceUnitTest.java
@@ -0,0 +1,79 @@
+package com.baeldung;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Import;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
+import org.springframework.security.test.context.support.WithAnonymousUser;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import com.baeldung.ServletResourceServerApplication.MessageService;
+import com.c4_soft.springaddons.security.oauth2.test.annotations.OpenIdClaims;
+import com.c4_soft.springaddons.security.oauth2.test.annotations.WithMockJwtAuth;
+
+@Import({ MessageService.class })
+@ExtendWith(SpringExtension.class)
+@EnableMethodSecurity
+class MessageServiceUnitTest {
+ @Autowired
+ MessageService messageService;
+
+ /*----------------------------------------------------------------------------*/
+ /* greet() */
+ /* Expects a JwtAuthenticationToken to be retrieved from the security-context */
+ /*----------------------------------------------------------------------------*/
+
+ @Test
+ void givenSecurityContextIsNotSet_whenGreet_thenThrowsAuthenticationCredentialsNotFoundException() {
+ assertThrows(AuthenticationCredentialsNotFoundException.class, () -> messageService.getSecret());
+ }
+
+ @Test
+ @WithAnonymousUser
+ void givenUserIsNotAuthenticated_whenGreet_thenThrowsAccessDeniedException() {
+ assertThrows(AccessDeniedException.class, () -> messageService.getSecret());
+ }
+
+ @Test
+ @WithMockJwtAuth(authorities = { "admin", "ROLE_AUTHORIZED_PERSONNEL" }, claims = @OpenIdClaims(preferredUsername = "ch4mpy"))
+ void givenSecurityContextIsPopulatedWithJwtAuthenticationToken_whenGreet_thenReturnGreetingWithPreferredUsernameAndAuthorities() {
+ assertEquals("Hello ch4mpy! You are granted with [admin, ROLE_AUTHORIZED_PERSONNEL].", messageService.greet());
+ }
+
+ @Test
+ @WithMockUser(authorities = { "admin", "ROLE_AUTHORIZED_PERSONNEL" }, username = "ch4mpy")
+ void givenSecurityContextIsPopulatedWithUsernamePasswordAuthenticationToken_whenGreet_thenThrowsClassCastException() {
+ assertThrows(ClassCastException.class, () -> messageService.greet());
+ }
+
+ /*--------------------------------------------------------------------*/
+ /* getSecret() */
+ /* is secured with "@PreAuthorize("hasRole('AUTHORIZED_PERSONNEL')")" */
+ /*--------------------------------------------------------------------*/
+
+ @Test
+ @WithAnonymousUser
+ void givenUserIsNotAuthenticated_whenGetSecret_thenThrowsAccessDeniedException() {
+ assertThrows(AccessDeniedException.class, () -> messageService.getSecret());
+ }
+
+ @Test
+ @WithMockJwtAuth(authorities = { "admin", "ROLE_AUTHORIZED_PERSONNEL" }, claims = @OpenIdClaims(preferredUsername = "ch4mpy"))
+ void givenUserIsGrantedWithRoleAuthorizedPersonnel_whenGetSecret_thenReturnSecret() {
+ assertEquals("Only authorized personnel can read that", messageService.getSecret());
+ }
+
+ @Test
+ @WithMockJwtAuth(authorities = { "admin" }, claims = @OpenIdClaims(preferredUsername = "ch4mpy"))
+ void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecret_thenThrowsAccessDeniedException() {
+ assertThrows(AccessDeniedException.class, () -> messageService.getSecret());
+ }
+
+}
diff --git a/spring-security-modules/spring-security-oauth2-testing/servlet-resource-server/src/test/java/com/baeldung/ServletResourceServerApplicationIntegrationTest.java b/spring-security-modules/spring-security-oauth2-testing/servlet-resource-server/src/test/java/com/baeldung/ServletResourceServerApplicationIntegrationTest.java
new file mode 100644
index 0000000000..0c6504bff0
--- /dev/null
+++ b/spring-security-modules/spring-security-oauth2-testing/servlet-resource-server/src/test/java/com/baeldung/ServletResourceServerApplicationIntegrationTest.java
@@ -0,0 +1,114 @@
+package com.baeldung;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.security.test.context.support.WithAnonymousUser;
+import org.springframework.test.web.servlet.MockMvc;
+
+import com.c4_soft.springaddons.security.oauth2.test.annotations.OpenIdClaims;
+import com.c4_soft.springaddons.security.oauth2.test.annotations.WithMockJwtAuth;
+
+@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
+@AutoConfigureMockMvc
+class ServletResourceServerApplicationIntegrationTest {
+ @Autowired
+ MockMvc api;
+
+ /*-----------------------------------------------------------------------------*/
+ /* /greet */
+ /* This end-point secured with ".anyRequest().authenticated()" in SecurityConf */
+ /*-----------------------------------------------------------------------------*/
+
+ @Test
+ @WithAnonymousUser
+ void givenUserIsNotAuthenticated_whenGetGreet_thenUnauthorized() throws Exception {
+ // @formatter:off
+ api.perform(get("/greet"))
+ .andExpect(status().isUnauthorized());
+ // @formatter:on
+ }
+
+ @Test
+ @WithMockJwtAuth(authorities = { "admin", "ROLE_AUTHORIZED_PERSONNEL" }, claims = @OpenIdClaims(preferredUsername = "ch4mpy"))
+ void givenUserIsAuthenticated_whenGetGreet_thenOk() throws Exception {
+ // @formatter:off
+ api.perform(get("/greet"))
+ .andExpect(status().isOk())
+ .andExpect(content().string("Hello ch4mpy! You are granted with [admin, ROLE_AUTHORIZED_PERSONNEL]."));
+ // @formatter:on
+ }
+
+ /*---------------------------------------------------------------------------------------------------------------------*/
+ /* /secured-route */
+ /* This end-point is secured with ".requestMatchers("/secured-route").hasRole("AUTHORIZED_PERSONNEL")" in SecurityConf */
+ /*---------------------------------------------------------------------------------------------------------------------*/
+
+ @Test
+ @WithAnonymousUser
+ void givenUserIsNotAuthenticated_whenGetSecuredRoute_thenUnauthorized() throws Exception {
+ // @formatter:off
+ api.perform(get("/secured-route"))
+ .andExpect(status().isUnauthorized());
+ // @formatter:on
+ }
+
+ @Test
+ @WithMockJwtAuth("ROLE_AUTHORIZED_PERSONNEL")
+ void givenUserIsGrantedWithRoleAuthorizedPersonnel_whenGetSecuredRoute_thenOk() throws Exception {
+ // @formatter:off
+ api.perform(get("/secured-route"))
+ .andExpect(status().isOk())
+ .andExpect(content().string("Only authorized personnel can read that"));
+ // @formatter:on
+ }
+
+ @Test
+ @WithMockJwtAuth("admin")
+ void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception {
+ // @formatter:off
+ api.perform(get("/secured-route"))
+ .andExpect(status().isForbidden());
+ // @formatter:on
+ }
+
+ /*---------------------------------------------------------------------------------------------------------*/
+ /* /secured-method */
+ /* This end-point is secured with "@PreAuthorize("hasRole('AUTHORIZED_PERSONNEL')")" on @Controller method */
+ /*---------------------------------------------------------------------------------------------------------*/
+
+ @Test
+ @WithAnonymousUser
+ void givenUserIsNotAuthenticated_whenGetSecuredMethod_thenUnauthorized() throws Exception {
+ // @formatter:off
+ api.perform(get("/secured-method"))
+ .andExpect(status().isUnauthorized());
+ // @formatter:on
+ }
+
+ @Test
+ @WithMockJwtAuth("ROLE_AUTHORIZED_PERSONNEL")
+ void givenUserIsGrantedWithRoleAuthorizedPersonnel_whenGetSecuredMethod_thenOk() throws Exception {
+ // @formatter:off
+ api.perform(get("/secured-method"))
+ .andExpect(status().isOk())
+ .andExpect(content().string("Only authorized personnel can read that"));
+ // @formatter:on
+ }
+
+ @Test
+ @WithMockJwtAuth("admin")
+ void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() throws Exception {
+ // @formatter:off
+ api.perform(get("/secured-method"))
+ .andExpect(status().isForbidden());
+ // @formatter:on
+ }
+
+}
diff --git a/spring-security-modules/spring-security-oauth2-testing/servlet-resource-server/src/test/java/com/baeldung/SpringAddonsGreetingControllerUnitTest.java b/spring-security-modules/spring-security-oauth2-testing/servlet-resource-server/src/test/java/com/baeldung/SpringAddonsGreetingControllerUnitTest.java
new file mode 100644
index 0000000000..76902bb9b7
--- /dev/null
+++ b/spring-security-modules/spring-security-oauth2-testing/servlet-resource-server/src/test/java/com/baeldung/SpringAddonsGreetingControllerUnitTest.java
@@ -0,0 +1,131 @@
+package com.baeldung;
+
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.security.test.context.support.WithAnonymousUser;
+import org.springframework.test.web.servlet.MockMvc;
+
+import com.baeldung.ServletResourceServerApplication.GreetingController;
+import com.baeldung.ServletResourceServerApplication.MessageService;
+import com.c4_soft.springaddons.security.oauth2.test.annotations.WithMockJwtAuth;
+
+@WebMvcTest(controllers = GreetingController.class, properties = { "server.ssl.enabled=false" })
+class SpringAddonsGreetingControllerUnitTest {
+
+ @MockBean
+ MessageService messageService;
+
+ @Autowired
+ MockMvc api;
+
+ /*-----------------------------------------------------------------------------*/
+ /* /greet */
+ /* This end-point secured with ".anyRequest().authenticated()" in SecurityConf */
+ /*-----------------------------------------------------------------------------*/
+
+ @Test
+ @WithAnonymousUser
+ void givenUserIsNotAuthenticated_whenGetGreet_thenUnauthorized() throws Exception {
+ // @formatter:off
+ api.perform(get("/greet"))
+ .andExpect(status().isUnauthorized());
+ // @formatter:on
+ }
+
+ @Test
+ @WithMockJwtAuth
+ void givenUserIsAuthenticated_whenGetGreet_thenOk() throws Exception {
+ final var greeting = "Whatever the service returns";
+ when(messageService.greet()).thenReturn(greeting);
+
+ // @formatter:off
+ api.perform(get("/greet"))
+ .andExpect(status().isOk())
+ .andExpect(content().string(greeting));
+ // @formatter:on
+
+ verify(messageService, times(1)).greet();
+ }
+
+ /*---------------------------------------------------------------------------------------------------------------------*/
+ /* /secured-route */
+ /* This end-point is secured with ".requestMatchers("/secured-route").hasRole("AUTHORIZED_PERSONNEL")" in SecurityConf */
+ /*---------------------------------------------------------------------------------------------------------------------*/
+
+ @Test
+ @WithAnonymousUser
+ void givenUserIsNotAuthenticated_whenGetSecuredRoute_thenUnauthorized() throws Exception {
+ // @formatter:off
+ api.perform(get("/secured-route"))
+ .andExpect(status().isUnauthorized());
+ // @formatter:on
+ }
+
+ @Test
+ @WithMockJwtAuth({ "admin", "ROLE_AUTHORIZED_PERSONNEL" })
+ void givenUserIsGrantedWithRoleAuthorizedPersonnel_whenGetSecuredRoute_thenOk() throws Exception {
+ final var secret = "Secret!";
+ when(messageService.getSecret()).thenReturn(secret);
+
+ // @formatter:off
+ api.perform(get("/secured-route"))
+ .andExpect(status().isOk())
+ .andExpect(content().string(secret));
+ // @formatter:on
+ }
+
+ @Test
+ @WithMockJwtAuth({ "admin" })
+ void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception {
+ // @formatter:off
+ api.perform(get("/secured-route"))
+ .andExpect(status().isForbidden());
+ // @formatter:on
+ }
+
+ /*---------------------------------------------------------------------------------------------------------*/
+ /* /secured-method */
+ /* This end-point is secured with "@PreAuthorize("hasRole('AUTHORIZED_PERSONNEL')")" on @Controller method */
+ /*---------------------------------------------------------------------------------------------------------*/
+
+ @Test
+ @WithAnonymousUser
+ void givenUserIsNotAuthenticated_whenGetSecuredMethod_thenUnauthorized() throws Exception {
+ // @formatter:off
+ api.perform(get("/secured-method"))
+ .andExpect(status().isUnauthorized());
+ // @formatter:on
+ }
+
+ @Test
+ @WithMockJwtAuth({ "admin", "ROLE_AUTHORIZED_PERSONNEL" })
+ void givenUserIsGrantedWithRoleAuthorizedPersonnel_whenGetSecuredMethod_thenOk() throws Exception {
+ final var secret = "Secret!";
+ when(messageService.getSecret()).thenReturn(secret);
+
+ // @formatter:off
+ api.perform(get("/secured-method"))
+ .andExpect(status().isOk())
+ .andExpect(content().string(secret));
+ // @formatter:on
+ }
+
+ @Test
+ @WithMockJwtAuth(authorities = { "admin" })
+ void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() throws Exception {
+ // @formatter:off
+ api.perform(get("/secured-method"))
+ .andExpect(status().isForbidden());
+ // @formatter:on
+ }
+
+}
diff --git a/spring-security-modules/spring-security-oauth2-testing/servlet-resource-server/src/test/java/com/baeldung/SpringSecurityTestGreetingControllerUnitTest.java b/spring-security-modules/spring-security-oauth2-testing/servlet-resource-server/src/test/java/com/baeldung/SpringSecurityTestGreetingControllerUnitTest.java
new file mode 100644
index 0000000000..aa53dd7531
--- /dev/null
+++ b/spring-security-modules/spring-security-oauth2-testing/servlet-resource-server/src/test/java/com/baeldung/SpringSecurityTestGreetingControllerUnitTest.java
@@ -0,0 +1,124 @@
+package com.baeldung;
+
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.anonymous;
+import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.test.web.servlet.MockMvc;
+
+import com.baeldung.ServletResourceServerApplication.GreetingController;
+import com.baeldung.ServletResourceServerApplication.MessageService;
+
+@WebMvcTest(controllers = GreetingController.class, properties = { "server.ssl.enabled=false" })
+class SpringSecurityTestGreetingControllerUnitTest {
+
+ @MockBean
+ MessageService messageService;
+
+ @Autowired
+ MockMvc api;
+
+ /*-----------------------------------------------------------------------------*/
+ /* /greet */
+ /* This end-point secured with ".anyRequest().authenticated()" in SecurityConf */
+ /*-----------------------------------------------------------------------------*/
+
+ @Test
+ void givenUserIsNotAuthenticated_whenGetGreet_thenUnauthorized() throws Exception {
+ // @formatter:off
+ api.perform(get("/greet").with(anonymous()))
+ .andExpect(status().isUnauthorized());
+ // @formatter:on
+ }
+
+ @Test
+ void givenUserIsAuthenticated_whenGetGreet_thenOk() throws Exception {
+ final var greeting = "Whatever the service returns";
+ when(messageService.greet()).thenReturn(greeting);
+
+ // @formatter:off
+ api.perform(get("/greet").with(jwt()))
+ .andExpect(status().isOk())
+ .andExpect(content().string(greeting));
+ // @formatter:on
+
+ verify(messageService, times(1)).greet();
+ }
+
+ /*---------------------------------------------------------------------------------------------------------------------*/
+ /* /secured-route */
+ /* This end-point is secured with ".requestMatchers("/secured-route").hasRole("AUTHORIZED_PERSONNEL")" in SecurityConf */
+ /*---------------------------------------------------------------------------------------------------------------------*/
+
+ @Test
+ void givenUserIsNotAuthenticated_whenGetSecuredRoute_thenUnauthorized() throws Exception {
+ // @formatter:off
+ api.perform(get("/secured-route").with(anonymous()))
+ .andExpect(status().isUnauthorized());
+ // @formatter:on
+ }
+
+ @Test
+ void givenUserIsGrantedWithRoleAuthorizedPersonnel_whenGetSecuredRoute_thenOk() throws Exception {
+ final var secret = "Secret!";
+ when(messageService.getSecret()).thenReturn(secret);
+
+ // @formatter:off
+ api.perform(get("/secured-route").with(jwt().authorities(new SimpleGrantedAuthority("ROLE_AUTHORIZED_PERSONNEL"))))
+ .andExpect(status().isOk())
+ .andExpect(content().string(secret));
+ // @formatter:on
+ }
+
+ @Test
+ void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception {
+ // @formatter:off
+ api.perform(get("/secured-route").with(jwt().authorities(new SimpleGrantedAuthority("admin"))))
+ .andExpect(status().isForbidden());
+ // @formatter:on
+ }
+
+ /*---------------------------------------------------------------------------------------------------------*/
+ /* /secured-method */
+ /* This end-point is secured with "@PreAuthorize("hasRole('AUTHORIZED_PERSONNEL')")" on @Controller method */
+ /*---------------------------------------------------------------------------------------------------------*/
+
+ @Test
+ void givenUserIsNotAuthenticated_whenGetSecuredMethod_thenUnauthorized() throws Exception {
+ // @formatter:off
+ api.perform(get("/secured-method").with(anonymous()))
+ .andExpect(status().isUnauthorized());
+ // @formatter:on
+ }
+
+ @Test
+ void givenUserIsGrantedWithRoleAuthorizedPersonnel_whenGetSecuredMethod_thenOk() throws Exception {
+ final var secret = "Secret!";
+ when(messageService.getSecret()).thenReturn(secret);
+
+ // @formatter:off
+ api.perform(get("/secured-method").with(jwt().authorities(new SimpleGrantedAuthority("ROLE_AUTHORIZED_PERSONNEL"))))
+ .andExpect(status().isOk())
+ .andExpect(content().string(secret));
+ // @formatter:on
+ }
+
+ @Test
+ void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() throws Exception {
+ // @formatter:off
+ api.perform(get("/secured-method").with(jwt().authorities(new SimpleGrantedAuthority("admin"))))
+ .andExpect(status().isForbidden());
+ // @formatter:on
+ }
+
+}