diff --git a/spring-security-modules/spring-security-oauth2-testing/pom.xml b/spring-security-modules/spring-security-oauth2-testing/pom.xml
index 93348cb48c..45fcf9bcce 100644
--- a/spring-security-modules/spring-security-oauth2-testing/pom.xml
+++ b/spring-security-modules/spring-security-oauth2-testing/pom.xml
@@ -14,7 +14,7 @@
../../parent-boot-3
- 6.1.0
+ 7.1.10
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
index 500d876bc4..716900ea51 100644
--- 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
@@ -1,7 +1,8 @@
package com.baeldung;
+import static org.springframework.security.config.Customizer.withDefaults;
+
import java.nio.charset.Charset;
-import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -27,6 +28,7 @@ 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.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverter;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.context.NoOpServerSecurityContextRepository;
import org.springframework.stereotype.Service;
@@ -34,6 +36,7 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import lombok.RequiredArgsConstructor;
+import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@SpringBootApplication
@@ -46,68 +49,66 @@ public class ReactiveResourceServerApplication {
@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
- public class SecurityConfig {
+ static 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 -> {
+ SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+ http.oauth2ResourceServer(resourceServer -> resourceServer.jwt(withDefaults()));
+ http.securityContextRepository(NoOpServerSecurityContextRepository.getInstance());
+ http.csrf(csrf -> csrf.disable());
+ http.exceptionHandling(eh -> eh
+ .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);
+ 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()));
+ final var buffer = dataBufferFactory.wrap(ex.getMessage().getBytes(Charset.defaultCharset()));
return response.writeWith(Mono.just(buffer))
- .doOnError(error -> DataBufferUtils.release(buffer));
- }));
+ .doOnError(error -> DataBufferUtils.release(buffer));
+ })));
- http.authorizeExchange()
- .pathMatchers("/secured-route")
- .hasRole("AUTHORIZED_PERSONNEL")
- .anyExchange()
- .authenticated();
+ // @formatter:off
+ http.authorizeExchange(req -> req
+ .pathMatchers("/secured-route").hasRole("AUTHORIZED_PERSONNEL").anyExchange()
+ .authenticated());
+ // @formatter:on
return http.build();
}
- static interface AuthoritiesConverter extends Converter>> {
+ static interface ReactiveJwtAuthoritiesConverter extends Converter> {
}
@Bean
- AuthoritiesConverter realmRoles2AuthoritiesConverter() {
+ ReactiveJwtAuthoritiesConverter realmRoles2AuthoritiesConverter() {
return (Jwt jwt) -> {
- final var realmRoles = Optional.of(jwt.getClaimAsMap("realm_access"))
- .orElse(Map.of());
+ 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());
+ return Flux.fromStream(roles.stream()).map(SimpleGrantedAuthority::new)
+ .map(GrantedAuthority.class::cast);
};
}
+
+ @Bean
+ ReactiveJwtAuthenticationConverter authenticationConverter(
+ Converter> authoritiesConverter) {
+ final var authenticationConverter = new ReactiveJwtAuthenticationConverter();
+ authenticationConverter.setPrincipalClaimName(StandardClaimNames.PREFERRED_USERNAME);
+ authenticationConverter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);
+ return authenticationConverter;
+ }
}
@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")));
+ return ReactiveSecurityContextHolder.getContext().map(ctx -> {
+ final var who = (JwtAuthenticationToken) ctx.getAuthentication();
+ return "Hello %s! You are granted with %s.".formatted(who.getName(), who.getAuthorities());
+ }).switchIfEmpty(Mono.error(new AuthenticationCredentialsNotFoundException("Security context is empty")));
}
@PreAuthorize("hasRole('AUTHORIZED_PERSONNEL')")
@@ -118,26 +119,23 @@ public class ReactiveResourceServerApplication {
@RestController
@RequiredArgsConstructor
- public class GreetingController {
+ public static class GreetingController {
private final MessageService messageService;
@GetMapping("/greet")
public Mono> greet() {
- return messageService.greet()
- .map(ResponseEntity::ok);
+ return messageService.greet().map(ResponseEntity::ok);
}
@GetMapping("/secured-route")
public Mono> securedRoute() {
- return messageService.getSecret()
- .map(ResponseEntity::ok);
+ return messageService.getSecret().map(ResponseEntity::ok);
}
@GetMapping("/secured-method")
@PreAuthorize("hasRole('AUTHORIZED_PERSONNEL')")
public Mono> securedMethod() {
- return messageService.getSecret()
- .map(ResponseEntity::ok);
+ return messageService.getSecret().map(ResponseEntity::ok);
}
}
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
index 97893bc1fb..c13a20ca38 100644
--- 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
@@ -3,28 +3,49 @@ package com.baeldung;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import java.util.stream.Stream;
+
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
+import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
-import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
+import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
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 com.baeldung.ReactiveResourceServerApplication.SecurityConfig;
+import com.c4_soft.springaddons.security.oauth2.test.AuthenticationFactoriesTestConf;
+import com.c4_soft.springaddons.security.oauth2.test.annotations.WithJwt;
+import com.c4_soft.springaddons.security.oauth2.test.annotations.parameterized.ParameterizedAuthentication;
-@Import({ MessageService.class })
+@Import({ MessageService.class, SecurityConfig.class })
+@ImportAutoConfiguration(AuthenticationFactoriesTestConf.class)
@ExtendWith(SpringExtension.class)
-@EnableReactiveMethodSecurity
+@TestInstance(Lifecycle.PER_CLASS)
class MessageServiceUnitTest {
@Autowired
MessageService messageService;
+ @Autowired
+ WithJwt.AuthenticationFactory authFactory;
+
+ @MockBean
+ ReactiveJwtDecoder jwtDecoder;
+
/*----------------------------------------------------------------------------*/
/* greet() */
/* Expects a JwtAuthenticationToken to be retrieved from the security-context */
@@ -43,10 +64,12 @@ class MessageServiceUnitTest {
.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()
+ @ParameterizedTest
+ @MethodSource("allIdentities")
+ void givenUserIsAuthenticated_whenGreet_thenReturnGreetingWithPreferredUsernameAndAuthorities(@ParameterizedAuthentication Authentication auth) {
+ final var jwt = (JwtAuthenticationToken) auth;
+ final var expected = "Hello %s! You are granted with %s.".formatted(jwt.getTokenAttributes().get(StandardClaimNames.PREFERRED_USERNAME), auth.getAuthorities());
+ assertEquals(expected, messageService.greet()
.block());
}
@@ -70,17 +93,25 @@ class MessageServiceUnitTest {
}
@Test
- @WithMockJwtAuth(authorities = { "admin", "ROLE_AUTHORIZED_PERSONNEL" }, claims = @OpenIdClaims(preferredUsername = "ch4mpy"))
- void givenUserIsGrantedWithRoleAuthorizedPersonnel_whenGetSecret_thenReturnSecret() {
+ @WithJwt("ch4mpy.json")
+ void givenUserIsCh4mpy_whenGetSecret_thenReturnSecret() {
assertEquals("Only authorized personnel can read that", messageService.getSecret()
.block());
}
@Test
- @WithMockJwtAuth(authorities = { "admin" }, claims = @OpenIdClaims(preferredUsername = "ch4mpy"))
- void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecret_thenThrowsAccessDeniedException() {
+ @WithJwt("tonton-pirate.json")
+ void givenUserIsTontonPirate_whenGetSecret_thenThrowsAccessDeniedException() {
assertThrows(AccessDeniedException.class, () -> messageService.getSecret()
.block());
}
+ /*--------------------------------------------*/
+ /* methodSource returning all test identities */
+ /*--------------------------------------------*/
+ private Stream allIdentities() {
+ final var authentications = authFactory.authenticationsFrom("ch4mpy.json", "tonton-pirate.json").toList();
+ return authentications.stream();
+ }
+
}
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
index 1ee6fc7e87..d6bfbf4e2d 100644
--- 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
@@ -8,8 +8,8 @@ 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;
+import com.c4_soft.springaddons.security.oauth2.test.annotations.WithJwt;
+import com.c4_soft.springaddons.security.oauth2.test.annotations.WithMockAuthentication;
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
@AutoConfigureWebTestClient
@@ -33,7 +33,7 @@ class ReactiveResourceServerApplicationIntegrationTest {
}
@Test
- @WithMockJwtAuth(authorities = { "admin", "ROLE_AUTHORIZED_PERSONNEL" }, claims = @OpenIdClaims(preferredUsername = "ch4mpy"))
+ @WithJwt("ch4mpy.json")
void givenUserIsAuthenticated_whenGetGreet_thenOk() throws Exception {
api.get()
.uri("/greet")
@@ -60,7 +60,7 @@ class ReactiveResourceServerApplicationIntegrationTest {
}
@Test
- @WithMockJwtAuth("ROLE_AUTHORIZED_PERSONNEL")
+ @WithMockAuthentication("ROLE_AUTHORIZED_PERSONNEL")
void givenUserIsGrantedWithRoleAuthorizedPersonnel_whenGetSecuredRoute_thenOk() throws Exception {
api.get()
.uri("/secured-route")
@@ -72,7 +72,7 @@ class ReactiveResourceServerApplicationIntegrationTest {
}
@Test
- @WithMockJwtAuth("admin")
+ @WithMockAuthentication("admin")
void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception {
api.get()
.uri("/secured-route")
@@ -97,7 +97,7 @@ class ReactiveResourceServerApplicationIntegrationTest {
}
@Test
- @WithMockJwtAuth("ROLE_AUTHORIZED_PERSONNEL")
+ @WithMockAuthentication("ROLE_AUTHORIZED_PERSONNEL")
void givenUserIsGrantedWithRoleAuthorizedPersonnel_whenGetSecuredMethod_thenOk() throws Exception {
api.get()
.uri("/secured-method")
@@ -109,7 +109,7 @@ class ReactiveResourceServerApplicationIntegrationTest {
}
@Test
- @WithMockJwtAuth("admin")
+ @WithMockAuthentication("admin")
void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() throws Exception {
api.get()
.uri("/secured-method")
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
index 6f55f287d8..f31bbe3ae8 100644
--- 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
@@ -5,16 +5,19 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
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.core.Authentication;
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 com.c4_soft.springaddons.security.oauth2.test.annotations.WithMockAuthentication;
+import com.c4_soft.springaddons.security.oauth2.test.annotations.parameterized.AuthenticationSource;
+import com.c4_soft.springaddons.security.oauth2.test.annotations.parameterized.ParameterizedAuthentication;
import reactor.core.publisher.Mono;
@@ -28,115 +31,88 @@ class SpringAddonsGreetingControllerUnitTest {
WebTestClient api;
/*-----------------------------------------------------------------------------*/
- /* /greet */
- /* This end-point secured with ".anyRequest().authenticated()" in SecurityConf */
+ /* /greet */
+ /*
+ * This end-point secured with ".anyRequest().authenticated()" in SecurityConf
+ */
/*-----------------------------------------------------------------------------*/
@Test
@WithAnonymousUser
void givenRequestIsAnonymous_whenGetGreet_thenUnauthorized() throws Exception {
- api.get()
- .uri("/greet")
- .exchange()
- .expectStatus()
- .isUnauthorized();
+ api.get().uri("/greet").exchange().expectStatus().isUnauthorized();
}
- @Test
- @WithMockJwtAuth(authorities = { "admin", "ROLE_AUTHORIZED_PERSONNEL" }, claims = @OpenIdClaims(preferredUsername = "ch4mpy"))
- void givenUserIsAuthenticated_whenGetGreet_thenOk() throws Exception {
+ @ParameterizedTest
+ @AuthenticationSource({
+ @WithMockAuthentication(authorities = { "admin", "ROLE_AUTHORIZED_PERSONNEL" }, name = "ch4mpy"),
+ @WithMockAuthentication(authorities = { "uncle", "PIRATE" }, name = "tonton-pirate") })
+ void givenUserIsAuthenticated_whenGetGreet_thenOk(@ParameterizedAuthentication Authentication auth) throws Exception {
final var greeting = "Whatever the service returns";
when(messageService.greet()).thenReturn(Mono.just(greeting));
- api.get()
- .uri("/greet")
- .exchange()
- .expectStatus()
- .isOk()
- .expectBody(String.class)
- .isEqualTo(greeting);
+ api.get().uri("/greet").exchange().expectStatus().isOk().expectBody(String.class).isEqualTo(greeting);
verify(messageService, times(1)).greet();
}
/*---------------------------------------------------------------------------------------------------------------------*/
- /* /secured-route */
- /* This end-point is secured with ".requestMatchers("/secured-route").hasRole("AUTHORIZED_PERSONNEL")" in SecurityConf */
+ /* /secured-route */
+ /*
+ * This end-point is secured with
+ * ".requestMatchers("/secured-route").hasRole("AUTHORIZED_PERSONNEL")" in
+ * SecurityConf
+ */
/*---------------------------------------------------------------------------------------------------------------------*/
@Test
@WithAnonymousUser
void givenRequestIsAnonymous_whenGetSecuredRoute_thenUnauthorized() throws Exception {
- api.get()
- .uri("/secured-route")
- .exchange()
- .expectStatus()
- .isUnauthorized();
+ api.get().uri("/secured-route").exchange().expectStatus().isUnauthorized();
}
@Test
- @WithMockJwtAuth("ROLE_AUTHORIZED_PERSONNEL")
+ @WithMockAuthentication("ROLE_AUTHORIZED_PERSONNEL")
void givenUserIsGrantedWithRoleAuthorizedPersonnel_whenGetSecuredRoute_thenOk() throws Exception {
final var secret = "Secret!";
when(messageService.getSecret()).thenReturn(Mono.just(secret));
- api.get()
- .uri("/secured-route")
- .exchange()
- .expectStatus()
- .isOk()
- .expectBody(String.class)
- .isEqualTo(secret);
+ api.get().uri("/secured-route").exchange().expectStatus().isOk().expectBody(String.class).isEqualTo(secret);
}
@Test
- @WithMockJwtAuth("admin")
+ @WithMockAuthentication("admin")
void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception {
- api.get()
- .uri("/secured-route")
- .exchange()
- .expectStatus()
- .isForbidden();
+ api.get().uri("/secured-route").exchange().expectStatus().isForbidden();
}
/*---------------------------------------------------------------------------------------------------------*/
- /* /secured-method */
- /* This end-point is secured with "@PreAuthorize("hasRole('AUTHORIZED_PERSONNEL')")" on @Controller method */
+ /* /secured-method */
+ /*
+ * This end-point is secured with
+ * "@PreAuthorize("hasRole('AUTHORIZED_PERSONNEL')")" on @Controller method
+ */
/*---------------------------------------------------------------------------------------------------------*/
@Test
@WithAnonymousUser
void givenRequestIsAnonymous_whenGetSecuredMethod_thenUnauthorized() throws Exception {
- api.get()
- .uri("/secured-method")
- .exchange()
- .expectStatus()
- .isUnauthorized();
+ api.get().uri("/secured-method").exchange().expectStatus().isUnauthorized();
}
@Test
- @WithMockJwtAuth("ROLE_AUTHORIZED_PERSONNEL")
+ @WithMockAuthentication("ROLE_AUTHORIZED_PERSONNEL")
void givenUserIsGrantedWithRoleAuthorizedPersonnel_whenGetSecuredMethod_thenOk() throws Exception {
final var secret = "Secret!";
when(messageService.getSecret()).thenReturn(Mono.just(secret));
- api.get()
- .uri("/secured-method")
- .exchange()
- .expectStatus()
- .isOk()
- .expectBody(String.class)
- .isEqualTo(secret);
+ api.get().uri("/secured-method").exchange().expectStatus().isOk().expectBody(String.class).isEqualTo(secret);
}
@Test
- @WithMockJwtAuth("admin")
+ @WithMockAuthentication("admin")
void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() throws Exception {
- api.get()
- .uri("/secured-method")
- .exchange()
- .expectStatus()
- .isForbidden();
+ api.get().uri("/secured-method").exchange().expectStatus().isForbidden();
}
}
diff --git a/spring-security-modules/spring-security-oauth2-testing/reactive-resource-server/src/test/resources/ch4mpy.json b/spring-security-modules/spring-security-oauth2-testing/reactive-resource-server/src/test/resources/ch4mpy.json
new file mode 100644
index 0000000000..22f7bb2cea
--- /dev/null
+++ b/spring-security-modules/spring-security-oauth2-testing/reactive-resource-server/src/test/resources/ch4mpy.json
@@ -0,0 +1,15 @@
+{
+ "iss": "https://localhost:8443/realms/master",
+ "sub": "281c4558-550c-413b-9972-2d2e5bde6b9b",
+ "iat": 1695992542,
+ "exp": 1695992642,
+ "preferred_username": "ch4mpy",
+ "realm_access": {
+ "roles": [
+ "admin",
+ "ROLE_AUTHORIZED_PERSONNEL"
+ ]
+ },
+ "email": "ch4mp@c4-soft.com",
+ "scope": "openid email"
+}
\ No newline at end of file
diff --git a/spring-security-modules/spring-security-oauth2-testing/reactive-resource-server/src/test/resources/tonton-pirate.json b/spring-security-modules/spring-security-oauth2-testing/reactive-resource-server/src/test/resources/tonton-pirate.json
new file mode 100644
index 0000000000..13a422f6fd
--- /dev/null
+++ b/spring-security-modules/spring-security-oauth2-testing/reactive-resource-server/src/test/resources/tonton-pirate.json
@@ -0,0 +1,15 @@
+{
+ "iss": "https://localhost:8443/realms/master",
+ "sub": "2d2e5bde6b9b-550c-413b-9972-281c4558",
+ "iat": 1695992551,
+ "exp": 1695992651,
+ "preferred_username": "tonton-pirate",
+ "realm_access": {
+ "roles": [
+ "uncle",
+ "PIRATE"
+ ]
+ },
+ "email": "tonton-pirate@c4-soft.com",
+ "scope": "openid email"
+}
\ No newline at end of file
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
index a30c60eab0..8258955afe 100644
--- 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
@@ -1,5 +1,7 @@
package com.baeldung;
+import static org.springframework.security.config.Customizer.withDefaults;
+
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -23,8 +25,10 @@ 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.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -43,56 +47,52 @@ public class ServletResourceServerApplication {
@EnableWebSecurity
static class SecurityConf {
@Bean
- SecurityFilterChain filterChain(HttpSecurity http, Converter> authoritiesConverter) throws Exception {
- 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());
- });
+ SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+ http.oauth2ResourceServer(resourceServer -> resourceServer.jwt(withDefaults()));
+ http.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
+ http.csrf(csrf -> csrf.disable());
+ http.exceptionHandling(eh -> eh.authenticationEntryPoint((request, response, authException) -> {
+ response.addHeader(HttpHeaders.WWW_AUTHENTICATE, "Bearer realm=\"Restricted Content\"");
+ response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
+ }));
- http.authorizeHttpRequests()
- .requestMatchers("/secured-route")
- .hasRole("AUTHORIZED_PERSONNEL")
- .anyRequest()
- .authenticated();
+ // @formatter:off
+ http.authorizeHttpRequests(req -> req
+ .requestMatchers(new AntPathRequestMatcher("/secured-route")).hasRole("AUTHORIZED_PERSONNEL")
+ .anyRequest().authenticated());
+ // @formatter:on
return http.build();
}
- static interface AuthoritiesConverter extends Converter> {
+ static interface JwtAuthoritiesConverter extends Converter> {
}
@Bean
- AuthoritiesConverter realmRoles2AuthoritiesConverter() {
+ JwtAuthoritiesConverter realmRoles2AuthoritiesConverter() {
return (Jwt jwt) -> {
- final var realmRoles = Optional.of(jwt.getClaimAsMap("realm_access"))
- .orElse(Map.of());
+ 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();
+ return roles.stream().map(SimpleGrantedAuthority::new).map(GrantedAuthority.class::cast).toList();
};
}
+
+ @Bean
+ JwtAuthenticationConverter authenticationConverter(Converter> authoritiesConverter) {
+ final var authenticationConverter = new JwtAuthenticationConverter();
+ authenticationConverter.setPrincipalClaimName(StandardClaimNames.PREFERRED_USERNAME);
+ authenticationConverter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);
+ return authenticationConverter;
+ }
}
@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());
+ final var who = (JwtAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
+ return "Hello %s! You are granted with %s.".formatted(who.getName(), who.getAuthorities());
}
@PreAuthorize("hasRole('AUTHORIZED_PERSONNEL')")
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
index 3c608d226e..ca237fb888 100644
--- 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
@@ -3,28 +3,49 @@ package com.baeldung;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import java.util.stream.Stream;
+
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
+import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
-import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
+import org.springframework.security.oauth2.jwt.JwtDecoder;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
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 com.baeldung.ServletResourceServerApplication.SecurityConf;
+import com.c4_soft.springaddons.security.oauth2.test.AuthenticationFactoriesTestConf;
+import com.c4_soft.springaddons.security.oauth2.test.annotations.WithJwt;
+import com.c4_soft.springaddons.security.oauth2.test.annotations.parameterized.ParameterizedAuthentication;
-@Import({ MessageService.class })
+@Import({ MessageService.class, SecurityConf.class })
+@ImportAutoConfiguration(AuthenticationFactoriesTestConf.class)
@ExtendWith(SpringExtension.class)
-@EnableMethodSecurity
+@TestInstance(Lifecycle.PER_CLASS)
class MessageServiceUnitTest {
@Autowired
MessageService messageService;
+ @Autowired
+ WithJwt.AuthenticationFactory authFactory;
+
+ @MockBean
+ JwtDecoder jwtDecoder;
+
/*----------------------------------------------------------------------------*/
/* greet() */
/* Expects a JwtAuthenticationToken to be retrieved from the security-context */
@@ -41,10 +62,12 @@ class MessageServiceUnitTest {
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());
+ @ParameterizedTest
+ @MethodSource("allIdentities")
+ void givenUserIsAuthenticated_whenGreet_thenReturnGreetingWithPreferredUsernameAndAuthorities(@ParameterizedAuthentication Authentication auth) {
+ final var jwt = (JwtAuthenticationToken) auth;
+ final var expected = "Hello %s! You are granted with %s.".formatted(jwt.getTokenAttributes().get(StandardClaimNames.PREFERRED_USERNAME), auth.getAuthorities());
+ assertEquals(expected, messageService.greet());
}
@Test
@@ -65,15 +88,22 @@ class MessageServiceUnitTest {
}
@Test
- @WithMockJwtAuth(authorities = { "admin", "ROLE_AUTHORIZED_PERSONNEL" }, claims = @OpenIdClaims(preferredUsername = "ch4mpy"))
- void givenUserIsGrantedWithRoleAuthorizedPersonnel_whenGetSecret_thenReturnSecret() {
+ @WithJwt("ch4mpy.json")
+ void givenUserIsCh4mpy_whenGetSecret_thenReturnSecret() {
assertEquals("Only authorized personnel can read that", messageService.getSecret());
}
@Test
- @WithMockJwtAuth(authorities = { "admin" }, claims = @OpenIdClaims(preferredUsername = "ch4mpy"))
- void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecret_thenThrowsAccessDeniedException() {
+ @WithJwt("tonton-pirate.json")
+ void givenUserIsTontonPirate_whenGetSecret_thenThrowsAccessDeniedException() {
assertThrows(AccessDeniedException.class, () -> messageService.getSecret());
}
+ /*--------------------------------------------*/
+ /* methodSource returning all test identities */
+ /*--------------------------------------------*/
+ private Stream allIdentities() {
+ final var authentications = authFactory.authenticationsFrom("ch4mpy.json", "tonton-pirate.json").toList();
+ return authentications.stream();
+ }
}
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
index 5bb539741f..4f2fe51787 100644
--- 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
@@ -12,8 +12,8 @@ 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;
+import com.c4_soft.springaddons.security.oauth2.test.annotations.WithJwt;
+import com.c4_soft.springaddons.security.oauth2.test.annotations.WithMockAuthentication;
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
@AutoConfigureMockMvc
@@ -34,7 +34,7 @@ class ServletResourceServerApplicationIntegrationTest {
}
@Test
- @WithMockJwtAuth(authorities = { "admin", "ROLE_AUTHORIZED_PERSONNEL" }, claims = @OpenIdClaims(preferredUsername = "ch4mpy"))
+ @WithJwt("ch4mpy.json")
void givenUserIsAuthenticated_whenGetGreet_thenOk() throws Exception {
api.perform(get("/greet"))
.andExpect(status().isOk())
@@ -54,7 +54,7 @@ class ServletResourceServerApplicationIntegrationTest {
}
@Test
- @WithMockJwtAuth("ROLE_AUTHORIZED_PERSONNEL")
+ @WithMockAuthentication("ROLE_AUTHORIZED_PERSONNEL")
void givenUserIsGrantedWithRoleAuthorizedPersonnel_whenGetSecuredRoute_thenOk() throws Exception {
api.perform(get("/secured-route"))
.andExpect(status().isOk())
@@ -62,7 +62,7 @@ class ServletResourceServerApplicationIntegrationTest {
}
@Test
- @WithMockJwtAuth("admin")
+ @WithMockAuthentication("admin")
void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception {
api.perform(get("/secured-route"))
.andExpect(status().isForbidden());
@@ -81,7 +81,7 @@ class ServletResourceServerApplicationIntegrationTest {
}
@Test
- @WithMockJwtAuth("ROLE_AUTHORIZED_PERSONNEL")
+ @WithMockAuthentication("ROLE_AUTHORIZED_PERSONNEL")
void givenUserIsGrantedWithRoleAuthorizedPersonnel_whenGetSecuredMethod_thenOk() throws Exception {
api.perform(get("/secured-method"))
.andExpect(status().isOk())
@@ -89,7 +89,7 @@ class ServletResourceServerApplicationIntegrationTest {
}
@Test
- @WithMockJwtAuth("admin")
+ @WithMockAuthentication("admin")
void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() throws Exception {
api.perform(get("/secured-method"))
.andExpect(status().isForbidden());
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
index 9162768930..2534d9919a 100644
--- 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
@@ -8,16 +8,19 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
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.Authentication;
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.OpenIdClaims;
-import com.c4_soft.springaddons.security.oauth2.test.annotations.WithMockJwtAuth;
+import com.c4_soft.springaddons.security.oauth2.test.annotations.WithMockAuthentication;
+import com.c4_soft.springaddons.security.oauth2.test.annotations.parameterized.AuthenticationSource;
+import com.c4_soft.springaddons.security.oauth2.test.annotations.parameterized.ParameterizedAuthentication;
@WebMvcTest(controllers = GreetingController.class, properties = { "server.ssl.enabled=false" })
class SpringAddonsGreetingControllerUnitTest {
@@ -40,9 +43,11 @@ class SpringAddonsGreetingControllerUnitTest {
.andExpect(status().isUnauthorized());
}
- @Test
- @WithMockJwtAuth(authorities = { "admin", "ROLE_AUTHORIZED_PERSONNEL" }, claims = @OpenIdClaims(preferredUsername = "ch4mpy"))
- void givenUserIsAuthenticated_whenGetGreet_thenOk() throws Exception {
+ @ParameterizedTest
+ @AuthenticationSource({
+ @WithMockAuthentication(authorities = { "admin", "ROLE_AUTHORIZED_PERSONNEL" }, name = "ch4mpy"),
+ @WithMockAuthentication(authorities = { "uncle", "PIRATE" }, name = "tonton-pirate") })
+ void givenUserIsAuthenticated_whenGetGreet_thenOk(@ParameterizedAuthentication Authentication auth) throws Exception {
final var greeting = "Whatever the service returns";
when(messageService.greet()).thenReturn(greeting);
@@ -66,7 +71,7 @@ class SpringAddonsGreetingControllerUnitTest {
}
@Test
- @WithMockJwtAuth({ "admin", "ROLE_AUTHORIZED_PERSONNEL" })
+ @WithMockAuthentication({ "admin", "ROLE_AUTHORIZED_PERSONNEL" })
void givenUserIsGrantedWithRoleAuthorizedPersonnel_whenGetSecuredRoute_thenOk() throws Exception {
final var secret = "Secret!";
when(messageService.getSecret()).thenReturn(secret);
@@ -77,7 +82,7 @@ class SpringAddonsGreetingControllerUnitTest {
}
@Test
- @WithMockJwtAuth({ "admin" })
+ @WithMockAuthentication({ "admin" })
void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception {
api.perform(get("/secured-route"))
.andExpect(status().isForbidden());
@@ -96,7 +101,7 @@ class SpringAddonsGreetingControllerUnitTest {
}
@Test
- @WithMockJwtAuth({ "admin", "ROLE_AUTHORIZED_PERSONNEL" })
+ @WithMockAuthentication({ "admin", "ROLE_AUTHORIZED_PERSONNEL" })
void givenUserIsGrantedWithRoleAuthorizedPersonnel_whenGetSecuredMethod_thenOk() throws Exception {
final var secret = "Secret!";
when(messageService.getSecret()).thenReturn(secret);
@@ -107,7 +112,7 @@ class SpringAddonsGreetingControllerUnitTest {
}
@Test
- @WithMockJwtAuth(authorities = { "admin" })
+ @WithMockAuthentication({ "admin" })
void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() throws Exception {
api.perform(get("/secured-method"))
.andExpect(status().isForbidden());
diff --git a/spring-security-modules/spring-security-oauth2-testing/servlet-resource-server/src/test/resources/ch4mpy.json b/spring-security-modules/spring-security-oauth2-testing/servlet-resource-server/src/test/resources/ch4mpy.json
new file mode 100644
index 0000000000..22f7bb2cea
--- /dev/null
+++ b/spring-security-modules/spring-security-oauth2-testing/servlet-resource-server/src/test/resources/ch4mpy.json
@@ -0,0 +1,15 @@
+{
+ "iss": "https://localhost:8443/realms/master",
+ "sub": "281c4558-550c-413b-9972-2d2e5bde6b9b",
+ "iat": 1695992542,
+ "exp": 1695992642,
+ "preferred_username": "ch4mpy",
+ "realm_access": {
+ "roles": [
+ "admin",
+ "ROLE_AUTHORIZED_PERSONNEL"
+ ]
+ },
+ "email": "ch4mp@c4-soft.com",
+ "scope": "openid email"
+}
\ No newline at end of file
diff --git a/spring-security-modules/spring-security-oauth2-testing/servlet-resource-server/src/test/resources/tonton-pirate.json b/spring-security-modules/spring-security-oauth2-testing/servlet-resource-server/src/test/resources/tonton-pirate.json
new file mode 100644
index 0000000000..13a422f6fd
--- /dev/null
+++ b/spring-security-modules/spring-security-oauth2-testing/servlet-resource-server/src/test/resources/tonton-pirate.json
@@ -0,0 +1,15 @@
+{
+ "iss": "https://localhost:8443/realms/master",
+ "sub": "2d2e5bde6b9b-550c-413b-9972-281c4558",
+ "iat": 1695992551,
+ "exp": 1695992651,
+ "preferred_username": "tonton-pirate",
+ "realm_access": {
+ "roles": [
+ "uncle",
+ "PIRATE"
+ ]
+ },
+ "email": "tonton-pirate@c4-soft.com",
+ "scope": "openid email"
+}
\ No newline at end of file