Merge pull request #16456 from ch4mpy/BAEL-7704
BAEL-7704 : Update to latest spring-addons-oauth2-test
This commit is contained in:
commit
08d8efb5b5
|
@ -0,0 +1 @@
|
||||||
|
KEYCLOAK_ADMIN_PASSWORD=admin
|
|
@ -0,0 +1,23 @@
|
||||||
|
name: baeldung-testing-oauth2
|
||||||
|
services:
|
||||||
|
|
||||||
|
keycloak:
|
||||||
|
image: quay.io/keycloak/keycloak:24.0.0
|
||||||
|
volumes:
|
||||||
|
- ./keycloak/import/:/opt/keycloak/data/import/
|
||||||
|
command:
|
||||||
|
- start-dev
|
||||||
|
- --import-realm
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
environment:
|
||||||
|
KEYCLOAK_ADMIN: admin
|
||||||
|
KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
|
||||||
|
KC_HTTP_PORT: 8080
|
||||||
|
KC_HOSTNAME_URL: http://localhost:8080
|
||||||
|
KC_HOSTNAME_ADMIN_URL: http://localhost:8080
|
||||||
|
KC_HTTP_RELATIVE_PATH: /
|
||||||
|
#KC_LOG_LEVEL: DEBUG
|
||||||
|
container_name: baeldung-testing-oauth2
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,62 @@
|
||||||
|
{
|
||||||
|
"realm": "baeldung",
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"id": "bb83bd5b-b895-49aa-b62e-fde8ff8d5e64",
|
||||||
|
"username": "authorized",
|
||||||
|
"firstName": "authorized",
|
||||||
|
"lastName": "user",
|
||||||
|
"email": "authorized@baeldung.gg",
|
||||||
|
"emailVerified": true,
|
||||||
|
"createdTimestamp": 1713615702990,
|
||||||
|
"enabled": true,
|
||||||
|
"totp": false,
|
||||||
|
"credentials": [
|
||||||
|
{
|
||||||
|
"id": "23d3b6ce-fa3c-4f6e-9b09-e7b3cefa00f6",
|
||||||
|
"type": "password",
|
||||||
|
"userLabel": "My password",
|
||||||
|
"createdDate": 1713615715678,
|
||||||
|
"secretData": "{\"value\":\"lCZEOiVbCynSkCWiWe67/5/PybB+Om5mhi6SynpGFPB+r2b+QEKNYsB7ibO2f1ur9UB2aMO7jfoBYQdTJMcHbQ==\",\"salt\":\"hrIbqMOt0L7PPEYh/PsLNQ==\",\"additionalParameters\":{}}",
|
||||||
|
"credentialData": "{\"hashIterations\":210000,\"algorithm\":\"pbkdf2-sha512\",\"additionalParameters\":{}}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"disableableCredentialTypes": [],
|
||||||
|
"requiredActions": [],
|
||||||
|
"realmRoles": [
|
||||||
|
"AUTHORIZED_PERSONNEL",
|
||||||
|
"default-roles-baeldung"
|
||||||
|
],
|
||||||
|
"notBefore": 0,
|
||||||
|
"groups": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "d205d854-547c-43cf-a02d-fd6c385b128b",
|
||||||
|
"username": "forbidden",
|
||||||
|
"firstName": "forbidden",
|
||||||
|
"lastName": "user",
|
||||||
|
"email": "forbidden@baeldung.gg",
|
||||||
|
"emailVerified": true,
|
||||||
|
"createdTimestamp": 1713615781285,
|
||||||
|
"enabled": true,
|
||||||
|
"totp": false,
|
||||||
|
"credentials": [
|
||||||
|
{
|
||||||
|
"id": "2fa8244c-fba9-43d9-ab4d-80804ca26f6f",
|
||||||
|
"type": "password",
|
||||||
|
"userLabel": "My password",
|
||||||
|
"createdDate": 1713615795357,
|
||||||
|
"secretData": "{\"value\":\"sOUpOdi8yf31YbMEub+tAF7N55QHjrPjg48mO3/C1zGaAxLUYdLodf4upuy6w7eBuRrFaVa1m4mRe6wkWtmLIw==\",\"salt\":\"OMbZn6ojNyZdp6/k/72B/A==\",\"additionalParameters\":{}}",
|
||||||
|
"credentialData": "{\"hashIterations\":210000,\"algorithm\":\"pbkdf2-sha512\",\"additionalParameters\":{}}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"disableableCredentialTypes": [],
|
||||||
|
"requiredActions": [],
|
||||||
|
"realmRoles": [
|
||||||
|
"default-roles-baeldung"
|
||||||
|
],
|
||||||
|
"notBefore": 0,
|
||||||
|
"groups": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -31,7 +31,7 @@
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<spring-addons.version>7.1.10</spring-addons.version>
|
<spring-addons.version>7.6.12</spring-addons.version>
|
||||||
<maven.compiler.release>17</maven.compiler.release>
|
<maven.compiler.release>17</maven.compiler.release>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
<groupId>com.baeldung</groupId>
|
<groupId>com.baeldung</groupId>
|
||||||
<artifactId>spring-security-oauth2-testing</artifactId>
|
<artifactId>spring-security-oauth2-testing</artifactId>
|
||||||
<version>0.0.1-SNAPSHOT</version>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<relativePath>..</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package com.baeldung;
|
package com.baeldung;
|
||||||
|
|
||||||
import static org.springframework.security.config.Customizer.withDefaults;
|
|
||||||
|
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -17,6 +15,7 @@ import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||||
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
|
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
|
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
|
||||||
|
@ -52,22 +51,22 @@ public class ReactiveResourceServerApplication {
|
||||||
@EnableReactiveMethodSecurity
|
@EnableReactiveMethodSecurity
|
||||||
static class SecurityConfig {
|
static class SecurityConfig {
|
||||||
@Bean
|
@Bean
|
||||||
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
|
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, Converter<Jwt, Mono<AbstractAuthenticationToken>> authenticationConverter) {
|
||||||
http.oauth2ResourceServer(resourceServer -> resourceServer.jwt(withDefaults()));
|
http.oauth2ResourceServer(resourceServer -> resourceServer.jwt(jwtResourceServer -> jwtResourceServer.jwtAuthenticationConverter(authenticationConverter)));
|
||||||
http.securityContextRepository(NoOpServerSecurityContextRepository.getInstance());
|
http.securityContextRepository(NoOpServerSecurityContextRepository.getInstance());
|
||||||
http.csrf(CsrfSpec::disable);
|
http.csrf(CsrfSpec::disable);
|
||||||
http.exceptionHandling(eh -> eh
|
http.exceptionHandling(eh -> eh.accessDeniedHandler((var exchange, var ex) -> exchange.getPrincipal()
|
||||||
.accessDeniedHandler((var exchange, var ex) -> exchange.getPrincipal().flatMap(principal -> {
|
.flatMap(principal -> {
|
||||||
final var response = exchange.getResponse();
|
final var response = exchange.getResponse();
|
||||||
response.setStatusCode(
|
response.setStatusCode(principal instanceof AnonymousAuthenticationToken ? HttpStatus.UNAUTHORIZED : HttpStatus.FORBIDDEN);
|
||||||
principal instanceof AnonymousAuthenticationToken ? HttpStatus.UNAUTHORIZED
|
response.getHeaders()
|
||||||
: HttpStatus.FORBIDDEN);
|
.setContentType(MediaType.TEXT_PLAIN);
|
||||||
response.getHeaders().setContentType(MediaType.TEXT_PLAIN);
|
final var dataBufferFactory = response.bufferFactory();
|
||||||
final var dataBufferFactory = response.bufferFactory();
|
final var buffer = dataBufferFactory.wrap(ex.getMessage()
|
||||||
final var buffer = dataBufferFactory.wrap(ex.getMessage().getBytes(Charset.defaultCharset()));
|
.getBytes(Charset.defaultCharset()));
|
||||||
return response.writeWith(Mono.just(buffer))
|
return response.writeWith(Mono.just(buffer))
|
||||||
.doOnError(error -> DataBufferUtils.release(buffer));
|
.doOnError(error -> DataBufferUtils.release(buffer));
|
||||||
})));
|
})));
|
||||||
|
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
http.authorizeExchange(req -> req
|
http.authorizeExchange(req -> req
|
||||||
|
@ -84,17 +83,18 @@ public class ReactiveResourceServerApplication {
|
||||||
@Bean
|
@Bean
|
||||||
ReactiveJwtAuthoritiesConverter realmRoles2AuthoritiesConverter() {
|
ReactiveJwtAuthoritiesConverter realmRoles2AuthoritiesConverter() {
|
||||||
return (Jwt jwt) -> {
|
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")
|
@SuppressWarnings("unchecked")
|
||||||
final var roles = (List<String>) realmRoles.getOrDefault("roles", List.of());
|
final var roles = (List<String>) realmRoles.getOrDefault("roles", List.of());
|
||||||
return Flux.fromStream(roles.stream()).map(SimpleGrantedAuthority::new)
|
return Flux.fromStream(roles.stream())
|
||||||
.map(GrantedAuthority.class::cast);
|
.map(SimpleGrantedAuthority::new)
|
||||||
|
.map(GrantedAuthority.class::cast);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
ReactiveJwtAuthenticationConverter authenticationConverter(
|
ReactiveJwtAuthenticationConverter authenticationConverter(Converter<Jwt, Flux<GrantedAuthority>> authoritiesConverter) {
|
||||||
Converter<Jwt, Flux<GrantedAuthority>> authoritiesConverter) {
|
|
||||||
final var authenticationConverter = new ReactiveJwtAuthenticationConverter();
|
final var authenticationConverter = new ReactiveJwtAuthenticationConverter();
|
||||||
authenticationConverter.setPrincipalClaimName(StandardClaimNames.PREFERRED_USERNAME);
|
authenticationConverter.setPrincipalClaimName(StandardClaimNames.PREFERRED_USERNAME);
|
||||||
authenticationConverter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);
|
authenticationConverter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);
|
||||||
|
@ -106,10 +106,12 @@ public class ReactiveResourceServerApplication {
|
||||||
public static class MessageService {
|
public static class MessageService {
|
||||||
|
|
||||||
public Mono<String> greet() {
|
public Mono<String> greet() {
|
||||||
return ReactiveSecurityContextHolder.getContext().map(ctx -> {
|
return ReactiveSecurityContextHolder.getContext()
|
||||||
final var who = (JwtAuthenticationToken) ctx.getAuthentication();
|
.map(ctx -> {
|
||||||
return "Hello %s! You are granted with %s.".formatted(who.getName(), who.getAuthorities());
|
final var who = (JwtAuthenticationToken) ctx.getAuthentication();
|
||||||
}).switchIfEmpty(Mono.error(new AuthenticationCredentialsNotFoundException("Security context is empty")));
|
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')")
|
@PreAuthorize("hasRole('AUTHORIZED_PERSONNEL')")
|
||||||
|
@ -125,18 +127,21 @@ public class ReactiveResourceServerApplication {
|
||||||
|
|
||||||
@GetMapping("/greet")
|
@GetMapping("/greet")
|
||||||
public Mono<ResponseEntity<String>> greet() {
|
public Mono<ResponseEntity<String>> greet() {
|
||||||
return messageService.greet().map(ResponseEntity::ok);
|
return messageService.greet()
|
||||||
|
.map(ResponseEntity::ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/secured-route")
|
@GetMapping("/secured-route")
|
||||||
public Mono<ResponseEntity<String>> securedRoute() {
|
public Mono<ResponseEntity<String>> securedRoute() {
|
||||||
return messageService.getSecret().map(ResponseEntity::ok);
|
return messageService.getSecret()
|
||||||
|
.map(ResponseEntity::ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/secured-method")
|
@GetMapping("/secured-method")
|
||||||
@PreAuthorize("hasRole('AUTHORIZED_PERSONNEL')")
|
@PreAuthorize("hasRole('AUTHORIZED_PERSONNEL')")
|
||||||
public Mono<ResponseEntity<String>> securedMethod() {
|
public Mono<ResponseEntity<String>> securedMethod() {
|
||||||
return messageService.getSecret().map(ResponseEntity::ok);
|
return messageService.getSecret()
|
||||||
|
.map(ResponseEntity::ok);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
spring:
|
|
||||||
security:
|
|
||||||
oauth2:
|
|
||||||
resourceserver:
|
|
||||||
jwt:
|
|
||||||
issuer-uri: https://localhost:8443/realms/master
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
server:
|
||||||
|
port: 8082
|
||||||
|
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: reactive-resource-server
|
||||||
|
security:
|
||||||
|
oauth2:
|
||||||
|
resourceserver:
|
||||||
|
jwt:
|
||||||
|
issuer-uri: http://localhost:8080/realms/baeldung
|
|
@ -0,0 +1,16 @@
|
||||||
|
${AnsiColor.GREEN} _____ _ _
|
||||||
|
${AnsiColor.GREEN} | __ \ | | (_)
|
||||||
|
${AnsiColor.GREEN} | |__) |___ __ _ ___| |_ ___ _____ _ __ ___ ___ ___ _ _ _ __ ___ ___ ___ ___ _ ____ _____ _ __
|
||||||
|
${AnsiColor.GREEN} | _ // _ \/ _` |/ __| __| \ \ / / _ \ | '__/ _ \/ __|/ _ \| | | | '__/ __/ _ \ / __|/ _ \ '__\ \ / / _ \ '__|
|
||||||
|
${AnsiColor.GREEN} | | \ \ __/ (_| | (__| |_| |\ V / __/ | | | __/\__ \ (_) | |_| | | | (_| __/ \__ \ __/ | \ V / __/ |
|
||||||
|
${AnsiColor.GREEN} |_| \_\___|\__,_|\___|\__|_| \_/ \___| |_| \___||___/\___/ \__,_|_| \___\___| |___/\___|_| \_/ \___|_|
|
||||||
|
${AnsiColor.GREEN}
|
||||||
|
${AnsiColor.GREEN}
|
||||||
|
${AnsiColor.BLUE} __ __ __ ______ __ __ ______
|
||||||
|
${AnsiColor.BLUE} _____/ /_ / // / ____ ___ ____ / ____ \_____/ // / _________ / __/ /_ _________ ____ ___
|
||||||
|
${AnsiColor.BLUE} / ___/ __ \/ // /_/ __ `__ \/ __ \/ / __ `/ ___/ // /_______/ ___/ __ \/ /_/ __// ___/ __ \/ __ `__ \
|
||||||
|
${AnsiColor.BLUE}/ /__/ / / /__ __/ / / / / / /_/ / / /_/ / /__/__ __/_____(__ ) /_/ / __/ /__/ /__/ /_/ / / / / / /
|
||||||
|
${AnsiColor.BLUE}\___/_/ /_/ /_/ /_/ /_/ /_/ .___/\ \__,_/\___/ /_/ /____/\____/_/ \__(_)___/\____/_/ /_/ /_/
|
||||||
|
${AnsiColor.BLUE} /_/ \____/
|
||||||
|
${AnsiColor.RED}Spring Boot ${spring-boot.formatted-version}
|
||||||
|
${AnsiColor.BLACK}
|
|
@ -3,8 +3,6 @@ package com.baeldung;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
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 java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -16,6 +14,7 @@ import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||||
import org.springframework.security.core.authority.AuthorityUtils;
|
import org.springframework.security.core.authority.AuthorityUtils;
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
|
import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
|
||||||
|
import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers;
|
||||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||||
|
|
||||||
import com.baeldung.ReactiveResourceServerApplication.GreetingController;
|
import com.baeldung.ReactiveResourceServerApplication.GreetingController;
|
||||||
|
@ -40,7 +39,7 @@ class SpringSecurityTestGreetingControllerUnitTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void givenRequestIsAnonymous_whenGetGreet_thenUnauthorized() {
|
void givenRequestIsAnonymous_whenGetGreet_thenUnauthorized() {
|
||||||
api.mutateWith(mockAuthentication(ANONYMOUS_AUTHENTICATION))
|
api.mutateWith(SecurityMockServerConfigurers.mockAuthentication(ANONYMOUS_AUTHENTICATION))
|
||||||
.get()
|
.get()
|
||||||
.uri("/greet")
|
.uri("/greet")
|
||||||
.exchange()
|
.exchange()
|
||||||
|
@ -53,7 +52,8 @@ class SpringSecurityTestGreetingControllerUnitTest {
|
||||||
final var greeting = "Whatever the service returns";
|
final var greeting = "Whatever the service returns";
|
||||||
when(messageService.greet()).thenReturn(Mono.just(greeting));
|
when(messageService.greet()).thenReturn(Mono.just(greeting));
|
||||||
|
|
||||||
api.mutateWith(mockJwt().authorities(List.of(new SimpleGrantedAuthority("admin"), new SimpleGrantedAuthority("ROLE_AUTHORIZED_PERSONNEL")))
|
api.mutateWith(SecurityMockServerConfigurers.mockJwt()
|
||||||
|
.authorities(List.of(new SimpleGrantedAuthority("admin"), new SimpleGrantedAuthority("ROLE_AUTHORIZED_PERSONNEL")))
|
||||||
.jwt(jwt -> jwt.claim(StandardClaimNames.PREFERRED_USERNAME, "ch4mpy")))
|
.jwt(jwt -> jwt.claim(StandardClaimNames.PREFERRED_USERNAME, "ch4mpy")))
|
||||||
.get()
|
.get()
|
||||||
.uri("/greet")
|
.uri("/greet")
|
||||||
|
@ -73,7 +73,7 @@ class SpringSecurityTestGreetingControllerUnitTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void givenRequestIsAnonymous_whenGetSecuredRoute_thenUnauthorized() {
|
void givenRequestIsAnonymous_whenGetSecuredRoute_thenUnauthorized() {
|
||||||
api.mutateWith(mockAuthentication(ANONYMOUS_AUTHENTICATION))
|
api.mutateWith(SecurityMockServerConfigurers.mockAuthentication(ANONYMOUS_AUTHENTICATION))
|
||||||
.get()
|
.get()
|
||||||
.uri("/secured-route")
|
.uri("/secured-route")
|
||||||
.exchange()
|
.exchange()
|
||||||
|
@ -86,7 +86,8 @@ class SpringSecurityTestGreetingControllerUnitTest {
|
||||||
final var secret = "Secret!";
|
final var secret = "Secret!";
|
||||||
when(messageService.getSecret()).thenReturn(Mono.just(secret));
|
when(messageService.getSecret()).thenReturn(Mono.just(secret));
|
||||||
|
|
||||||
api.mutateWith(mockJwt().authorities(new SimpleGrantedAuthority("ROLE_AUTHORIZED_PERSONNEL")))
|
api.mutateWith(SecurityMockServerConfigurers.mockJwt()
|
||||||
|
.authorities(new SimpleGrantedAuthority("ROLE_AUTHORIZED_PERSONNEL")))
|
||||||
.get()
|
.get()
|
||||||
.uri("/secured-route")
|
.uri("/secured-route")
|
||||||
.exchange()
|
.exchange()
|
||||||
|
@ -98,7 +99,8 @@ class SpringSecurityTestGreetingControllerUnitTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() {
|
void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() {
|
||||||
api.mutateWith(mockJwt().authorities(new SimpleGrantedAuthority("admin")))
|
api.mutateWith(SecurityMockServerConfigurers.mockJwt()
|
||||||
|
.authorities(new SimpleGrantedAuthority("admin")))
|
||||||
.get()
|
.get()
|
||||||
.uri("/secured-route")
|
.uri("/secured-route")
|
||||||
.exchange()
|
.exchange()
|
||||||
|
@ -113,7 +115,7 @@ class SpringSecurityTestGreetingControllerUnitTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void givenRequestIsAnonymous_whenGetSecuredMethod_thenUnauthorized() {
|
void givenRequestIsAnonymous_whenGetSecuredMethod_thenUnauthorized() {
|
||||||
api.mutateWith(mockAuthentication(ANONYMOUS_AUTHENTICATION))
|
api.mutateWith(SecurityMockServerConfigurers.mockAuthentication(ANONYMOUS_AUTHENTICATION))
|
||||||
.get()
|
.get()
|
||||||
.uri("/secured-method")
|
.uri("/secured-method")
|
||||||
.exchange()
|
.exchange()
|
||||||
|
@ -126,7 +128,8 @@ class SpringSecurityTestGreetingControllerUnitTest {
|
||||||
final var secret = "Secret!";
|
final var secret = "Secret!";
|
||||||
when(messageService.getSecret()).thenReturn(Mono.just(secret));
|
when(messageService.getSecret()).thenReturn(Mono.just(secret));
|
||||||
|
|
||||||
api.mutateWith(mockJwt().authorities(new SimpleGrantedAuthority("ROLE_AUTHORIZED_PERSONNEL")))
|
api.mutateWith(SecurityMockServerConfigurers.mockJwt()
|
||||||
|
.authorities(new SimpleGrantedAuthority("ROLE_AUTHORIZED_PERSONNEL")))
|
||||||
.get()
|
.get()
|
||||||
.uri("/secured-method")
|
.uri("/secured-method")
|
||||||
.exchange()
|
.exchange()
|
||||||
|
@ -138,7 +141,8 @@ class SpringSecurityTestGreetingControllerUnitTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() {
|
void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() {
|
||||||
api.mutateWith(mockJwt().authorities(new SimpleGrantedAuthority("admin")))
|
api.mutateWith(SecurityMockServerConfigurers.mockJwt()
|
||||||
|
.authorities(new SimpleGrantedAuthority("admin")))
|
||||||
.get()
|
.get()
|
||||||
.uri("/secured-method")
|
.uri("/secured-method")
|
||||||
.exchange()
|
.exchange()
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
<groupId>com.baeldung</groupId>
|
<groupId>com.baeldung</groupId>
|
||||||
<artifactId>spring-security-oauth2-testing</artifactId>
|
<artifactId>spring-security-oauth2-testing</artifactId>
|
||||||
<version>0.0.1-SNAPSHOT</version>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<relativePath>..</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package com.baeldung;
|
package com.baeldung;
|
||||||
|
|
||||||
import static org.springframework.security.config.Customizer.withDefaults;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -16,6 +14,7 @@ import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
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.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
@ -48,8 +47,8 @@ public class ServletResourceServerApplication {
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
static class SecurityConf {
|
static class SecurityConf {
|
||||||
@Bean
|
@Bean
|
||||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
SecurityFilterChain filterChain(HttpSecurity http, Converter<Jwt, AbstractAuthenticationToken> authenticationConverter) throws Exception {
|
||||||
http.oauth2ResourceServer(resourceServer -> resourceServer.jwt(withDefaults()));
|
http.oauth2ResourceServer(resourceServer -> resourceServer.jwt(jwtResourceServer -> jwtResourceServer.jwtAuthenticationConverter(authenticationConverter)));
|
||||||
http.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
http.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
||||||
http.csrf(AbstractHttpConfigurer::disable);
|
http.csrf(AbstractHttpConfigurer::disable);
|
||||||
http.exceptionHandling(eh -> eh.authenticationEntryPoint((request, response, authException) -> {
|
http.exceptionHandling(eh -> eh.authenticationEntryPoint((request, response, authException) -> {
|
||||||
|
@ -57,11 +56,11 @@ public class ServletResourceServerApplication {
|
||||||
response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
|
response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
http.authorizeHttpRequests(req -> req
|
http.authorizeHttpRequests(req -> req
|
||||||
.requestMatchers(new AntPathRequestMatcher("/secured-route")).hasRole("AUTHORIZED_PERSONNEL")
|
.requestMatchers(new AntPathRequestMatcher("/secured-route")).hasRole("AUTHORIZED_PERSONNEL")
|
||||||
.anyRequest().authenticated());
|
.anyRequest().authenticated());
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
@ -72,10 +71,14 @@ public class ServletResourceServerApplication {
|
||||||
@Bean
|
@Bean
|
||||||
JwtAuthoritiesConverter realmRoles2AuthoritiesConverter() {
|
JwtAuthoritiesConverter realmRoles2AuthoritiesConverter() {
|
||||||
return (Jwt jwt) -> {
|
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")
|
@SuppressWarnings("unchecked")
|
||||||
final var roles = (List<String>) realmRoles.getOrDefault("roles", List.of());
|
final var roles = (List<String>) 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();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,7 +95,8 @@ public class ServletResourceServerApplication {
|
||||||
public static class MessageService {
|
public static class MessageService {
|
||||||
|
|
||||||
public String greet() {
|
public String greet() {
|
||||||
final var who = (JwtAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
|
final var who = (JwtAuthenticationToken) SecurityContextHolder.getContext()
|
||||||
|
.getAuthentication();
|
||||||
return "Hello %s! You are granted with %s.".formatted(who.getName(), who.getAuthorities());
|
return "Hello %s! You are granted with %s.".formatted(who.getName(), who.getAuthorities());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
spring.security.oauth2.resourceserver.jwt.issuer-uri=https://localhost:8443/realms/master
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
server:
|
||||||
|
port: 8081
|
||||||
|
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: servlet-resource-server
|
||||||
|
security:
|
||||||
|
oauth2:
|
||||||
|
resourceserver:
|
||||||
|
jwt:
|
||||||
|
issuer-uri: http://localhost:8080/realms/baeldung
|
|
@ -0,0 +1,16 @@
|
||||||
|
${AnsiColor.GREEN} _____ _ _
|
||||||
|
${AnsiColor.GREEN} / ____| | | | |
|
||||||
|
${AnsiColor.GREEN} | (___ ___ _ ____ _| | ___| |_ _ __ ___ ___ ___ _ _ _ __ ___ ___ ___ ___ _ ____ _____ _ __
|
||||||
|
${AnsiColor.GREEN} \___ \ / _ \ '__\ \ / / |/ _ \ __| | '__/ _ \/ __|/ _ \| | | | '__/ __/ _ \ / __|/ _ \ '__\ \ / / _ \ '__|
|
||||||
|
${AnsiColor.GREEN} ____) | __/ | \ V /| | __/ |_ | | | __/\__ \ (_) | |_| | | | (_| __/ \__ \ __/ | \ V / __/ |
|
||||||
|
${AnsiColor.GREEN} |_____/ \___|_| \_/ |_|\___|\__| |_| \___||___/\___/ \__,_|_| \___\___| |___/\___|_| \_/ \___|_|
|
||||||
|
${AnsiColor.GREEN}
|
||||||
|
${AnsiColor.GREEN}
|
||||||
|
${AnsiColor.BLUE} __ __ __ ______ __ __ ______
|
||||||
|
${AnsiColor.BLUE} _____/ /_ / // / ____ ___ ____ / ____ \_____/ // / _________ / __/ /_ _________ ____ ___
|
||||||
|
${AnsiColor.BLUE} / ___/ __ \/ // /_/ __ `__ \/ __ \/ / __ `/ ___/ // /_______/ ___/ __ \/ /_/ __// ___/ __ \/ __ `__ \
|
||||||
|
${AnsiColor.BLUE}/ /__/ / / /__ __/ / / / / / /_/ / / /_/ / /__/__ __/_____(__ ) /_/ / __/ /__/ /__/ /_/ / / / / / /
|
||||||
|
${AnsiColor.BLUE}\___/_/ /_/ /_/ /_/ /_/ /_/ .___/\ \__,_/\___/ /_/ /____/\____/_/ \__(_)___/\____/_/ /_/ /_/
|
||||||
|
${AnsiColor.BLUE} /_/ \____/
|
||||||
|
${AnsiColor.RED}Spring Boot ${spring-boot.formatted-version}
|
||||||
|
${AnsiColor.BLACK}
|
|
@ -44,9 +44,7 @@ class SpringAddonsGreetingControllerUnitTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@AuthenticationSource({
|
@AuthenticationSource({ @WithMockAuthentication(authorities = { "admin", "ROLE_AUTHORIZED_PERSONNEL" }, name = "ch4mpy"), @WithMockAuthentication(authorities = { "uncle", "PIRATE" }, name = "tonton-pirate") })
|
||||||
@WithMockAuthentication(authorities = { "admin", "ROLE_AUTHORIZED_PERSONNEL" }, name = "ch4mpy"),
|
|
||||||
@WithMockAuthentication(authorities = { "uncle", "PIRATE" }, name = "tonton-pirate") })
|
|
||||||
void givenUserIsAuthenticated_whenGetGreet_thenOk(@ParameterizedAuthentication Authentication auth) throws Exception {
|
void givenUserIsAuthenticated_whenGetGreet_thenOk(@ParameterizedAuthentication Authentication auth) throws Exception {
|
||||||
final var greeting = "Whatever the service returns";
|
final var greeting = "Whatever the service returns";
|
||||||
when(messageService.greet()).thenReturn(greeting);
|
when(messageService.greet()).thenReturn(greeting);
|
||||||
|
|
|
@ -3,8 +3,6 @@ package com.baeldung;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
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.request.MockMvcRequestBuilders.get;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
@ -17,6 +15,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
|
import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
|
||||||
|
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors;
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
|
|
||||||
import com.baeldung.ServletResourceServerApplication.GreetingController;
|
import com.baeldung.ServletResourceServerApplication.GreetingController;
|
||||||
|
@ -38,7 +37,7 @@ class SpringSecurityTestGreetingControllerUnitTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void givenRequestIsAnonymous_whenGetGreet_thenUnauthorized() throws Exception {
|
void givenRequestIsAnonymous_whenGetGreet_thenUnauthorized() throws Exception {
|
||||||
api.perform(get("/greet").with(anonymous()))
|
api.perform(get("/greet").with(SecurityMockMvcRequestPostProcessors.anonymous()))
|
||||||
.andExpect(status().isUnauthorized());
|
.andExpect(status().isUnauthorized());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +46,8 @@ class SpringSecurityTestGreetingControllerUnitTest {
|
||||||
final var greeting = "Whatever the service returns";
|
final var greeting = "Whatever the service returns";
|
||||||
when(messageService.greet()).thenReturn(greeting);
|
when(messageService.greet()).thenReturn(greeting);
|
||||||
|
|
||||||
api.perform(get("/greet").with(jwt().authorities(List.of(new SimpleGrantedAuthority("admin"), new SimpleGrantedAuthority("ROLE_AUTHORIZED_PERSONNEL")))
|
api.perform(get("/greet").with(SecurityMockMvcRequestPostProcessors.jwt()
|
||||||
|
.authorities(List.of(new SimpleGrantedAuthority("admin"), new SimpleGrantedAuthority("ROLE_AUTHORIZED_PERSONNEL")))
|
||||||
.jwt(jwt -> jwt.claim(StandardClaimNames.PREFERRED_USERNAME, "ch4mpy"))))
|
.jwt(jwt -> jwt.claim(StandardClaimNames.PREFERRED_USERNAME, "ch4mpy"))))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(content().string(greeting));
|
.andExpect(content().string(greeting));
|
||||||
|
@ -62,7 +62,7 @@ class SpringSecurityTestGreetingControllerUnitTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void givenRequestIsAnonymous_whenGetSecuredRoute_thenUnauthorized() throws Exception {
|
void givenRequestIsAnonymous_whenGetSecuredRoute_thenUnauthorized() throws Exception {
|
||||||
api.perform(get("/secured-route").with(anonymous()))
|
api.perform(get("/secured-route").with(SecurityMockMvcRequestPostProcessors.anonymous()))
|
||||||
.andExpect(status().isUnauthorized());
|
.andExpect(status().isUnauthorized());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,14 +71,16 @@ class SpringSecurityTestGreetingControllerUnitTest {
|
||||||
final var secret = "Secret!";
|
final var secret = "Secret!";
|
||||||
when(messageService.getSecret()).thenReturn(secret);
|
when(messageService.getSecret()).thenReturn(secret);
|
||||||
|
|
||||||
api.perform(get("/secured-route").with(jwt().authorities(new SimpleGrantedAuthority("ROLE_AUTHORIZED_PERSONNEL"))))
|
api.perform(get("/secured-route").with(SecurityMockMvcRequestPostProcessors.jwt()
|
||||||
|
.authorities(new SimpleGrantedAuthority("ROLE_AUTHORIZED_PERSONNEL"))))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(content().string(secret));
|
.andExpect(content().string(secret));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception {
|
void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecuredRoute_thenForbidden() throws Exception {
|
||||||
api.perform(get("/secured-route").with(jwt().authorities(new SimpleGrantedAuthority("admin"))))
|
api.perform(get("/secured-route").with(SecurityMockMvcRequestPostProcessors.jwt()
|
||||||
|
.authorities(new SimpleGrantedAuthority("admin"))))
|
||||||
.andExpect(status().isForbidden());
|
.andExpect(status().isForbidden());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,7 +91,7 @@ class SpringSecurityTestGreetingControllerUnitTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void givenRequestIsAnonymous_whenGetSecuredMethod_thenUnauthorized() throws Exception {
|
void givenRequestIsAnonymous_whenGetSecuredMethod_thenUnauthorized() throws Exception {
|
||||||
api.perform(get("/secured-method").with(anonymous()))
|
api.perform(get("/secured-method").with(SecurityMockMvcRequestPostProcessors.anonymous()))
|
||||||
.andExpect(status().isUnauthorized());
|
.andExpect(status().isUnauthorized());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,14 +100,16 @@ class SpringSecurityTestGreetingControllerUnitTest {
|
||||||
final var secret = "Secret!";
|
final var secret = "Secret!";
|
||||||
when(messageService.getSecret()).thenReturn(secret);
|
when(messageService.getSecret()).thenReturn(secret);
|
||||||
|
|
||||||
api.perform(get("/secured-method").with(jwt().authorities(new SimpleGrantedAuthority("ROLE_AUTHORIZED_PERSONNEL"))))
|
api.perform(get("/secured-method").with(SecurityMockMvcRequestPostProcessors.jwt()
|
||||||
|
.authorities(new SimpleGrantedAuthority("ROLE_AUTHORIZED_PERSONNEL"))))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(content().string(secret));
|
.andExpect(content().string(secret));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() throws Exception {
|
void givenUserIsNotGrantedWithRoleAuthorizedPersonnel_whenGetSecuredMethod_thenForbidden() throws Exception {
|
||||||
api.perform(get("/secured-method").with(jwt().authorities(new SimpleGrantedAuthority("admin"))))
|
api.perform(get("/secured-method").with(SecurityMockMvcRequestPostProcessors.jwt()
|
||||||
|
.authorities(new SimpleGrantedAuthority("admin"))))
|
||||||
.andExpect(status().isForbidden());
|
.andExpect(status().isForbidden());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue