From 1104a02c853bda8e08a616b2b6a7f96c1b1e8f4b Mon Sep 17 00:00:00 2001
From: ch4mpy
Date: Tue, 16 May 2023 09:33:52 -1000
Subject: [PATCH] Format code
---
.../tutorials/GreetingController.java | 25 +-
...ceServerMultitenantDynamicApplication.java | 16 +-
.../tutorials/GreetingControllerTest.java | 71 +-
.../tutorials/GreetingController.java | 16 +-
...eServerWithOAuthenticationApplication.java | 6 +-
.../tutorials/WebSecurityConfig.java | 63 +-
.../tutorials/GreetingControllerTest.java | 24 +-
...erWithOAuthenticationApplicationTests.java | 24 +-
.../tutorials/GreetingController.java | 25 +-
...eServerWithOAuthenticationApplication.java | 53 +-
.../tutorials/GreetingControllerTest.java | 71 +-
...erWithOAuthenticationApplicationTests.java | 137 +-
.../README.md | 2 +-
.../tutorials/ProxiesAuthentication.java | 38 +-
.../tutorials/ProxiesClaimSet.java | 5 +-
.../tutorials/SecurityConfig.java | 57 +-
.../tutorials/WebClientConfig.java | 27 +-
.../tutorials/WebSecurityConfig.java | 2 +-
.../MessageService.java | 9 +-
.../SecurityConfig.java | 8 +-
.../GreetingControllerAnnotatedTest.java | 107 +-
.../GreetingControllerFluentApiTest.java | 12 +-
.../MessageServiceTests.java | 9 +-
.../SecretRepoTest.java | 53 +-
.../MessageService.java | 9 +-
.../SecurityConfig.java | 31 +-
.../GreetingControllerAnnotatedTest.java | 9 +-
.../GreetingControllerFluentApiTest.java | 9 +-
.../MessageServiceTests.java | 9 +-
.../SecretRepoTest.java | 9 +-
.../MessageService.java | 9 +-
.../SecurityConfig.java | 8 +-
.../GreetingControllerAnnotatedTest.java | 116 +-
.../GreetingControllerFluentApiTest.java | 77 +-
.../MessageServiceTests.java | 9 +-
.../SecretRepoTest.java | 9 +-
.../MessageService.java | 9 +-
.../SecurityConfig.java | 31 +-
.../GreetingControllerAnnotatedTest.java | 102 +-
.../MessageServiceTests.java | 9 +-
.../SampleApiIntegrationTests.java | 35 +-
.../SecretRepoTest.java | 9 +-
.../MessageService.java | 9 +-
.../SecurityConfig.java | 8 +-
.../GreetingControllerAnnotatedTest.java | 131 +-
.../GreetingControllerFluentApiTest.java | 88 +-
.../MessageServiceTests.java | 9 +-
.../SecretRepoTest.java | 9 +-
.../MessageService.java | 9 +-
.../SecurityConfig.java | 27 +-
.../GreetingControllerAnnotatedTest.java | 88 +-
.../GreetingControllerFluentApiTest.java | 9 +-
.../MessageServiceTests.java | 9 +-
.../SecretRepoTest.java | 9 +-
.../MessageService.java | 18 +-
.../PersistedGrantedAuthoritiesRetriever.java | 9 +-
.../SecurityConfig.java | 16 +-
.../UserAuthority.java | 9 +-
.../UserAuthorityId.java | 9 +-
.../UserAuthorityRepository.java | 9 +-
.../GreetingControllerAnnotatedTest.java | 133 +-
.../GreetingControllerFluentApiTest.java | 9 +-
.../MessageServiceTests.java | 96 +-
.../GreetingController.java | 46 +-
.../MessageService.java | 9 +-
.../OAuth2SecurityConfig.java | 8 +-
.../WebmvcJwtDefault.java | 6 +-
.../GreetingControllerAnnotatedTest.java | 122 +-
.../GreetingControllerFluentApiTest.java | 9 +-
.../MessageServiceTests.java | 9 +-
.../SecretRepoTest.java | 9 +-
.../MessageService.java | 9 +-
.../SecurityConfig.java | 27 +-
.../GreetingControllerAnnotatedTest.java | 9 +-
.../GreetingControllerFluentApiTest.java | 9 +-
.../MessageServiceTests.java | 9 +-
.../SecretRepoTest.java | 9 +-
...AuthoritiesConverterNotAMockException.java | 14 +-
.../security/oauth2/test/Defaults.java | 9 +-
.../test/OAuthenticationTestingBuilder.java | 9 +-
.../oauth2/test/OpenidClaimSetBuilder.java | 465 ++++---
.../oauth2/AuthenticationBuilder.java | 21 +-
.../security/oauth2/ClaimSet.java | 11 +-
.../security/oauth2/DelegatingMap.java | 23 +-
.../security/oauth2/ModifiableClaimSet.java | 43 +-
.../security/oauth2/OAuthentication.java | 137 +-
.../security/oauth2/UnmodifiableClaimSet.java | 9 +-
.../web/ByteArrayHttpOutputMessage.java | 14 +-
.../test/support/web/SerializationHelper.java | 26 +-
.../C4ReCaptchaValidationService.java | 19 +-
.../starter/webclient/C4ProxySettings.java | 8 +-
.../C4WebClientBuilderFactoryService.java | 18 +-
.../SpringAddonsBackChannelLogoutBeans.java | 197 ++-
...ddonsOAuth2ServerLogoutSuccessHandler.java | 57 +-
...erverOAuth2AuthorizedClientRepository.java | 454 +++----
...entAuthorizeExchangeSpecPostProcessor.java | 2 +-
.../ClientHttpSecurityPostProcessor.java | 2 +-
.../ReactiveConfigurationSupport.java | 218 ++-
...verAuthorizeExchangeSpecPostProcessor.java | 1 -
...sourceServerHttpSecurityPostProcessor.java | 7 +-
.../reactive/ServerHttpRequestSupport.java | 120 +-
.../config/reactive/AddonsSecurityBeans.java | 25 +-
.../AutoConfigureAddonsWebSecurity.java | 4 +-
.../config/reactive/AddonsSecurityBeans.java | 25 +-
.../jwt/AutoConfigureAddonsWebSecurity.java | 4 +-
.../test/webflux/AddonsWebfluxTestConf.java | 284 ++--
.../webflux/AuthenticationConfigurer.java | 9 +-
...AuthenticationWebTestClientConfigurer.java | 9 +-
...nticationTokenWebTestClientConfigurer.java | 9 +-
.../test/webflux/WebTestClientSupport.java | 176 ++-
.../SpringAddonsBackChannelLogoutBeans.java | 197 ++-
...onsOAuth2AuthorizationRequestResolver.java | 61 +-
...ddonsOAuth2AuthorizedClientRepository.java | 431 +++---
.../SpringAddonsOAuth2ClientBeans.java | 444 +++---
...pringAddonsOAuth2LogoutSuccessHandler.java | 50 +-
...sionInterceptUrlRegistryPostProcessor.java | 1 -
.../ClientHttpSecurityPostProcessor.java | 4 +-
...sionInterceptUrlRegistryPostProcessor.java | 5 +-
.../HttpServletRequestSupport.java | 171 ++-
.../OAuth2AuthenticationFactory.java | 2 +-
...sionInterceptUrlRegistryPostProcessor.java | 3 +-
...sourceServerHttpSecurityPostProcessor.java | 6 +-
.../ServerHttpSecurityPostProcessor.java | 2 +-
.../ServletConfigurationSupport.java | 263 ++--
.../synchronised/AddonsSecurityBeans.java | 26 +-
.../synchronised/AddonsWebSecurityBeans.java | 278 ++--
.../AutoConfigureAddonsWebSecurity.java | 4 +-
.../synchronised/AddonsSecurityBeans.java | 25 +-
.../synchronised/AddonsWebSecurityBeans.java | 338 ++---
.../jwt/AutoConfigureAddonsWebSecurity.java | 9 +-
.../test/mockmvc/AddonsWebmvcTestConf.java | 274 ++--
.../AuthenticationRequestPostProcessor.java | 21 +-
...ockAuthenticationRequestPostProcessor.java | 9 +-
.../oauth2/test/mockmvc/MockMvcSupport.java | 1185 ++++++++---------
...thenticationTokenRequestPostProcessor.java | 9 +-
.../mockmvc/ServletUnitTestingSupport.java | 18 +-
136 files changed, 4051 insertions(+), 4576 deletions(-)
diff --git a/samples/tutorials/resource-server_multitenant_dynamic/src/main/java/com/c4soft/springaddons/tutorials/GreetingController.java b/samples/tutorials/resource-server_multitenant_dynamic/src/main/java/com/c4soft/springaddons/tutorials/GreetingController.java
index 9e3635041..f43f8ada7 100644
--- a/samples/tutorials/resource-server_multitenant_dynamic/src/main/java/com/c4soft/springaddons/tutorials/GreetingController.java
+++ b/samples/tutorials/resource-server_multitenant_dynamic/src/main/java/com/c4soft/springaddons/tutorials/GreetingController.java
@@ -11,19 +11,18 @@
@PreAuthorize("isAuthenticated()")
public class GreetingController {
- @GetMapping("/greet")
- public MessageDto getGreeting(OAuthentication auth) {
- return new MessageDto("Hi %s! You are granted with: %s and your email is %s."
- .formatted(auth.getName(), auth.getAuthorities(), auth.getClaims().getEmail()));
- }
+ @GetMapping("/greet")
+ public MessageDto getGreeting(OAuthentication auth) {
+ return new MessageDto(
+ "Hi %s! You are granted with: %s and your email is %s.".formatted(auth.getName(), auth.getAuthorities(), auth.getClaims().getEmail()));
+ }
- @GetMapping("/nice")
- @PreAuthorize("hasAuthority('NICE')")
- public MessageDto getNiceGreeting(OAuthentication auth) {
- return new MessageDto("Dear %s! You are granted with: %s."
- .formatted(auth.getName(), auth.getAuthorities()));
- }
+ @GetMapping("/nice")
+ @PreAuthorize("hasAuthority('NICE')")
+ public MessageDto getNiceGreeting(OAuthentication auth) {
+ return new MessageDto("Dear %s! You are granted with: %s.".formatted(auth.getName(), auth.getAuthorities()));
+ }
- static record MessageDto(String body) {
- }
+ static record MessageDto(String body) {
+ }
}
diff --git a/samples/tutorials/resource-server_multitenant_dynamic/src/main/java/com/c4soft/springaddons/tutorials/ResourceServerMultitenantDynamicApplication.java b/samples/tutorials/resource-server_multitenant_dynamic/src/main/java/com/c4soft/springaddons/tutorials/ResourceServerMultitenantDynamicApplication.java
index 2dbe1285c..f5a094fa0 100644
--- a/samples/tutorials/resource-server_multitenant_dynamic/src/main/java/com/c4soft/springaddons/tutorials/ResourceServerMultitenantDynamicApplication.java
+++ b/samples/tutorials/resource-server_multitenant_dynamic/src/main/java/com/c4soft/springaddons/tutorials/ResourceServerMultitenantDynamicApplication.java
@@ -9,13 +9,19 @@
import io.swagger.v3.oas.annotations.security.OAuthScope;
import io.swagger.v3.oas.annotations.security.SecurityScheme;
-@SecurityScheme(name = "authorization-code", type = SecuritySchemeType.OAUTH2, flows = @OAuthFlows(authorizationCode = @OAuthFlow(authorizationUrl = "https://localhost:8443/realms/master/protocol/openid-connect/auth", tokenUrl = "https://localhost:8443/realms/master/protocol/openid-connect/token", scopes = {
- @OAuthScope(name = "openid"), @OAuthScope(name = "profile") })))
+@SecurityScheme(
+ name = "authorization-code",
+ type = SecuritySchemeType.OAUTH2,
+ flows = @OAuthFlows(
+ authorizationCode = @OAuthFlow(
+ authorizationUrl = "https://localhost:8443/realms/master/protocol/openid-connect/auth",
+ tokenUrl = "https://localhost:8443/realms/master/protocol/openid-connect/token",
+ scopes = { @OAuthScope(name = "openid"), @OAuthScope(name = "profile") })))
@SpringBootApplication
public class ResourceServerMultitenantDynamicApplication {
- public static void main(String[] args) {
- SpringApplication.run(ResourceServerMultitenantDynamicApplication.class, args);
- }
+ public static void main(String[] args) {
+ SpringApplication.run(ResourceServerMultitenantDynamicApplication.class, args);
+ }
}
diff --git a/samples/tutorials/resource-server_multitenant_dynamic/src/test/java/com/c4soft/springaddons/tutorials/GreetingControllerTest.java b/samples/tutorials/resource-server_multitenant_dynamic/src/test/java/com/c4soft/springaddons/tutorials/GreetingControllerTest.java
index 059a92361..7b5f10bd4 100644
--- a/samples/tutorials/resource-server_multitenant_dynamic/src/test/java/com/c4soft/springaddons/tutorials/GreetingControllerTest.java
+++ b/samples/tutorials/resource-server_multitenant_dynamic/src/test/java/com/c4soft/springaddons/tutorials/GreetingControllerTest.java
@@ -19,41 +19,40 @@
@Import(WebSecurityConfig.class)
class GreetingControllerTest {
- @Autowired
- MockMvcSupport api;
-
- @Test
- @OpenId(authorities = {
- "AUTHOR" }, claims = @OpenIdClaims(usernameClaim = StandardClaimNames.PREFERRED_USERNAME, preferredUsername = "Tonton Pirate", email = "ch4mp@c4-soft.com"))
- void givenUserIsAuthenticated_whenGreet_thenOk() throws Exception {
- api.get("/greet").andExpect(status().isOk())
- .andExpect(jsonPath("$.body").value(
- "Hi Tonton Pirate! You are granted with: [AUTHOR] and your email is ch4mp@c4-soft.com."));
- }
-
- @Test
- void givenRequestIsAnonymous_whenGreet_thenUnauthorized() throws Exception {
- api.get("/greet").andExpect(status().isUnauthorized());
- }
-
- @Test
- @OpenId(authorities = { "NICE",
- "AUTHOR" }, claims = @OpenIdClaims(usernameClaim = StandardClaimNames.PREFERRED_USERNAME, preferredUsername = "Tonton Pirate", email = "ch4mp@c4-soft.com"))
- void givenUserIsGrantedWithNice_whenGetNice_thenOk() throws Exception {
- api.get("/nice").andExpect(status().isOk())
- .andExpect(jsonPath("$.body").value(
- "Dear Tonton Pirate! You are granted with: [NICE, AUTHOR]."));
- }
-
- @Test
- @OpenId(authorities = { "AUTHOR" }, claims = @OpenIdClaims(preferredUsername = "Tonton Pirate"))
- void givenUserIsNotGrantedWithNice_whenGetNice_thenForbidden() throws Exception {
- api.get("/nice").andExpect(status().isForbidden());
- }
-
- @Test
- void givenRequestIsAnonymous_whenGetNice_thenUnauthorized() throws Exception {
- api.get("/nice").andExpect(status().isUnauthorized());
- }
+ @Autowired
+ MockMvcSupport api;
+
+ @Test
+ @OpenId(
+ authorities = { "AUTHOR" },
+ claims = @OpenIdClaims(usernameClaim = StandardClaimNames.PREFERRED_USERNAME, preferredUsername = "Tonton Pirate", email = "ch4mp@c4-soft.com"))
+ void givenUserIsAuthenticated_whenGreet_thenOk() throws Exception {
+ api.get("/greet").andExpect(status().isOk())
+ .andExpect(jsonPath("$.body").value("Hi Tonton Pirate! You are granted with: [AUTHOR] and your email is ch4mp@c4-soft.com."));
+ }
+
+ @Test
+ void givenRequestIsAnonymous_whenGreet_thenUnauthorized() throws Exception {
+ api.get("/greet").andExpect(status().isUnauthorized());
+ }
+
+ @Test
+ @OpenId(
+ authorities = { "NICE", "AUTHOR" },
+ claims = @OpenIdClaims(usernameClaim = StandardClaimNames.PREFERRED_USERNAME, preferredUsername = "Tonton Pirate", email = "ch4mp@c4-soft.com"))
+ void givenUserIsGrantedWithNice_whenGetNice_thenOk() throws Exception {
+ api.get("/nice").andExpect(status().isOk()).andExpect(jsonPath("$.body").value("Dear Tonton Pirate! You are granted with: [NICE, AUTHOR]."));
+ }
+
+ @Test
+ @OpenId(authorities = { "AUTHOR" }, claims = @OpenIdClaims(preferredUsername = "Tonton Pirate"))
+ void givenUserIsNotGrantedWithNice_whenGetNice_thenForbidden() throws Exception {
+ api.get("/nice").andExpect(status().isForbidden());
+ }
+
+ @Test
+ void givenRequestIsAnonymous_whenGetNice_thenUnauthorized() throws Exception {
+ api.get("/nice").andExpect(status().isUnauthorized());
+ }
}
diff --git a/samples/tutorials/resource-server_with_introspection/src/main/java/com/c4soft/springaddons/tutorials/GreetingController.java b/samples/tutorials/resource-server_with_introspection/src/main/java/com/c4soft/springaddons/tutorials/GreetingController.java
index c1c492323..55699ed83 100644
--- a/samples/tutorials/resource-server_with_introspection/src/main/java/com/c4soft/springaddons/tutorials/GreetingController.java
+++ b/samples/tutorials/resource-server_with_introspection/src/main/java/com/c4soft/springaddons/tutorials/GreetingController.java
@@ -10,14 +10,12 @@
@RequestMapping("/greet")
public class GreetingController {
- @GetMapping()
- @PreAuthorize("hasAuthority('NICE')")
- public MessageDto getGreeting(Authentication auth) {
- return new MessageDto("Hi %s! You are granted with: %s.".formatted(
- auth.getName(),
- auth.getAuthorities()));
- }
+ @GetMapping()
+ @PreAuthorize("hasAuthority('NICE')")
+ public MessageDto getGreeting(Authentication auth) {
+ return new MessageDto("Hi %s! You are granted with: %s.".formatted(auth.getName(), auth.getAuthorities()));
+ }
- static record MessageDto(String body) {
- }
+ static record MessageDto(String body) {
+ }
}
diff --git a/samples/tutorials/resource-server_with_introspection/src/main/java/com/c4soft/springaddons/tutorials/ResourceServerWithOAuthenticationApplication.java b/samples/tutorials/resource-server_with_introspection/src/main/java/com/c4soft/springaddons/tutorials/ResourceServerWithOAuthenticationApplication.java
index ebaf6792b..c7f9bf896 100644
--- a/samples/tutorials/resource-server_with_introspection/src/main/java/com/c4soft/springaddons/tutorials/ResourceServerWithOAuthenticationApplication.java
+++ b/samples/tutorials/resource-server_with_introspection/src/main/java/com/c4soft/springaddons/tutorials/ResourceServerWithOAuthenticationApplication.java
@@ -6,8 +6,8 @@
@SpringBootApplication
public class ResourceServerWithOAuthenticationApplication {
- public static void main(String[] args) {
- SpringApplication.run(ResourceServerWithOAuthenticationApplication.class, args);
- }
+ public static void main(String[] args) {
+ SpringApplication.run(ResourceServerWithOAuthenticationApplication.class, args);
+ }
}
diff --git a/samples/tutorials/resource-server_with_introspection/src/main/java/com/c4soft/springaddons/tutorials/WebSecurityConfig.java b/samples/tutorials/resource-server_with_introspection/src/main/java/com/c4soft/springaddons/tutorials/WebSecurityConfig.java
index 79426b545..29c10068a 100644
--- a/samples/tutorials/resource-server_with_introspection/src/main/java/com/c4soft/springaddons/tutorials/WebSecurityConfig.java
+++ b/samples/tutorials/resource-server_with_introspection/src/main/java/com/c4soft/springaddons/tutorials/WebSecurityConfig.java
@@ -30,41 +30,38 @@
@EnableMethodSecurity
public class WebSecurityConfig {
- @Bean
- @Profile("oauthentication")
- // This bean is optional as a default one is provided (building a
- // BearerAuthenticationToken)
- OpaqueTokenAuthenticationConverter introspectionAuthenticationConverter(
- Converter
*
- * It is mainly intended at parsing additional headers when authorizing
- * requests.
+ * It is mainly intended at parsing additional headers when authorizing requests.
*
*
* @author ch4mp
*/
public class ServerHttpRequestSupport {
- /**
- * @return the request in current context
- */
- public static Mono getRequest() {
- return Mono.deferContextual(Mono::just)
- .map(contextView -> contextView.get(ServerWebExchange.class).getRequest());
- }
+ /**
+ * @return the request in current context
+ */
+ public static Mono getRequest() {
+ return Mono.deferContextual(Mono::just).map(contextView -> contextView.get(ServerWebExchange.class).getRequest());
+ }
- public static Mono getSession() {
- return Mono.deferContextual(Mono::just)
- .flatMap(contextView -> contextView.get(ServerWebExchange.class).getSession());
- }
+ public static Mono getSession() {
+ return Mono.deferContextual(Mono::just).flatMap(contextView -> contextView.get(ServerWebExchange.class).getSession());
+ }
- /**
- * @param headerName name of the header to retrieve
- * @return the unique value for the given header in current request
- * @throws MissingHeaderException if no non-empty value is found for that
- * header
- * @throws MultiValuedHeaderException more than one non-empty value is found for
- * that header
- */
- public static Mono getUniqueHeader(String headerName)
- throws MissingHeaderException, MultiValuedHeaderException {
- try {
- return getNonEmptyHeaderValues(headerName).single();
- } catch (NoSuchElementException e) {
- throw new MissingHeaderException(headerName);
- } catch (IndexOutOfBoundsException e) {
- throw new MultiValuedHeaderException(headerName);
- }
+ /**
+ * @param headerName name of the header to retrieve
+ * @return the unique value for the given header in current request
+ * @throws MissingHeaderException if no non-empty value is found for that header
+ * @throws MultiValuedHeaderException more than one non-empty value is found for that header
+ */
+ public static Mono getUniqueHeader(String headerName) throws MissingHeaderException, MultiValuedHeaderException {
+ try {
+ return getNonEmptyHeaderValues(headerName).single();
+ } catch (NoSuchElementException e) {
+ throw new MissingHeaderException(headerName);
+ } catch (IndexOutOfBoundsException e) {
+ throw new MultiValuedHeaderException(headerName);
+ }
- }
+ }
- /**
- * @param headerName the name of the header to retrieve
- * @return a stream of non empty values for a given header from the request in
- * current context
- */
- public static Flux getNonEmptyHeaderValues(String headerName) {
- return getRequest().flatMapMany(req -> Flux
- .fromStream(req.getHeaders().getOrEmpty(headerName).stream().filter(StringUtils::hasLength)));
- }
+ /**
+ * @param headerName the name of the header to retrieve
+ * @return a stream of non empty values for a given header from the request in current context
+ */
+ public static Flux getNonEmptyHeaderValues(String headerName) {
+ return getRequest().flatMapMany(req -> Flux.fromStream(req.getHeaders().getOrEmpty(headerName).stream().filter(StringUtils::hasLength)));
+ }
- @ResponseStatus(code = HttpStatus.UNAUTHORIZED)
- public static class MissingHeaderException extends RuntimeException {
- private static final long serialVersionUID = -4894061353773464761L;
+ @ResponseStatus(code = HttpStatus.UNAUTHORIZED)
+ public static class MissingHeaderException extends RuntimeException {
+ private static final long serialVersionUID = -4894061353773464761L;
- public MissingHeaderException(String headerName) {
- super(headerName + " is missing");
- assert (StringUtils.hasText(headerName));
- }
- }
+ public MissingHeaderException(String headerName) {
+ super(headerName + " is missing");
+ assert (StringUtils.hasText(headerName));
+ }
+ }
- @ResponseStatus(code = HttpStatus.UNAUTHORIZED)
- public static class MultiValuedHeaderException extends RuntimeException {
- private static final long serialVersionUID = 1654993007508549674L;
+ @ResponseStatus(code = HttpStatus.UNAUTHORIZED)
+ public static class MultiValuedHeaderException extends RuntimeException {
+ private static final long serialVersionUID = 1654993007508549674L;
- public MultiValuedHeaderException(String headerName) {
- super(headerName + " is not unique");
- assert (StringUtils.hasText(headerName));
- }
- }
+ public MultiValuedHeaderException(String headerName) {
+ super(headerName + " is not unique");
+ assert (StringUtils.hasText(headerName));
+ }
+ }
- @ResponseStatus(code = HttpStatus.UNAUTHORIZED)
- public static class InvalidHeaderException extends RuntimeException {
- private static final long serialVersionUID = -6233252290377524340L;
+ @ResponseStatus(code = HttpStatus.UNAUTHORIZED)
+ public static class InvalidHeaderException extends RuntimeException {
+ private static final long serialVersionUID = -6233252290377524340L;
- public InvalidHeaderException(String headerName) {
- super(headerName + " is not valid");
- assert (StringUtils.hasText(headerName));
- }
- }
+ public InvalidHeaderException(String headerName) {
+ super(headerName + " is not valid");
+ assert (StringUtils.hasText(headerName));
+ }
+ }
}
\ No newline at end of file
diff --git a/webflux/spring-addons-webflux-introspecting-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/AddonsSecurityBeans.java b/webflux/spring-addons-webflux-introspecting-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/AddonsSecurityBeans.java
index cae61f717..9f5fb0677 100644
--- a/webflux/spring-addons-webflux-introspecting-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/AddonsSecurityBeans.java
+++ b/webflux/spring-addons-webflux-introspecting-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/AddonsSecurityBeans.java
@@ -19,17 +19,16 @@
@Import({ SpringAddonsSecurityProperties.class })
public class AddonsSecurityBeans {
- /**
- * Retrieves granted authorities from the introspected token attributes
- * according to the configuration set for issuer (iss attribute)
- *
- * @param securityProperties
- * @return
- */
- @ConditionalOnMissingBean
- @Bean
- OAuth2AuthoritiesConverter authoritiesConverter(SpringAddonsSecurityProperties addonsProperties) {
- log.debug("Building default CorsConfigurationSource with: {}", addonsProperties);
- return new ConfigurableClaimSet2AuthoritiesConverter(addonsProperties);
- }
+ /**
+ * Retrieves granted authorities from the introspected token attributes according to the configuration set for issuer (iss attribute)
+ *
+ * @param securityProperties
+ * @return
+ */
+ @ConditionalOnMissingBean
+ @Bean
+ OAuth2AuthoritiesConverter authoritiesConverter(SpringAddonsSecurityProperties addonsProperties) {
+ log.debug("Building default CorsConfigurationSource with: {}", addonsProperties);
+ return new ConfigurableClaimSet2AuthoritiesConverter(addonsProperties);
+ }
}
\ No newline at end of file
diff --git a/webflux/spring-addons-webflux-introspecting-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/introspecting/AutoConfigureAddonsWebSecurity.java b/webflux/spring-addons-webflux-introspecting-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/introspecting/AutoConfigureAddonsWebSecurity.java
index 0a9d8c629..7bf3655a8 100644
--- a/webflux/spring-addons-webflux-introspecting-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/introspecting/AutoConfigureAddonsWebSecurity.java
+++ b/webflux/spring-addons-webflux-introspecting-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/introspecting/AutoConfigureAddonsWebSecurity.java
@@ -13,8 +13,8 @@
/**
*
- * Auto-configures {@link AddonsSecurityBeans} and {@link AddonsWebSecurityBeans}. To be used to test controllers but not services or
- * repositories (web context is not desired in that case).
+ * Auto-configures {@link AddonsSecurityBeans} and {@link AddonsWebSecurityBeans}. To be used to test controllers but not services or repositories (web context
+ * is not desired in that case).
*
* See {@link AutoConfigureAddonsSecurity}
*
diff --git a/webflux/spring-addons-webflux-jwt-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/AddonsSecurityBeans.java b/webflux/spring-addons-webflux-jwt-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/AddonsSecurityBeans.java
index a7ecf787a..9556e9d39 100644
--- a/webflux/spring-addons-webflux-jwt-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/AddonsSecurityBeans.java
+++ b/webflux/spring-addons-webflux-jwt-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/AddonsSecurityBeans.java
@@ -19,17 +19,16 @@
@Import({ SpringAddonsSecurityProperties.class })
public class AddonsSecurityBeans {
- /**
- * Retrieves granted authorities from the Jwt (from its private claims or with
- * the help of an external service)
- *
- * @param securityProperties
- * @return
- */
- @ConditionalOnMissingBean
- @Bean
- OAuth2AuthoritiesConverter authoritiesConverter(SpringAddonsSecurityProperties addonsProperties) {
- log.debug("Building default CorsConfigurationSource with: {}", addonsProperties);
- return new ConfigurableClaimSet2AuthoritiesConverter(addonsProperties);
- }
+ /**
+ * Retrieves granted authorities from the Jwt (from its private claims or with the help of an external service)
+ *
+ * @param securityProperties
+ * @return
+ */
+ @ConditionalOnMissingBean
+ @Bean
+ OAuth2AuthoritiesConverter authoritiesConverter(SpringAddonsSecurityProperties addonsProperties) {
+ log.debug("Building default CorsConfigurationSource with: {}", addonsProperties);
+ return new ConfigurableClaimSet2AuthoritiesConverter(addonsProperties);
+ }
}
\ No newline at end of file
diff --git a/webflux/spring-addons-webflux-jwt-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/jwt/AutoConfigureAddonsWebSecurity.java b/webflux/spring-addons-webflux-jwt-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/jwt/AutoConfigureAddonsWebSecurity.java
index f827859ad..c89efa072 100644
--- a/webflux/spring-addons-webflux-jwt-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/jwt/AutoConfigureAddonsWebSecurity.java
+++ b/webflux/spring-addons-webflux-jwt-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/jwt/AutoConfigureAddonsWebSecurity.java
@@ -13,8 +13,8 @@
/**
*
- * Auto-configures {@link AddonsSecurityBeans} and {@link AddonsWebSecurityBeans}. To be used to test controllers but not services or
- * repositories (web context is not desired in that case).
+ * Auto-configures {@link AddonsSecurityBeans} and {@link AddonsWebSecurityBeans}. To be used to test controllers but not services or repositories (web context
+ * is not desired in that case).
*
* See {@link AutoConfigureAddonsSecurity}
*
diff --git a/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/AddonsWebfluxTestConf.java b/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/AddonsWebfluxTestConf.java
index e2674902a..15b2b627b 100644
--- a/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/AddonsWebfluxTestConf.java
+++ b/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/AddonsWebfluxTestConf.java
@@ -48,148 +48,146 @@
@Import({ WebTestClientProperties.class })
public class AddonsWebfluxTestConf {
- @MockBean
- ReactiveJwtDecoder jwtDecoder;
-
- @MockBean
- ReactiveAuthenticationManagerResolver jwtIssuerReactiveAuthenticationManagerResolver;
-
- @MockBean
- ReactiveOpaqueTokenIntrospector introspector;
-
- @ConditionalOnMissingBean
- @Bean
- InMemoryReactiveClientRegistrationRepository clientRegistrationRepository() {
- final var clientRegistrationRepository = mock(InMemoryReactiveClientRegistrationRepository.class);
- when(clientRegistrationRepository.iterator()).thenReturn(new ArrayList().iterator());
- when(clientRegistrationRepository.spliterator()).thenReturn(new ArrayList().spliterator());
- return clientRegistrationRepository;
- }
-
- @MockBean
- ReactiveOAuth2AuthorizedClientService oAuth2AuthorizedClientService;
-
- @Bean
- HttpSecurity httpSecurity() {
- return mock(HttpSecurity.class);
- }
-
- @Bean
- @Scope("prototype")
- WebTestClientSupport webTestClientSupport(
- WebTestClientProperties webTestClientProperties,
- WebTestClient webTestClient,
- SpringAddonsSecurityProperties addonsProperties) {
- return new WebTestClientSupport(webTestClientProperties, webTestClient, addonsProperties);
- }
-
- @ConditionalOnMissingBean
- @Bean
- OAuth2AuthoritiesConverter authoritiesConverter() {
- return mock(OAuth2AuthoritiesConverter.class);
- }
-
- @ConditionalOnMissingBean
- @Bean
- ServerAccessDeniedHandler serverAccessDeniedHandler() {
- return (var exchange, var ex) -> exchange.getPrincipal().flatMap(principal -> {
- var response = exchange.getResponse();
- response.setStatusCode(
- principal instanceof AnonymousAuthenticationToken ? HttpStatus.UNAUTHORIZED : HttpStatus.FORBIDDEN);
- response.getHeaders().setContentType(MediaType.TEXT_PLAIN);
- var dataBufferFactory = response.bufferFactory();
- var buffer = dataBufferFactory.wrap(ex.getMessage().getBytes(Charset.defaultCharset()));
- return response.writeWith(Mono.just(buffer)).doOnError(error -> DataBufferUtils.release(buffer));
- });
- }
-
- @ConditionalOnMissingBean
- @Bean
- SecurityWebFilterChain springAddonsResourceServerSecurityFilterChain(
- ServerHttpSecurity http,
- ServerAccessDeniedHandler accessDeniedHandler,
- SpringAddonsSecurityProperties addonsProperties,
- ServerProperties serverProperties,
- ResourceServerAuthorizeExchangeSpecPostProcessor authorizePostProcessor,
- ResourceServerHttpSecurityPostProcessor httpPostProcessor,
- CorsConfigurationSource corsConfigurationSource)
- throws Exception {
-
- if (addonsProperties.getPermitAll().length > 0) {
- http.anonymous();
- }
-
- if (addonsProperties.getCors().length > 0) {
- http.cors().configurationSource(corsConfigurationSource);
- } else {
- http.cors().disable();
- }
-
- switch (addonsProperties.getCsrf()) {
- case DISABLE:
- http.csrf().disable();
- break;
- case DEFAULT:
- if (addonsProperties.isStatlessSessions()) {
- http.csrf().disable();
- } else {
- http.csrf();
- }
- break;
- case SESSION:
- break;
- case COOKIE_HTTP_ONLY:
- http.csrf().csrfTokenRepository(new CookieServerCsrfTokenRepository());
- break;
- case COOKIE_ACCESSIBLE_FROM_JS:
- http.csrf().csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse())
- .csrfTokenRequestHandler(new XorServerCsrfTokenRequestAttributeHandler()::handle);
- break;
- }
-
- if (addonsProperties.isStatlessSessions()) {
- http.securityContextRepository(NoOpServerSecurityContextRepository.getInstance());
- }
-
- if (!addonsProperties.isRedirectToLoginIfUnauthorizedOnRestrictedContent()) {
- http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
- }
-
- if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) {
- http.redirectToHttps();
- }
-
- authorizePostProcessor.authorizeHttpRequests(
- http.authorizeExchange().pathMatchers(addonsProperties.getPermitAll()).permitAll());
-
- return httpPostProcessor.process(http).build();
- }
-
- @ConditionalOnMissingBean
- @Bean
- ResourceServerAuthorizeExchangeSpecPostProcessor authorizePostProcessor() {
- return (ServerHttpSecurity.AuthorizeExchangeSpec spec) -> spec.anyExchange().authenticated();
- }
-
- @ConditionalOnMissingBean
- @Bean
- ResourceServerHttpSecurityPostProcessor httpPostProcessor() {
- return serverHttpSecurity -> serverHttpSecurity;
- }
-
- @ConditionalOnMissingBean
- @Bean
- CorsConfigurationSource corsConfigurationSource(SpringAddonsSecurityProperties addonsProperties) {
- final var source = new UrlBasedCorsConfigurationSource();
- for (final var corsProps : addonsProperties.getCors()) {
- final var configuration = new CorsConfiguration();
- configuration.setAllowedOrigins(Arrays.asList(corsProps.getAllowedOrigins()));
- configuration.setAllowedMethods(Arrays.asList(corsProps.getAllowedMethods()));
- configuration.setAllowedHeaders(Arrays.asList(corsProps.getAllowedHeaders()));
- configuration.setExposedHeaders(Arrays.asList(corsProps.getExposedHeaders()));
- source.registerCorsConfiguration(corsProps.getPath(), configuration);
- }
- return source;
- }
+ @MockBean
+ ReactiveJwtDecoder jwtDecoder;
+
+ @MockBean
+ ReactiveAuthenticationManagerResolver jwtIssuerReactiveAuthenticationManagerResolver;
+
+ @MockBean
+ ReactiveOpaqueTokenIntrospector introspector;
+
+ @ConditionalOnMissingBean
+ @Bean
+ InMemoryReactiveClientRegistrationRepository clientRegistrationRepository() {
+ final var clientRegistrationRepository = mock(InMemoryReactiveClientRegistrationRepository.class);
+ when(clientRegistrationRepository.iterator()).thenReturn(new ArrayList().iterator());
+ when(clientRegistrationRepository.spliterator()).thenReturn(new ArrayList().spliterator());
+ return clientRegistrationRepository;
+ }
+
+ @MockBean
+ ReactiveOAuth2AuthorizedClientService oAuth2AuthorizedClientService;
+
+ @Bean
+ HttpSecurity httpSecurity() {
+ return mock(HttpSecurity.class);
+ }
+
+ @Bean
+ @Scope("prototype")
+ WebTestClientSupport webTestClientSupport(
+ WebTestClientProperties webTestClientProperties,
+ WebTestClient webTestClient,
+ SpringAddonsSecurityProperties addonsProperties) {
+ return new WebTestClientSupport(webTestClientProperties, webTestClient, addonsProperties);
+ }
+
+ @ConditionalOnMissingBean
+ @Bean
+ OAuth2AuthoritiesConverter authoritiesConverter() {
+ return mock(OAuth2AuthoritiesConverter.class);
+ }
+
+ @ConditionalOnMissingBean
+ @Bean
+ ServerAccessDeniedHandler serverAccessDeniedHandler() {
+ return (var exchange, var ex) -> exchange.getPrincipal().flatMap(principal -> {
+ var response = exchange.getResponse();
+ response.setStatusCode(principal instanceof AnonymousAuthenticationToken ? HttpStatus.UNAUTHORIZED : HttpStatus.FORBIDDEN);
+ response.getHeaders().setContentType(MediaType.TEXT_PLAIN);
+ var dataBufferFactory = response.bufferFactory();
+ var buffer = dataBufferFactory.wrap(ex.getMessage().getBytes(Charset.defaultCharset()));
+ return response.writeWith(Mono.just(buffer)).doOnError(error -> DataBufferUtils.release(buffer));
+ });
+ }
+
+ @ConditionalOnMissingBean
+ @Bean
+ SecurityWebFilterChain springAddonsResourceServerSecurityFilterChain(
+ ServerHttpSecurity http,
+ ServerAccessDeniedHandler accessDeniedHandler,
+ SpringAddonsSecurityProperties addonsProperties,
+ ServerProperties serverProperties,
+ ResourceServerAuthorizeExchangeSpecPostProcessor authorizePostProcessor,
+ ResourceServerHttpSecurityPostProcessor httpPostProcessor,
+ CorsConfigurationSource corsConfigurationSource)
+ throws Exception {
+
+ if (addonsProperties.getPermitAll().length > 0) {
+ http.anonymous();
+ }
+
+ if (addonsProperties.getCors().length > 0) {
+ http.cors().configurationSource(corsConfigurationSource);
+ } else {
+ http.cors().disable();
+ }
+
+ switch (addonsProperties.getCsrf()) {
+ case DISABLE:
+ http.csrf().disable();
+ break;
+ case DEFAULT:
+ if (addonsProperties.isStatlessSessions()) {
+ http.csrf().disable();
+ } else {
+ http.csrf();
+ }
+ break;
+ case SESSION:
+ break;
+ case COOKIE_HTTP_ONLY:
+ http.csrf().csrfTokenRepository(new CookieServerCsrfTokenRepository());
+ break;
+ case COOKIE_ACCESSIBLE_FROM_JS:
+ http.csrf().csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse())
+ .csrfTokenRequestHandler(new XorServerCsrfTokenRequestAttributeHandler()::handle);
+ break;
+ }
+
+ if (addonsProperties.isStatlessSessions()) {
+ http.securityContextRepository(NoOpServerSecurityContextRepository.getInstance());
+ }
+
+ if (!addonsProperties.isRedirectToLoginIfUnauthorizedOnRestrictedContent()) {
+ http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
+ }
+
+ if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) {
+ http.redirectToHttps();
+ }
+
+ authorizePostProcessor.authorizeHttpRequests(http.authorizeExchange().pathMatchers(addonsProperties.getPermitAll()).permitAll());
+
+ return httpPostProcessor.process(http).build();
+ }
+
+ @ConditionalOnMissingBean
+ @Bean
+ ResourceServerAuthorizeExchangeSpecPostProcessor authorizePostProcessor() {
+ return (ServerHttpSecurity.AuthorizeExchangeSpec spec) -> spec.anyExchange().authenticated();
+ }
+
+ @ConditionalOnMissingBean
+ @Bean
+ ResourceServerHttpSecurityPostProcessor httpPostProcessor() {
+ return serverHttpSecurity -> serverHttpSecurity;
+ }
+
+ @ConditionalOnMissingBean
+ @Bean
+ CorsConfigurationSource corsConfigurationSource(SpringAddonsSecurityProperties addonsProperties) {
+ final var source = new UrlBasedCorsConfigurationSource();
+ for (final var corsProps : addonsProperties.getCors()) {
+ final var configuration = new CorsConfiguration();
+ configuration.setAllowedOrigins(Arrays.asList(corsProps.getAllowedOrigins()));
+ configuration.setAllowedMethods(Arrays.asList(corsProps.getAllowedMethods()));
+ configuration.setAllowedHeaders(Arrays.asList(corsProps.getAllowedHeaders()));
+ configuration.setExposedHeaders(Arrays.asList(corsProps.getExposedHeaders()));
+ source.registerCorsConfiguration(corsProps.getPath(), configuration);
+ }
+ return source;
+ }
}
\ No newline at end of file
diff --git a/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/AuthenticationConfigurer.java b/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/AuthenticationConfigurer.java
index a81d234a5..c630b198b 100644
--- a/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/AuthenticationConfigurer.java
+++ b/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/AuthenticationConfigurer.java
@@ -1,14 +1,13 @@
/*
* Copyright 2019 Jérôme Wacongne
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may
- * obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the
+ * License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
- * and limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
package com.c4_soft.springaddons.security.oauth2.test.webflux;
diff --git a/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/MockAuthenticationWebTestClientConfigurer.java b/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/MockAuthenticationWebTestClientConfigurer.java
index bf15d5089..65a1b26b7 100644
--- a/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/MockAuthenticationWebTestClientConfigurer.java
+++ b/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/MockAuthenticationWebTestClientConfigurer.java
@@ -1,14 +1,13 @@
/*
* Copyright 2019 Jérôme Wacongne
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may
- * obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the
+ * License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
- * and limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
package com.c4_soft.springaddons.security.oauth2.test.webflux;
diff --git a/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/OidcIdAuthenticationTokenWebTestClientConfigurer.java b/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/OidcIdAuthenticationTokenWebTestClientConfigurer.java
index 629f23354..ae579faff 100644
--- a/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/OidcIdAuthenticationTokenWebTestClientConfigurer.java
+++ b/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/OidcIdAuthenticationTokenWebTestClientConfigurer.java
@@ -1,14 +1,13 @@
/*
* Copyright 2019 Jérôme Wacongne
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may
- * obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the
+ * License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
- * and limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
package com.c4_soft.springaddons.security.oauth2.test.webflux;
diff --git a/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/WebTestClientSupport.java b/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/WebTestClientSupport.java
index f422b9d8a..7ead9a807 100644
--- a/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/WebTestClientSupport.java
+++ b/webflux/spring-addons-webflux-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webflux/WebTestClientSupport.java
@@ -1,14 +1,13 @@
/*
* Copyright 2020 Jérôme Wacongne
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may
- * obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the
+ * License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
- * and limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
package com.c4_soft.springaddons.security.oauth2.test.webflux;
@@ -26,99 +25,88 @@
/**
* You may configure in your test properties:
*
- * - {@code com.c4-soft.springaddons.test.web.default-charset} defaulted to
- * utf-8
- *
- {@code com.c4-soft.springaddons.test.web.default-media-type} defaulted to
- * application+json
+ *
- {@code com.c4-soft.springaddons.test.web.default-charset} defaulted to utf-8
+ *
- {@code com.c4-soft.springaddons.test.web.default-media-type} defaulted to application+json
*
*
* @author Jérôme Wacongne <ch4mp@c4-soft.com>
*/
public class WebTestClientSupport {
- private MediaType mediaType;
-
- private Charset charset;
-
- private WebTestClient delegate;
-
- public WebTestClientSupport(
- WebTestClientProperties webTestClientProperties,
- WebTestClient webTestClient,
- SpringAddonsSecurityProperties addonsProperties) {
- this.mediaType = MediaType.valueOf(webTestClientProperties.getDefaultMediaType());
- this.charset = Charset.forName(webTestClientProperties.getDefaultCharset());
- this.delegate = webTestClient;
- this.setCsrf(!addonsProperties.getCsrf().equals(Csrf.DISABLE));
- }
-
- /**
- * @param mediaType override configured default media-type
- * @return
- */
- public WebTestClientSupport setMediaType(MediaType mediaType) {
- this.mediaType = mediaType;
- return this;
- }
-
- /**
- * @param charset override configured default charset
- * @return
- */
- public WebTestClientSupport setCharset(Charset charset) {
- this.charset = charset;
- return this;
- }
-
- public ResponseSpec get(MediaType accept, String uriTemplate, Object... uriVars) {
- return delegate.get().uri(uriTemplate, uriVars).accept(accept).exchange();
- }
-
- public ResponseSpec get(String uriTemplate, Object... uriVars) {
- return get(new MediaType(mediaType, charset), uriTemplate, uriVars);
- }
-
- public ResponseSpec post(T payload, MediaType contentType, Charset charset, MediaType accept,
- String uriTemplate, Object... uriVars) {
- return delegate.post().uri(uriTemplate, uriVars).accept(accept).contentType(new MediaType(contentType, charset))
- .bodyValue(payload).exchange();
- }
-
- public ResponseSpec post(T payload, String uriTemplate, Object... uriVars) {
- return post(payload, mediaType, charset, mediaType, uriTemplate, uriVars);
- }
-
- public ResponseSpec put(T payload, MediaType contentType, Charset charset, String uriTemplate,
- Object... uriVars) {
- return delegate.put().uri(uriTemplate, uriVars).contentType(new MediaType(contentType, charset))
- .bodyValue(payload).exchange();
- }
-
- public ResponseSpec put(T payload, String uriTemplate, Object... uriVars) {
- return put(payload, mediaType, charset, uriTemplate, uriVars);
- }
-
- public ResponseSpec patch(T payload, MediaType contentType, Charset charset, String uriTemplate,
- Object... uriVars) {
- return delegate.patch().uri(uriTemplate, uriVars).contentType(new MediaType(contentType, charset))
- .bodyValue(payload).exchange();
- }
-
- public ResponseSpec patch(T payload, String uriTemplate, Object... uriVars) {
- return patch(payload, mediaType, charset, uriTemplate, uriVars);
- }
-
- public ResponseSpec delete(String uriTemplate, Object... uriVars) {
- return delegate.delete().uri(uriTemplate, uriVars).exchange();
- }
-
- public WebTestClientSupport mutateWith(WebTestClientConfigurer configurer) {
- delegate = delegate.mutateWith(configurer);
- return this;
- }
-
- public WebTestClientSupport setCsrf(boolean isCsrf) {
- delegate.mutateWith(SecurityMockServerConfigurers.csrf());
- return this;
- }
+ private MediaType mediaType;
+
+ private Charset charset;
+
+ private WebTestClient delegate;
+
+ public WebTestClientSupport(WebTestClientProperties webTestClientProperties, WebTestClient webTestClient, SpringAddonsSecurityProperties addonsProperties) {
+ this.mediaType = MediaType.valueOf(webTestClientProperties.getDefaultMediaType());
+ this.charset = Charset.forName(webTestClientProperties.getDefaultCharset());
+ this.delegate = webTestClient;
+ this.setCsrf(!addonsProperties.getCsrf().equals(Csrf.DISABLE));
+ }
+
+ /**
+ * @param mediaType override configured default media-type
+ * @return
+ */
+ public WebTestClientSupport setMediaType(MediaType mediaType) {
+ this.mediaType = mediaType;
+ return this;
+ }
+
+ /**
+ * @param charset override configured default charset
+ * @return
+ */
+ public WebTestClientSupport setCharset(Charset charset) {
+ this.charset = charset;
+ return this;
+ }
+
+ public ResponseSpec get(MediaType accept, String uriTemplate, Object... uriVars) {
+ return delegate.get().uri(uriTemplate, uriVars).accept(accept).exchange();
+ }
+
+ public ResponseSpec get(String uriTemplate, Object... uriVars) {
+ return get(new MediaType(mediaType, charset), uriTemplate, uriVars);
+ }
+
+ public ResponseSpec post(T payload, MediaType contentType, Charset charset, MediaType accept, String uriTemplate, Object... uriVars) {
+ return delegate.post().uri(uriTemplate, uriVars).accept(accept).contentType(new MediaType(contentType, charset)).bodyValue(payload).exchange();
+ }
+
+ public ResponseSpec post(T payload, String uriTemplate, Object... uriVars) {
+ return post(payload, mediaType, charset, mediaType, uriTemplate, uriVars);
+ }
+
+ public ResponseSpec put(T payload, MediaType contentType, Charset charset, String uriTemplate, Object... uriVars) {
+ return delegate.put().uri(uriTemplate, uriVars).contentType(new MediaType(contentType, charset)).bodyValue(payload).exchange();
+ }
+
+ public ResponseSpec put(T payload, String uriTemplate, Object... uriVars) {
+ return put(payload, mediaType, charset, uriTemplate, uriVars);
+ }
+
+ public ResponseSpec patch(T payload, MediaType contentType, Charset charset, String uriTemplate, Object... uriVars) {
+ return delegate.patch().uri(uriTemplate, uriVars).contentType(new MediaType(contentType, charset)).bodyValue(payload).exchange();
+ }
+
+ public ResponseSpec patch(T payload, String uriTemplate, Object... uriVars) {
+ return patch(payload, mediaType, charset, uriTemplate, uriVars);
+ }
+
+ public ResponseSpec delete(String uriTemplate, Object... uriVars) {
+ return delegate.delete().uri(uriTemplate, uriVars).exchange();
+ }
+
+ public WebTestClientSupport mutateWith(WebTestClientConfigurer configurer) {
+ delegate = delegate.mutateWith(configurer);
+ return this;
+ }
+
+ public WebTestClientSupport setCsrf(boolean isCsrf) {
+ delegate.mutateWith(SecurityMockServerConfigurers.csrf());
+ return this;
+ }
}
diff --git a/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsBackChannelLogoutBeans.java b/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsBackChannelLogoutBeans.java
index 23ecf42d9..04456a0da 100644
--- a/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsBackChannelLogoutBeans.java
+++ b/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsBackChannelLogoutBeans.java
@@ -38,131 +38,110 @@
/**
*
- * This provides with a client side implementation of the OIDC
- * Back-Channel
- * Logout specification. Keycloak conforms to this OP side of the spec.
- * Auth0
- * could some day.
+ * This provides with a client side implementation of the OIDC Back-Channel Logout
+ * specification. Keycloak conforms to this OP side of the spec.
+ * Auth0 could some day.
*
*
- * Implementation is made with a security filter-chain intercepting just the
- * "/backchannel_logout" route and a controller handling requests to that
- * end-point.
+ * Implementation is made with a security filter-chain intercepting just the "/backchannel_logout" route and a controller handling requests to that end-point.
*
*
- * This beans are defined only if
- * "com.c4-soft.springaddons.security.client.back-channel-logout-enabled"
- * property is true.
+ * This beans are defined only if "com.c4-soft.springaddons.security.client.back-channel-logout-enabled" property is true.
*
*
- *
* @author Jerome Wacongne ch4mp@c4-soft.com
- *
*/
@ConditionalOnProperty("com.c4-soft.springaddons.security.client.back-channel-logout-enabled")
@AutoConfiguration
@Import({ SpringAddonsOAuth2ClientProperties.class })
public class SpringAddonsBackChannelLogoutBeans {
- /**
- * Requests from the OP are anonymous, are not part of a session, and have no
- * CSRF token. It contains a logout JWT which serves both to authenticate the
- * request and protect against CSRF.
- *
- * @param http
- * @param serverProperties Spring Boot server properties
- * @return a security filter-chain dedicated to back-channel logout handling
- * @throws Exception
- */
- @Order(Ordered.HIGHEST_PRECEDENCE)
- @Bean
- SecurityFilterChain springAddonsBackChannelLogoutClientFilterChain(
- HttpSecurity http,
- ServerProperties serverProperties)
- throws Exception {
- http.securityMatcher(new AntPathRequestMatcher("/backchannel_logout"));
- http.authorizeHttpRequests().anyRequest().permitAll();
- if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) {
- http.requiresChannel().anyRequest().requiresSecure();
- }
- http.cors().disable();
- http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
- http.csrf().disable();
- return http.build();
- }
+ /**
+ * Requests from the OP are anonymous, are not part of a session, and have no CSRF token. It contains a logout JWT which serves both to authenticate the
+ * request and protect against CSRF.
+ *
+ * @param http
+ * @param serverProperties Spring Boot server properties
+ * @return a security filter-chain dedicated to back-channel logout handling
+ * @throws Exception
+ */
+ @Order(Ordered.HIGHEST_PRECEDENCE)
+ @Bean
+ SecurityFilterChain springAddonsBackChannelLogoutClientFilterChain(HttpSecurity http, ServerProperties serverProperties) throws Exception {
+ http.securityMatcher(new AntPathRequestMatcher("/backchannel_logout"));
+ http.authorizeHttpRequests().anyRequest().permitAll();
+ if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) {
+ http.requiresChannel().anyRequest().requiresSecure();
+ }
+ http.cors().disable();
+ http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
+ http.csrf().disable();
+ return http.build();
+ }
- /**
- *
- * Handles a POST request containing a JWT logout token provided as
- * application/x-www-form-urlencoded as specified in Back-Channel
- * Logout specification.
- *
- *
- * This end-point will:
- *
- * - remove the relevant authorized client (based on issuer URI) for the
- * relevant user (based on the subject)
- * - maybe invalidate user session: only if the removed authorized client was
- * the last one the user had
- *
- *
- * @author Jerome Wacongne ch4mp@c4-soft.com
- *
- */
- @Component
- @RestController
- public static class BackChannelLogoutController {
- private final SpringAddonsOAuth2AuthorizedClientRepository authorizedClientRepository;
- private final Map jwtDecoders;
+ /**
+ *
+ * Handles a POST request containing a JWT logout token provided as application/x-www-form-urlencoded as specified in
+ * Back-Channel Logout specification.
+ *
+ *
+ * This end-point will:
+ *
+ * - remove the relevant authorized client (based on issuer URI) for the relevant user (based on the subject)
+ * - maybe invalidate user session: only if the removed authorized client was the last one the user had
+ *
+ *
+ * @author Jerome Wacongne ch4mp@c4-soft.com
+ */
+ @Component
+ @RestController
+ public static class BackChannelLogoutController {
+ private final SpringAddonsOAuth2AuthorizedClientRepository authorizedClientRepository;
+ private final Map jwtDecoders;
- public BackChannelLogoutController(SpringAddonsOAuth2AuthorizedClientRepository authorizedClientRepository,
- InMemoryClientRegistrationRepository registrationRepo) {
- this.authorizedClientRepository = authorizedClientRepository;
- this.jwtDecoders = StreamSupport.stream(registrationRepo.spliterator(), false)
- .filter(reg -> AuthorizationGrantType.AUTHORIZATION_CODE.equals(reg.getAuthorizationGrantType()))
- .map(ClientRegistration::getProviderDetails)
- .collect(Collectors.toMap(provider -> provider.getIssuerUri(),
- provider -> NimbusJwtDecoder.withJwkSetUri(provider.getJwkSetUri()).build()));
- }
+ public BackChannelLogoutController(
+ SpringAddonsOAuth2AuthorizedClientRepository authorizedClientRepository,
+ InMemoryClientRegistrationRepository registrationRepo) {
+ this.authorizedClientRepository = authorizedClientRepository;
+ this.jwtDecoders = StreamSupport.stream(registrationRepo.spliterator(), false)
+ .filter(reg -> AuthorizationGrantType.AUTHORIZATION_CODE.equals(reg.getAuthorizationGrantType()))
+ .map(ClientRegistration::getProviderDetails).collect(
+ Collectors.toMap(provider -> provider.getIssuerUri(), provider -> NimbusJwtDecoder.withJwkSetUri(provider.getJwkSetUri()).build()));
+ }
- @PostMapping(path = "/backchannel_logout", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
- public ResponseEntity backChannelLogout(@RequestParam MultiValueMap body) {
- final var tokenString = body.get("logout_token");
- if (tokenString == null || tokenString.size() != 1) {
- throw new BadLogoutRequestException();
- }
- jwtDecoders.forEach((issuer, decoder) -> {
- try {
- final var jwt = decoder.decode(tokenString.get(0));
- final var isLogoutToken = Optional.ofNullable(jwt.getClaims().get("events"))
- .map(Object::toString)
- .map(evt -> evt.contains("http://schemas.openid.net/event/backchannel-logout"))
- .orElse(false);
- if (!isLogoutToken) {
- throw new BadLogoutRequestException();
- }
- final var logoutIss = Optional.ofNullable(jwt.getIssuer()).map(URL::toString).orElse(null);
- if (!Objects.equals(issuer, logoutIss)) {
- throw new BadLogoutRequestException();
- }
- final var logoutSub = jwt.getSubject();
- final var sessionsToInvalidate = authorizedClientRepository.removeAuthorizedClients(logoutIss,
- logoutSub);
- sessionsToInvalidate.forEach(s -> {
- s.invalidate();
- });
- } catch (JwtException e) {
- }
- });
- return ResponseEntity.ok().build();
- }
+ @PostMapping(path = "/backchannel_logout", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
+ public ResponseEntity backChannelLogout(@RequestParam MultiValueMap body) {
+ final var tokenString = body.get("logout_token");
+ if (tokenString == null || tokenString.size() != 1) {
+ throw new BadLogoutRequestException();
+ }
+ jwtDecoders.forEach((issuer, decoder) -> {
+ try {
+ final var jwt = decoder.decode(tokenString.get(0));
+ final var isLogoutToken = Optional.ofNullable(jwt.getClaims().get("events")).map(Object::toString)
+ .map(evt -> evt.contains("http://schemas.openid.net/event/backchannel-logout")).orElse(false);
+ if (!isLogoutToken) {
+ throw new BadLogoutRequestException();
+ }
+ final var logoutIss = Optional.ofNullable(jwt.getIssuer()).map(URL::toString).orElse(null);
+ if (!Objects.equals(issuer, logoutIss)) {
+ throw new BadLogoutRequestException();
+ }
+ final var logoutSub = jwt.getSubject();
+ final var sessionsToInvalidate = authorizedClientRepository.removeAuthorizedClients(logoutIss, logoutSub);
+ sessionsToInvalidate.forEach(s -> {
+ s.invalidate();
+ });
+ } catch (JwtException e) {
+ }
+ });
+ return ResponseEntity.ok().build();
+ }
- @ResponseStatus(HttpStatus.BAD_REQUEST)
- static final class BadLogoutRequestException extends RuntimeException {
- }
- }
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ static final class BadLogoutRequestException extends RuntimeException {
+ private static final long serialVersionUID = -8703279699142477824L;
+ }
+ }
}
diff --git a/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2AuthorizationRequestResolver.java b/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2AuthorizationRequestResolver.java
index fcd26ae33..2614c6bdd 100644
--- a/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2AuthorizationRequestResolver.java
+++ b/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2AuthorizationRequestResolver.java
@@ -14,41 +14,38 @@
import jakarta.servlet.http.HttpServletRequest;
/**
- * Forces the usage of {@link SpringAddonsOAuth2ClientProperties#getClientUri()
- * SpringAddonsOAuth2ClientProperties#client-uri} in post-login redirection URI
+ * Forces the usage of {@link SpringAddonsOAuth2ClientProperties#getClientUri() SpringAddonsOAuth2ClientProperties#client-uri} in post-login redirection URI
*
* @author Jerome Wacongne ch4mp@c4-soft.com
- *
*/
public class SpringAddonsOAuth2AuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver {
- private final OAuth2AuthorizationRequestResolver defaultResolver;
-
- public SpringAddonsOAuth2AuthorizationRequestResolver(ClientRegistrationRepository clientRegistrationRepository) {
- defaultResolver = new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository,
- OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI);
- }
-
- @Override
- public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
- return toAbsolute(defaultResolver.resolve(request), request);
- }
-
- @Override
- public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientRegistrationId) {
- return toAbsolute(defaultResolver.resolve(request, clientRegistrationId), request);
- }
-
- private OAuth2AuthorizationRequest toAbsolute(OAuth2AuthorizationRequest defaultAuthorizationRequest,
- HttpServletRequest request) {
- final var clientUriString = request.getRequestURL();
- if (defaultAuthorizationRequest == null || clientUriString == null) {
- return defaultAuthorizationRequest;
- }
- final var clientUri = URI.create(clientUriString.toString());
- final var redirectUri = UriComponentsBuilder.fromUriString(defaultAuthorizationRequest.getRedirectUri())
- .scheme(clientUri.getScheme()).host(clientUri.getHost())
- .port(clientUri.getPort()).build().toUriString();
- return OAuth2AuthorizationRequest.from(defaultAuthorizationRequest).redirectUri(redirectUri).build();
- }
+ private final OAuth2AuthorizationRequestResolver defaultResolver;
+
+ public SpringAddonsOAuth2AuthorizationRequestResolver(ClientRegistrationRepository clientRegistrationRepository) {
+ defaultResolver = new DefaultOAuth2AuthorizationRequestResolver(
+ clientRegistrationRepository,
+ OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI);
+ }
+
+ @Override
+ public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
+ return toAbsolute(defaultResolver.resolve(request), request);
+ }
+
+ @Override
+ public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientRegistrationId) {
+ return toAbsolute(defaultResolver.resolve(request, clientRegistrationId), request);
+ }
+
+ private OAuth2AuthorizationRequest toAbsolute(OAuth2AuthorizationRequest defaultAuthorizationRequest, HttpServletRequest request) {
+ final var clientUriString = request.getRequestURL();
+ if (defaultAuthorizationRequest == null || clientUriString == null) {
+ return defaultAuthorizationRequest;
+ }
+ final var clientUri = URI.create(clientUriString.toString());
+ final var redirectUri = UriComponentsBuilder.fromUriString(defaultAuthorizationRequest.getRedirectUri()).scheme(clientUri.getScheme())
+ .host(clientUri.getHost()).port(clientUri.getPort()).build().toUriString();
+ return OAuth2AuthorizationRequest.from(defaultAuthorizationRequest).redirectUri(redirectUri).build();
+ }
}
\ No newline at end of file
diff --git a/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2AuthorizedClientRepository.java b/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2AuthorizedClientRepository.java
index 79dc76b2a..06e2352d5 100644
--- a/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2AuthorizedClientRepository.java
+++ b/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2AuthorizedClientRepository.java
@@ -31,234 +31,219 @@
/**
*
- * Work around the single tenancy nature of {@link OAuth2AuthenticationToken}
- * and {@link InMemoryReactiveClientRegistrationRepository}: if a user
- * authenticates sequentially on several OP, his OAuth2AuthenticationToken will
- * contain an {@link OAuth2User} corresponding only to the last OP he
- * authenticated with. To work around this limitation, this repository keeps an
- * OAuth2User for each OP (issuer) and resolves the authorization client with
- * the right subject for each issuer.
+ * Work around the single tenancy nature of {@link OAuth2AuthenticationToken} and {@link InMemoryReactiveClientRegistrationRepository}: if a user authenticates
+ * sequentially on several OP, his OAuth2AuthenticationToken will contain an {@link OAuth2User} corresponding only to the last OP he authenticated with. To work
+ * around this limitation, this repository keeps an OAuth2User for each OP (issuer) and resolves the authorization client with the right subject for each
+ * issuer.
*
*
- * This repo is also a session listener to keep track of all the (issuer,
- * subject) pairs and their associations with sessions (many to many relation).
- * This enables it to expose the required API for back-channel logout where a
- * request is received to remove an authorized client based on its issuer and
- * subject but without a session token.
+ * This repo is also a session listener to keep track of all the (issuer, subject) pairs and their associations with sessions (many to many relation). This
+ * enables it to expose the required API for back-channel logout where a request is received to remove an authorized client based on its issuer and subject but
+ * without a session token.
*
*
* @author Jerome Wacongne ch4mp@c4-soft.com
- *
*/
@RequiredArgsConstructor
-public class SpringAddonsOAuth2AuthorizedClientRepository
- implements OAuth2AuthorizedClientRepository, HttpSessionListener, HttpSessionIdListener {
- private static final String OAUTH2_USERS_KEY = "com.c4-soft.spring-addons.OAuth2.client.oauth2-users";
- private static final String AUTHORIZED_CLIENTS_KEY = "com.c4-soft.spring-addons.OAuth2.client.authorized-clients";
-
- private static final Map> sessionsByuserId = new ConcurrentHashMap<>();
- private static final Map> userIdsBySessionId = new ConcurrentHashMap<>();
-
- private final ClientRegistrationRepository clientRegistrationRepository;
-
- @Override
- public void sessionIdChanged(HttpSessionEvent event, String oldSessionId) {
- if (userIdsBySessionId.containsKey(oldSessionId) && !Objects.equals(event.getSession().getId(), oldSessionId)) {
- userIdsBySessionId.put(event.getSession().getId(), userIdsBySessionId.get(oldSessionId));
- userIdsBySessionId.remove(oldSessionId);
- }
- }
-
- @Override
- public void sessionCreated(HttpSessionEvent se) {
- }
-
- @Override
- public void sessionDestroyed(HttpSessionEvent se) {
- final var idsToUpdate = getUserIds(se.getSession().getId());
- for (var id : idsToUpdate) {
- setSessions(id.iss(), id.sub(), new HashSet<>(getSessions(id.iss(), id.sub()).stream()
- .filter(s -> !(s.getId().equals(se.getSession().getId()))).collect(Collectors.toSet())));
- }
- userIdsBySessionId.remove(se.getSession().getId());
- }
-
- private Optional getUserSubject(HttpSession session, String issuer) {
- final var oauth2Users = getOAuth2Users(session);
- return Optional.ofNullable(oauth2Users.get(issuer)).map(u -> u.getAttribute(JWTClaimNames.SUBJECT));
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public T loadAuthorizedClient(String clientRegistrationId,
- Authentication auth, HttpServletRequest request) {
- final var issuer = clientRegistrationRepository.findByRegistrationId(clientRegistrationId).getProviderDetails()
- .getIssuerUri();
- final var subject = getUserSubject(request.getSession(), issuer).orElse(auth.getName());
-
- return (T) loadAuthorizedClient(request.getSession(), issuer, subject);
- }
-
- public OAuth2AuthorizedClient loadAuthorizedClient(HttpSession session, String issuer, String subject) {
- final var authorizedClients = getAuthorizedClients(session);
- return authorizedClients.stream()
- .filter(ac -> Objects.equals(ac.getClientRegistration().getProviderDetails().getIssuerUri(), issuer)
- && Objects.equals(ac.getPrincipalName(), subject))
- .findAny().orElse(null);
- }
-
- @Override
- public void saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication auth,
- HttpServletRequest request, HttpServletResponse response) {
- if (auth instanceof OAuth2LoginAuthenticationToken || auth instanceof OAuth2AuthenticationToken) {
- saveAuthorizedClient(request.getSession(), authorizedClient, (OAuth2User) auth.getPrincipal());
- }
- }
-
- private void saveAuthorizedClient(HttpSession session, OAuth2AuthorizedClient authorizedClient, OAuth2User user) {
- final var issuer = authorizedClient.getClientRegistration().getProviderDetails().getIssuerUri();
- final var subject = user.getAttributes().get(JWTClaimNames.SUBJECT).toString();
-
- final var oauth2Users = getOAuth2Users(session);
- if (oauth2Users.containsKey(issuer)) {
- removeAuthorizedClient(session, issuer, oauth2Users.get(issuer).getName());
- }
- oauth2Users.put(issuer, user);
- setOAuth2Users(session, oauth2Users);
-
- final var authorizedClients = getAuthorizedClients(session);
- authorizedClients.add(authorizedClient);
- setAuthorizedClients(session, authorizedClients);
-
- final var sessions = getSessions(issuer, subject);
- if (!sessions.contains(session)) {
- sessions.add(session);
- setSessions(issuer, subject, sessions);
- }
-
- final var userIds = getUserIds(session.getId());
- userIds.add(new UserId(issuer, subject));
- setUserIds(session.getId(), userIds);
- }
-
- @Override
- public void removeAuthorizedClient(String clientRegistrationId, Authentication auth, HttpServletRequest request,
- HttpServletResponse response) {
- if (auth instanceof OAuth2LoginAuthenticationToken || auth instanceof OAuth2AuthenticationToken) {
- final var issuer = clientRegistrationRepository.findByRegistrationId(clientRegistrationId)
- .getProviderDetails()
- .getIssuerUri();
- final var subject = getUserSubject(request.getSession(), issuer).orElse(auth.getName());
-
- removeAuthorizedClient(request.getSession(), issuer, subject);
- }
- }
-
- public void removeAuthorizedClient(HttpSession session, String issuer, String subject) {
- final var allAuthorizedClients = getAuthorizedClients(session);
- final var authorizedClientsToRemove = allAuthorizedClients.stream()
- .filter(ac -> Objects.equals(ac.getClientRegistration().getProviderDetails().getIssuerUri(), issuer)
- && Objects.equals(ac.getPrincipalName(), subject))
- .collect(Collectors.toSet());
- allAuthorizedClients.removeAll(authorizedClientsToRemove);
- setAuthorizedClients(session, allAuthorizedClients);
-
- final var oauth2Users = getOAuth2Users(session);
- if (oauth2Users.containsKey(issuer)) {
- oauth2Users.remove(issuer);
- setOAuth2Users(session, oauth2Users);
- }
-
- final var sessions = getSessions(issuer, subject);
- if (sessions.contains(session)) {
- sessions.remove(session);
- setSessions(issuer, subject, sessions);
- }
-
- final var userIds = getUserIds(session.getId());
- userIds.remove(new UserId(issuer, subject));
- }
-
- /**
- * Removes an authorized client and returns a list of sessions to invalidate
- * (those for which the current user has no more authorized client after this
- * one was removed)
- *
- * @param issuer OP issuer URI
- * @param subject current user subject for this OP
- * @return the list of user sessions for which this authorized client was the
- * last one
- */
- public Collection removeAuthorizedClients(String issuer, String subject) {
- final var sessions = getSessions(issuer, subject);
-
- final var sessionsToInvalidate = sessions.stream().filter(s -> {
- return getAuthorizedClients(s).stream()
- .filter(authorizedClient -> authorizedClient.getClientRegistration().getProviderDetails()
- .getIssuerUri().equals(issuer)
- && authorizedClient.getPrincipalName().equals(subject))
- .count() < 1;
- }).toList();
-
- for (var session : sessions) {
- removeAuthorizedClient(session, issuer, subject);
- }
-
- return sessionsToInvalidate;
- }
-
- @SuppressWarnings("unchecked")
- private Set getAuthorizedClients(HttpSession session) {
- final var sessionAuthorizedClients = (Set) session.getAttribute(AUTHORIZED_CLIENTS_KEY);
- return sessionAuthorizedClients == null ? new HashSet<>() : sessionAuthorizedClients;
- }
-
- private void setAuthorizedClients(HttpSession session, Set sessionAuthorizedClients) {
- session.setAttribute(AUTHORIZED_CLIENTS_KEY, sessionAuthorizedClients);
- }
-
- public Map getOAuth2UsersBySession(HttpSession session) {
- if (session == null) {
- return null;
- }
- return Collections.unmodifiableMap(getOAuth2Users(session));
- }
-
- @SuppressWarnings("unchecked")
- private Map getOAuth2Users(HttpSession s) {
- final var sessionOauth2UsersByIssuer = (Map) s.getAttribute(OAUTH2_USERS_KEY);
- return sessionOauth2UsersByIssuer == null ? new ConcurrentHashMap()
- : sessionOauth2UsersByIssuer;
- }
-
- private void setOAuth2Users(HttpSession s, Map sessionOauth2UsersByIssuer) {
- s.setAttribute(OAUTH2_USERS_KEY, sessionOauth2UsersByIssuer);
- }
-
- private Set getSessions(String issuer, String subject) {
- return sessionsByuserId.getOrDefault(new UserId(issuer, subject), new HashSet<>());
- }
-
- private void setSessions(String issuer, String subject, Set sessions) {
- if (sessions == null || sessions.isEmpty()) {
- sessionsByuserId.remove(new UserId(issuer, subject));
- } else {
- sessionsByuserId.put(new UserId(issuer, subject), sessions);
- }
- }
-
- private Set getUserIds(String sessionId) {
- return userIdsBySessionId.getOrDefault(sessionId, new HashSet<>());
- }
-
- private void setUserIds(String sessionId, Set userIds) {
- if (userIds == null || userIds.isEmpty()) {
- userIdsBySessionId.remove(sessionId);
- } else {
- userIdsBySessionId.put(sessionId, userIds);
- }
- }
-
- private static record UserId(String iss, String sub) {
- }
+public class SpringAddonsOAuth2AuthorizedClientRepository implements OAuth2AuthorizedClientRepository, HttpSessionListener, HttpSessionIdListener {
+ private static final String OAUTH2_USERS_KEY = "com.c4-soft.spring-addons.OAuth2.client.oauth2-users";
+ private static final String AUTHORIZED_CLIENTS_KEY = "com.c4-soft.spring-addons.OAuth2.client.authorized-clients";
+
+ private static final Map> sessionsByuserId = new ConcurrentHashMap<>();
+ private static final Map> userIdsBySessionId = new ConcurrentHashMap<>();
+
+ private final ClientRegistrationRepository clientRegistrationRepository;
+
+ @Override
+ public void sessionIdChanged(HttpSessionEvent event, String oldSessionId) {
+ if (userIdsBySessionId.containsKey(oldSessionId) && !Objects.equals(event.getSession().getId(), oldSessionId)) {
+ userIdsBySessionId.put(event.getSession().getId(), userIdsBySessionId.get(oldSessionId));
+ userIdsBySessionId.remove(oldSessionId);
+ }
+ }
+
+ @Override
+ public void sessionCreated(HttpSessionEvent se) {
+ }
+
+ @Override
+ public void sessionDestroyed(HttpSessionEvent se) {
+ final var idsToUpdate = getUserIds(se.getSession().getId());
+ for (var id : idsToUpdate) {
+ setSessions(
+ id.iss(),
+ id.sub(),
+ new HashSet<>(
+ getSessions(id.iss(), id.sub()).stream().filter(s -> !(s.getId().equals(se.getSession().getId()))).collect(Collectors.toSet())));
+ }
+ userIdsBySessionId.remove(se.getSession().getId());
+ }
+
+ private Optional getUserSubject(HttpSession session, String issuer) {
+ final var oauth2Users = getOAuth2Users(session);
+ return Optional.ofNullable(oauth2Users.get(issuer)).map(u -> u.getAttribute(JWTClaimNames.SUBJECT));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public T loadAuthorizedClient(String clientRegistrationId, Authentication auth, HttpServletRequest request) {
+ final var issuer = clientRegistrationRepository.findByRegistrationId(clientRegistrationId).getProviderDetails().getIssuerUri();
+ final var subject = getUserSubject(request.getSession(), issuer).orElse(auth.getName());
+
+ return (T) loadAuthorizedClient(request.getSession(), issuer, subject);
+ }
+
+ public OAuth2AuthorizedClient loadAuthorizedClient(HttpSession session, String issuer, String subject) {
+ final var authorizedClients = getAuthorizedClients(session);
+ return authorizedClients.stream().filter(
+ ac -> Objects.equals(ac.getClientRegistration().getProviderDetails().getIssuerUri(), issuer) && Objects.equals(ac.getPrincipalName(), subject))
+ .findAny().orElse(null);
+ }
+
+ @Override
+ public void saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication auth, HttpServletRequest request, HttpServletResponse response) {
+ if (auth instanceof OAuth2LoginAuthenticationToken || auth instanceof OAuth2AuthenticationToken) {
+ saveAuthorizedClient(request.getSession(), authorizedClient, (OAuth2User) auth.getPrincipal());
+ }
+ }
+
+ private void saveAuthorizedClient(HttpSession session, OAuth2AuthorizedClient authorizedClient, OAuth2User user) {
+ final var issuer = authorizedClient.getClientRegistration().getProviderDetails().getIssuerUri();
+ final var subject = user.getAttributes().get(JWTClaimNames.SUBJECT).toString();
+
+ final var oauth2Users = getOAuth2Users(session);
+ if (oauth2Users.containsKey(issuer)) {
+ removeAuthorizedClient(session, issuer, oauth2Users.get(issuer).getName());
+ }
+ oauth2Users.put(issuer, user);
+ setOAuth2Users(session, oauth2Users);
+
+ final var authorizedClients = getAuthorizedClients(session);
+ authorizedClients.add(authorizedClient);
+ setAuthorizedClients(session, authorizedClients);
+
+ final var sessions = getSessions(issuer, subject);
+ if (!sessions.contains(session)) {
+ sessions.add(session);
+ setSessions(issuer, subject, sessions);
+ }
+
+ final var userIds = getUserIds(session.getId());
+ userIds.add(new UserId(issuer, subject));
+ setUserIds(session.getId(), userIds);
+ }
+
+ @Override
+ public void removeAuthorizedClient(String clientRegistrationId, Authentication auth, HttpServletRequest request, HttpServletResponse response) {
+ if (auth instanceof OAuth2LoginAuthenticationToken || auth instanceof OAuth2AuthenticationToken) {
+ final var issuer = clientRegistrationRepository.findByRegistrationId(clientRegistrationId).getProviderDetails().getIssuerUri();
+ final var subject = getUserSubject(request.getSession(), issuer).orElse(auth.getName());
+
+ removeAuthorizedClient(request.getSession(), issuer, subject);
+ }
+ }
+
+ public void removeAuthorizedClient(HttpSession session, String issuer, String subject) {
+ final var allAuthorizedClients = getAuthorizedClients(session);
+ final var authorizedClientsToRemove = allAuthorizedClients.stream().filter(
+ ac -> Objects.equals(ac.getClientRegistration().getProviderDetails().getIssuerUri(), issuer) && Objects.equals(ac.getPrincipalName(), subject))
+ .collect(Collectors.toSet());
+ allAuthorizedClients.removeAll(authorizedClientsToRemove);
+ setAuthorizedClients(session, allAuthorizedClients);
+
+ final var oauth2Users = getOAuth2Users(session);
+ if (oauth2Users.containsKey(issuer)) {
+ oauth2Users.remove(issuer);
+ setOAuth2Users(session, oauth2Users);
+ }
+
+ final var sessions = getSessions(issuer, subject);
+ if (sessions.contains(session)) {
+ sessions.remove(session);
+ setSessions(issuer, subject, sessions);
+ }
+
+ final var userIds = getUserIds(session.getId());
+ userIds.remove(new UserId(issuer, subject));
+ }
+
+ /**
+ * Removes an authorized client and returns a list of sessions to invalidate (those for which the current user has no more authorized client after this one
+ * was removed)
+ *
+ * @param issuer OP issuer URI
+ * @param subject current user subject for this OP
+ * @return the list of user sessions for which this authorized client was the last one
+ */
+ public Collection removeAuthorizedClients(String issuer, String subject) {
+ final var sessions = getSessions(issuer, subject);
+
+ final var sessionsToInvalidate = sessions.stream().filter(s -> {
+ return getAuthorizedClients(s).stream()
+ .filter(
+ authorizedClient -> authorizedClient.getClientRegistration().getProviderDetails().getIssuerUri().equals(issuer)
+ && authorizedClient.getPrincipalName().equals(subject))
+ .count() < 1;
+ }).toList();
+
+ for (var session : sessions) {
+ removeAuthorizedClient(session, issuer, subject);
+ }
+
+ return sessionsToInvalidate;
+ }
+
+ @SuppressWarnings("unchecked")
+ private Set getAuthorizedClients(HttpSession session) {
+ final var sessionAuthorizedClients = (Set) session.getAttribute(AUTHORIZED_CLIENTS_KEY);
+ return sessionAuthorizedClients == null ? new HashSet<>() : sessionAuthorizedClients;
+ }
+
+ private void setAuthorizedClients(HttpSession session, Set sessionAuthorizedClients) {
+ session.setAttribute(AUTHORIZED_CLIENTS_KEY, sessionAuthorizedClients);
+ }
+
+ public Map getOAuth2UsersBySession(HttpSession session) {
+ if (session == null) {
+ return null;
+ }
+ return Collections.unmodifiableMap(getOAuth2Users(session));
+ }
+
+ @SuppressWarnings("unchecked")
+ private Map getOAuth2Users(HttpSession s) {
+ final var sessionOauth2UsersByIssuer = (Map) s.getAttribute(OAUTH2_USERS_KEY);
+ return sessionOauth2UsersByIssuer == null ? new ConcurrentHashMap() : sessionOauth2UsersByIssuer;
+ }
+
+ private void setOAuth2Users(HttpSession s, Map sessionOauth2UsersByIssuer) {
+ s.setAttribute(OAUTH2_USERS_KEY, sessionOauth2UsersByIssuer);
+ }
+
+ private Set getSessions(String issuer, String subject) {
+ return sessionsByuserId.getOrDefault(new UserId(issuer, subject), new HashSet<>());
+ }
+
+ private void setSessions(String issuer, String subject, Set sessions) {
+ if (sessions == null || sessions.isEmpty()) {
+ sessionsByuserId.remove(new UserId(issuer, subject));
+ } else {
+ sessionsByuserId.put(new UserId(issuer, subject), sessions);
+ }
+ }
+
+ private Set getUserIds(String sessionId) {
+ return userIdsBySessionId.getOrDefault(sessionId, new HashSet<>());
+ }
+
+ private void setUserIds(String sessionId, Set userIds) {
+ if (userIds == null || userIds.isEmpty()) {
+ userIdsBySessionId.remove(sessionId);
+ } else {
+ userIdsBySessionId.put(sessionId, userIds);
+ }
+ }
+
+ private static record UserId(String iss, String sub) {
+ }
}
diff --git a/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2ClientBeans.java b/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2ClientBeans.java
index 2a63c7336..7fe0ab3f1 100644
--- a/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2ClientBeans.java
+++ b/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2ClientBeans.java
@@ -44,47 +44,28 @@
import lombok.extern.slf4j.Slf4j;
/**
- * The following {@link ConditionalOnMissingBean @ConditionalOnMissingBeans}
- * are auto-configured
+ * The following {@link ConditionalOnMissingBean @ConditionalOnMissingBeans} are auto-configured
*
- * - springAddonsClientFilterChain: a {@link SecurityFilterChain}.
- * Instantiated only if
- * "com.c4-soft.springaddons.security.client.security-matchers" property has at
- * least one entry. If defined, it is with highest precedence, to ensure that
- * all routes defined in this security matcher property are intercepted by this
- * filter-chain.
- * - oAuth2AuthorizationRequestResolver: a
- * {@link OAuth2AuthorizationRequestResolver}. Default instance is a
- * {@link SpringAddonsOAuth2AuthorizationRequestResolver} which sets the client
- * hostname in the redirect URI with
- * {@link SpringAddonsOAuth2ClientProperties#getClientUri()
- * SpringAddonsOAuth2ClientProperties#client-uri}
- * - logoutRequestUriBuilder: builder for RP-Initiated
- * Logout queries, taking configuration from properties for OIDC providers
- * which do not strictly comply with the spec: logout URI not provided by OIDC
- * conf or non standard parameter names (Auth0 and Cognito are samples of such
- * OPs)
- * - logoutSuccessHandler: a {@link LogoutSuccessHandler}. Default
- * instance is a {@link SpringAddonsOAuth2LogoutSuccessHandler} which logs a
- * user out from the last authorization server he logged on.
- * - authoritiesConverter: an {@link OAuth2AuthoritiesConverter}. Default
- * instance is a {@link ConfigurableClaimSet2AuthoritiesConverter} which reads
+ *
- springAddonsClientFilterChain: a {@link SecurityFilterChain}. Instantiated only if "com.c4-soft.springaddons.security.client.security-matchers" property
+ * has at least one entry. If defined, it is with highest precedence, to ensure that all routes defined in this security matcher property are intercepted by
+ * this filter-chain.
+ * - oAuth2AuthorizationRequestResolver: a {@link OAuth2AuthorizationRequestResolver}. Default instance is a
+ * {@link SpringAddonsOAuth2AuthorizationRequestResolver} which sets the client hostname in the redirect URI with
+ * {@link SpringAddonsOAuth2ClientProperties#getClientUri() SpringAddonsOAuth2ClientProperties#client-uri}
+ * - logoutRequestUriBuilder: builder for RP-Initiated Logout queries, taking
+ * configuration from properties for OIDC providers which do not strictly comply with the spec: logout URI not provided by OIDC conf or non standard parameter
+ * names (Auth0 and Cognito are samples of such OPs)
+ * - logoutSuccessHandler: a {@link LogoutSuccessHandler}. Default instance is a {@link SpringAddonsOAuth2LogoutSuccessHandler} which logs a user out from the
+ * last authorization server he logged on.
+ * - authoritiesConverter: an {@link OAuth2AuthoritiesConverter}. Default instance is a {@link ConfigurableClaimSet2AuthoritiesConverter} which reads
* spring-addons {@link SpringAddonsSecurityProperties}
- * - grantedAuthoritiesMapper: a {@link GrantedAuthoritiesMapper} using the
- * already configured {@link OAuth2AuthoritiesConverter}
- * - oAuth2AuthorizedClientRepository: a
- * {@link SpringAddonsOAuth2AuthorizedClientRepository} (which is also a session
- * listener) capable of handling multi-tenancy and back-channel logout.
- * - clientAuthorizePostProcessor: a
- * {@link ClientExpressionInterceptUrlRegistryPostProcessor} post processor to
- * fine tune access control from java configuration. It applies to all routes
- * not listed in "permit-all" property configuration. Default requires users to
- * be authenticated.
- * - clientHttpPostProcessor: a
- * {@link ClientHttpSecurityPostProcessor} to override anything from above
- * auto-configuration. It is called just before the security filter-chain is
- * returned. Default is a no-op.
+ * - grantedAuthoritiesMapper: a {@link GrantedAuthoritiesMapper} using the already configured {@link OAuth2AuthoritiesConverter}
+ * - oAuth2AuthorizedClientRepository: a {@link SpringAddonsOAuth2AuthorizedClientRepository} (which is also a session listener) capable of handling
+ * multi-tenancy and back-channel logout.
+ * - clientAuthorizePostProcessor: a {@link ClientExpressionInterceptUrlRegistryPostProcessor} post processor to fine tune access control from java
+ * configuration. It applies to all routes not listed in "permit-all" property configuration. Default requires users to be authenticated.
+ * - clientHttpPostProcessor: a {@link ClientHttpSecurityPostProcessor} to override anything from above auto-configuration. It is called just before the
+ * security filter-chain is returned. Default is a no-op.
*
*
* @author Jerome Wacongne ch4mp@c4-soft.com
@@ -96,69 +77,48 @@
@Slf4j
public class SpringAddonsOAuth2ClientBeans {
- /**
- *
- * Instantiated only if
- * "com.c4-soft.springaddons.security.client.security-matchers" property has at
- * least one entry. If defined, it is with highest precedence, to ensure that
- * all routes defined in this security matcher property are intercepted by this
- * filter-chain.
- *
- * It defines:
- *
- * - If the path to login page was provided in conf, a @Controller must be
- * provided to handle it. Otherwise Spring Boot default generated one is used
- * (be aware that it does not work when bound to 80 or 8080 with SSL
- * enabled, so, in that case, use another port or define a login path and a
- * controller to handle it)
- * - logout (using {@link SpringAddonsOAuth2LogoutSuccessHandler} by
- * default)
- * - forces SSL usage if it is enabled
- * properties
- * - CSRF protection as defined in spring-addons client properties
- * (enabled by default in this filter-chain).
- * - allow access to unauthorized requests to path matchers listed in
- * spring-security client "permit-all" property
- * - as usual, apply {@link ClientExpressionInterceptUrlRegistryPostProcessor}
- * for access control configuration from Java conf and
- * {@link ClientHttpSecurityPostProcessor} to override anything from the
- * auto-configuration listed above
- *
- *
- * @param http the security filter-chain builder to
- * configure
- * @param serverProperties Spring Boot standard server properties
- * @param authorizationRequestResolver the authorization request resolver to
- * use. By default
- * {@link SpringAddonsOAuth2AuthorizationRequestResolver}
- * @param clientProps {@link SpringAddonsOAuth2ClientProperties
- * spring-addons client properties}
- * @param authorizePostProcessor post process authorization after
- * "permit-all" configuration was applied
- * (default is "isAuthenticated()" to
- * everything that was not matched)
- * @param httpPostProcessor post process the "http" builder just
- * before it is returned (enables to
- * override anything from the
- * auto-configuration)
- * spring-addons client properties}
- * @return a security filter-chain scoped to specified security-matchers and
- * adapted to OAuth2 clients
- * @throws Exception in case of miss-configuration
- */
- @ConditionalOnExpression("!(T(org.springframework.util.StringUtils).isEmpty('${com.c4-soft.springaddons.security.client.security-matchers:}') && T(org.springframework.util.StringUtils).isEmpty('${com.c4-soft.springaddons.security.client.security-matchers[0]:}'))")
- @Order(Ordered.HIGHEST_PRECEDENCE + 1)
- @Bean
- SecurityFilterChain springAddonsClientFilterChain(
- HttpSecurity http,
- ServerProperties serverProperties,
- OAuth2AuthorizationRequestResolver authorizationRequestResolver,
- LogoutSuccessHandler logoutSuccessHandler,
- SpringAddonsOAuth2ClientProperties clientProps,
- ClientExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor,
- ClientHttpSecurityPostProcessor httpPostProcessor)
- throws Exception {
- // @formatter:off
+ /**
+ *
+ * Instantiated only if "com.c4-soft.springaddons.security.client.security-matchers" property has at least one entry. If defined, it is with highest
+ * precedence, to ensure that all routes defined in this security matcher property are intercepted by this filter-chain.
+ *
+ * It defines:
+ *
+ * - If the path to login page was provided in conf, a @Controller must be provided to handle it. Otherwise Spring Boot default generated one is used
+ * (be aware that it does not work when bound to 80 or 8080 with SSL enabled, so, in that case, use another port or define a login path and a controller to
+ * handle it)
+ * - logout (using {@link SpringAddonsOAuth2LogoutSuccessHandler} by default)
+ * - forces SSL usage if it is enabled
properties
+ * - CSRF protection as defined in spring-addons client properties (enabled by default in this filter-chain).
+ * - allow access to unauthorized requests to path matchers listed in spring-security client "permit-all" property
+ * - as usual, apply {@link ClientExpressionInterceptUrlRegistryPostProcessor} for access control configuration from Java conf and
+ * {@link ClientHttpSecurityPostProcessor} to override anything from the auto-configuration listed above
+ *
+ *
+ * @param http the security filter-chain builder to configure
+ * @param serverProperties Spring Boot standard server properties
+ * @param authorizationRequestResolver the authorization request resolver to use. By default {@link SpringAddonsOAuth2AuthorizationRequestResolver}
+ * @param clientProps {@link SpringAddonsOAuth2ClientProperties spring-addons client properties}
+ * @param authorizePostProcessor post process authorization after "permit-all" configuration was applied (default is "isAuthenticated()" to
+ * everything that was not matched)
+ * @param httpPostProcessor post process the "http" builder just before it is returned (enables to override anything from the
+ * auto-configuration) spring-addons client properties}
+ * @return a security filter-chain scoped to specified security-matchers and adapted to OAuth2 clients
+ * @throws Exception in case of miss-configuration
+ */
+ @ConditionalOnExpression("!(T(org.springframework.util.StringUtils).isEmpty('${com.c4-soft.springaddons.security.client.security-matchers:}') && T(org.springframework.util.StringUtils).isEmpty('${com.c4-soft.springaddons.security.client.security-matchers[0]:}'))")
+ @Order(Ordered.HIGHEST_PRECEDENCE + 1)
+ @Bean
+ SecurityFilterChain springAddonsClientFilterChain(
+ HttpSecurity http,
+ ServerProperties serverProperties,
+ OAuth2AuthorizationRequestResolver authorizationRequestResolver,
+ LogoutSuccessHandler logoutSuccessHandler,
+ SpringAddonsOAuth2ClientProperties clientProps,
+ ClientExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor,
+ ClientHttpSecurityPostProcessor httpPostProcessor)
+ throws Exception {
+ // @formatter:off
log.info("Applying client OAuth2 configuration for: {}", (Object[]) clientProps.getSecurityMatchers());
http.securityMatcher(clientProps.getSecurityMatchers());
@@ -177,176 +137,154 @@ SecurityFilterChain springAddonsClientFilterChain(
});
// @formatter:on
- ServletConfigurationSupport.configureClient(http, serverProperties, clientProps, authorizePostProcessor,
- httpPostProcessor);
+ ServletConfigurationSupport.configureClient(http, serverProperties, clientProps, authorizePostProcessor, httpPostProcessor);
- return http.build();
- }
+ return http.build();
+ }
- /**
- * Use a {@link SpringAddonsOAuth2AuthorizationRequestResolver} which takes
- * hostname and port from configuration properties (and works even if SSL is
- * enabled)
- *
- * @param clientRegistrationRepository
- * @param clientProps
- * @return {@link SpringAddonsOAuth2AuthorizationRequestResolver}
- */
- @ConditionalOnMissingBean
- @Bean
- OAuth2AuthorizationRequestResolver oAuth2AuthorizationRequestResolver(
- ClientRegistrationRepository clientRegistrationRepository,
- SpringAddonsOAuth2ClientProperties clientProps) {
- return new SpringAddonsOAuth2AuthorizationRequestResolver(clientRegistrationRepository);
- }
+ /**
+ * Use a {@link SpringAddonsOAuth2AuthorizationRequestResolver} which takes hostname and port from configuration properties (and works even if SSL is
+ * enabled)
+ *
+ * @param clientRegistrationRepository
+ * @param clientProps
+ * @return {@link SpringAddonsOAuth2AuthorizationRequestResolver}
+ */
+ @ConditionalOnMissingBean
+ @Bean
+ OAuth2AuthorizationRequestResolver
+ oAuth2AuthorizationRequestResolver(ClientRegistrationRepository clientRegistrationRepository, SpringAddonsOAuth2ClientProperties clientProps) {
+ return new SpringAddonsOAuth2AuthorizationRequestResolver(clientRegistrationRepository);
+ }
- /**
- * Build logout request for RP-Initiated
- * Logout. It works with most OIDC provider: those complying with the spec
- * (Keycloak for instance), off course, but also those which are close enough to
- * it (Auth0, Cognito, ...)
- *
- * @param clientProps {@link SpringAddonsOAuth2ClientProperties} to pick logout
- * configuration for divergence to the standard (logout URI
- * not provided in .well-known/openid-configuration and
- * non-conform parameter names)
- * @return {@link SpringAddonsOAuth2LogoutRequestUriBuilder]
- */
- @ConditionalOnMissingBean
- @Bean
- LogoutRequestUriBuilder logoutRequestUriBuilder(
- SpringAddonsOAuth2ClientProperties clientProps) {
- return new SpringAddonsOAuth2LogoutRequestUriBuilder(clientProps);
- }
+ /**
+ * Build logout request for RP-Initiated Logout. It works with most OIDC
+ * provider: those complying with the spec (Keycloak for instance), off course, but also those which are close enough to it (Auth0, Cognito, ...)
+ *
+ * @param clientProps {@link SpringAddonsOAuth2ClientProperties} to pick logout configuration for divergence to the standard (logout URI not provided in
+ * .well-known/openid-configuration and non-conform parameter names)
+ * @return {@link SpringAddonsOAuth2LogoutRequestUriBuilder]
+ */
+ @ConditionalOnMissingBean
+ @Bean
+ LogoutRequestUriBuilder logoutRequestUriBuilder(SpringAddonsOAuth2ClientProperties clientProps) {
+ return new SpringAddonsOAuth2LogoutRequestUriBuilder(clientProps);
+ }
- /**
- * Single tenant logout handler for OIDC provider complying to RP-Initiated
- * Logout (or approximately complying to it like Auth0 or Cognito)
- *
- * @param logoutRequestUriBuilder delegate doing the smart job
- * @param clientRegistrationRepository
- * @return {@link SpringAddonsOAuth2LogoutSuccessHandler}
- */
- @ConditionalOnMissingBean
- @Bean
- LogoutSuccessHandler logoutSuccessHandler(LogoutRequestUriBuilder logoutRequestUriBuilder,
- ClientRegistrationRepository clientRegistrationRepository) {
- return new SpringAddonsOAuth2LogoutSuccessHandler(logoutRequestUriBuilder, clientRegistrationRepository);
- }
+ /**
+ * Single tenant logout handler for OIDC provider complying to RP-Initiated
+ * Logout (or approximately complying to it like Auth0 or Cognito)
+ *
+ * @param logoutRequestUriBuilder delegate doing the smart job
+ * @param clientRegistrationRepository
+ * @return {@link SpringAddonsOAuth2LogoutSuccessHandler}
+ */
+ @ConditionalOnMissingBean
+ @Bean
+ LogoutSuccessHandler logoutSuccessHandler(LogoutRequestUriBuilder logoutRequestUriBuilder, ClientRegistrationRepository clientRegistrationRepository) {
+ return new SpringAddonsOAuth2LogoutSuccessHandler(logoutRequestUriBuilder, clientRegistrationRepository);
+ }
- /**
- * Instantiate a {@link ConfigurableClaimSet2AuthoritiesConverter} from token
- * claims to spring authorities (which claims to pick, how to transform roles
- * strings for each claim).
- *
- * @param addonsProperties converter configuration source
- * @return {@link ConfigurableClaimSet2AuthoritiesConverter}
- */
- @ConditionalOnMissingBean
- @Bean
- OAuth2AuthoritiesConverter authoritiesConverter(SpringAddonsSecurityProperties addonsProperties) {
- return new ConfigurableClaimSet2AuthoritiesConverter(addonsProperties);
- }
+ /**
+ * Instantiate a {@link ConfigurableClaimSet2AuthoritiesConverter} from token claims to spring authorities (which claims to pick, how to transform roles
+ * strings for each claim).
+ *
+ * @param addonsProperties converter configuration source
+ * @return {@link ConfigurableClaimSet2AuthoritiesConverter}
+ */
+ @ConditionalOnMissingBean
+ @Bean
+ OAuth2AuthoritiesConverter authoritiesConverter(SpringAddonsSecurityProperties addonsProperties) {
+ return new ConfigurableClaimSet2AuthoritiesConverter(addonsProperties);
+ }
- /**
- *
- * @param authoritiesConverter the authorities converter to use (by default
- * {@link ConfigurableClaimSet2AuthoritiesConverter})
- * @return {@link GrantedAuthoritiesMapper} using the authorities converter in
- * the context
- */
- @ConditionalOnMissingBean
- @Bean
- GrantedAuthoritiesMapper grantedAuthoritiesMapper(
- Converter, Collection extends GrantedAuthority>> authoritiesConverter) {
- return (authorities) -> {
- Set mappedAuthorities = new HashSet<>();
+ /**
+ * @param authoritiesConverter the authorities converter to use (by default {@link ConfigurableClaimSet2AuthoritiesConverter})
+ * @return {@link GrantedAuthoritiesMapper} using the authorities converter in the context
+ */
+ @ConditionalOnMissingBean
+ @Bean
+ GrantedAuthoritiesMapper grantedAuthoritiesMapper(Converter, Collection extends GrantedAuthority>> authoritiesConverter) {
+ return (authorities) -> {
+ Set mappedAuthorities = new HashSet<>();
- authorities.forEach(authority -> {
- if (authority instanceof OidcUserAuthority oidcAuth) {
- mappedAuthorities.addAll(authoritiesConverter.convert(oidcAuth.getIdToken().getClaims()));
+ authorities.forEach(authority -> {
+ if (authority instanceof OidcUserAuthority oidcAuth) {
+ mappedAuthorities.addAll(authoritiesConverter.convert(oidcAuth.getIdToken().getClaims()));
- } else if (authority instanceof OAuth2UserAuthority oauth2Auth) {
- mappedAuthorities.addAll(authoritiesConverter.convert(oauth2Auth.getAttributes()));
+ } else if (authority instanceof OAuth2UserAuthority oauth2Auth) {
+ mappedAuthorities.addAll(authoritiesConverter.convert(oauth2Auth.getAttributes()));
- }
- });
+ }
+ });
- return mappedAuthorities;
- };
- }
+ return mappedAuthorities;
+ };
+ }
- /**
- *
- * @param corsProperties the properties to pick CORS configuration from
- * @return a CORS configuration built from properties
- */
- CorsConfigurationSource corsConfig(CorsProperties[] corsProperties) {
- log.debug("Building default CorsConfigurationSource with: {}", Stream.of(corsProperties).toList());
- final var source = new UrlBasedCorsConfigurationSource();
- for (final var corsProps : corsProperties) {
- final var configuration = new CorsConfiguration();
- configuration.setAllowedOrigins(Arrays.asList(corsProps.getAllowedOrigins()));
- configuration.setAllowedMethods(Arrays.asList(corsProps.getAllowedMethods()));
- configuration.setAllowedHeaders(Arrays.asList(corsProps.getAllowedHeaders()));
- configuration.setExposedHeaders(Arrays.asList(corsProps.getExposedHeaders()));
- source.registerCorsConfiguration(corsProps.getPath(), configuration);
- }
- return source;
- }
+ /**
+ * @param corsProperties the properties to pick CORS configuration from
+ * @return a CORS configuration built from properties
+ */
+ CorsConfigurationSource corsConfig(CorsProperties[] corsProperties) {
+ log.debug("Building default CorsConfigurationSource with: {}", Stream.of(corsProperties).toList());
+ final var source = new UrlBasedCorsConfigurationSource();
+ for (final var corsProps : corsProperties) {
+ final var configuration = new CorsConfiguration();
+ configuration.setAllowedOrigins(Arrays.asList(corsProps.getAllowedOrigins()));
+ configuration.setAllowedMethods(Arrays.asList(corsProps.getAllowedMethods()));
+ configuration.setAllowedHeaders(Arrays.asList(corsProps.getAllowedHeaders()));
+ configuration.setExposedHeaders(Arrays.asList(corsProps.getExposedHeaders()));
+ source.registerCorsConfiguration(corsProps.getPath(), configuration);
+ }
+ return source;
+ }
- /**
- *
- * @param clientRegistrationRepository the OIDC providers configuration
- * @return {@link SpringAddonsOAuth2AuthorizedClientRepository}, an authorized
- * client repository supporting multi-tenancy and exposing the required
- * API for back-channel logout
- */
- @ConditionalOnMissingBean
- @Bean
- SpringAddonsOAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository(
- ClientRegistrationRepository clientRegistrationRepository) {
- return new SpringAddonsOAuth2AuthorizedClientRepository(clientRegistrationRepository);
- }
+ /**
+ * @param clientRegistrationRepository the OIDC providers configuration
+ * @return {@link SpringAddonsOAuth2AuthorizedClientRepository}, an authorized client repository supporting multi-tenancy and
+ * exposing the required API for back-channel logout
+ */
+ @ConditionalOnMissingBean
+ @Bean
+ SpringAddonsOAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository(ClientRegistrationRepository clientRegistrationRepository) {
+ return new SpringAddonsOAuth2AuthorizedClientRepository(clientRegistrationRepository);
+ }
- /**
- * @return a Post processor for access control in Java configuration which
- * requires users to be authenticated. It is called after "permit-all"
- * configuration property was applied.
- */
- @ConditionalOnMissingBean
- @Bean
- ClientExpressionInterceptUrlRegistryPostProcessor clientAuthorizePostProcessor() {
- return registry -> registry.anyRequest().authenticated();
- }
+ /**
+ * @return a Post processor for access control in Java configuration which requires users to be authenticated. It is called after "permit-all" configuration
+ * property was applied.
+ */
+ @ConditionalOnMissingBean
+ @Bean
+ ClientExpressionInterceptUrlRegistryPostProcessor clientAuthorizePostProcessor() {
+ return registry -> registry.anyRequest().authenticated();
+ }
- /**
- *
- * @return a no-op post processor
- */
- @ConditionalOnMissingBean
- @Bean
- ClientHttpSecurityPostProcessor clientHttpPostProcessor() {
- return http -> http;
- }
+ /**
+ * @return a no-op post processor
+ */
+ @ConditionalOnMissingBean
+ @Bean
+ ClientHttpSecurityPostProcessor clientHttpPostProcessor() {
+ return http -> http;
+ }
- static class HasClientSecurityMatcher extends AnyNestedCondition {
+ static class HasClientSecurityMatcher extends AnyNestedCondition {
- public HasClientSecurityMatcher() {
- super(ConfigurationPhase.PARSE_CONFIGURATION);
- }
+ public HasClientSecurityMatcher() {
+ super(ConfigurationPhase.PARSE_CONFIGURATION);
+ }
- @ConditionalOnExpression("!(T(org.springframework.util.StringUtils).isEmpty('${com.c4-soft.springaddons.security.client.security-matchers:}') && T(org.springframework.util.StringUtils).isEmpty('${com.c4-soft.springaddons.security.client.security-matchers[0]:}'))")
- static class Value1Condition {
+ @ConditionalOnExpression("!(T(org.springframework.util.StringUtils).isEmpty('${com.c4-soft.springaddons.security.client.security-matchers:}') && T(org.springframework.util.StringUtils).isEmpty('${com.c4-soft.springaddons.security.client.security-matchers[0]:}'))")
+ static class Value1Condition {
- }
+ }
- @ConditionalOnProperty(name = "com.c4-soft.springaddons.security.client.security-matchers[0]")
- static class Value2Condition {
+ @ConditionalOnProperty(name = "com.c4-soft.springaddons.security.client.security-matchers[0]")
+ static class Value2Condition {
- }
+ }
- }
+ }
}
\ No newline at end of file
diff --git a/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2LogoutSuccessHandler.java b/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2LogoutSuccessHandler.java
index db7f87dab..fb41a3a31 100644
--- a/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2LogoutSuccessHandler.java
+++ b/webmvc/spring-addons-webmvc-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/SpringAddonsOAuth2LogoutSuccessHandler.java
@@ -19,55 +19,41 @@
/**
*
- * Provide with
- * RP-Initiated
- * Logout for authorization-servers fully compliant with OIDC standard as
- * well as those "almost"
- * implementing the spec. It is (auto)configured with
- * {@link SpringAddonsOAuth2ClientProperties}.
+ * Provide with RP-Initiated Logout for authorization-servers fully compliant with
+ * OIDC standard as well as those "almost" implementing the spec. It is (auto)configured with {@link SpringAddonsOAuth2ClientProperties}.
*
- *
*
- * This implementation is not multi-tenant ready. It will terminate the
- * user session on this application as well as on a single authorization-server
- * (the one which emitted the access-token with which the logout request is
- * made).
+ * This implementation is not multi-tenant ready. It will terminate the user session on this application as well as on a single authorization-server (the
+ * one which emitted the access-token with which the logout request is made).
*
- *
*
- * This bean is auto-configured by {@link SpringAddonsOAuth2ClientBeans} as
- * {@link ConditionalOnMissingBean @ConditionalOnMissingBean} of type
+ * This bean is auto-configured by {@link SpringAddonsOAuth2ClientBeans} as {@link ConditionalOnMissingBean @ConditionalOnMissingBean} of type
* {@link LogoutSuccessHandler}. Usage:
*
*
*
* SecurityFilterChain uiFilterChain(HttpSecurity http, LogoutSuccessHandler logoutSuccessHandler) {
- * http.logout().logoutSuccessHandler(logoutSuccessHandler);
+ * http.logout().logoutSuccessHandler(logoutSuccessHandler);
* }
*
*
* @author Jerome Wacongne ch4mp@c4-soft.com
- *
- * @see SpringAddonsOAuth2LogoutRequestUriBuilder
- * @see SpringAddonsOAuth2ClientProperties
- *
+ * @see SpringAddonsOAuth2LogoutRequestUriBuilder
+ * @see SpringAddonsOAuth2ClientProperties
*/
@Data
@RequiredArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class SpringAddonsOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
- private final LogoutRequestUriBuilder uriBuilder;
- private final ClientRegistrationRepository clientRegistrationRepository;
+ private final LogoutRequestUriBuilder uriBuilder;
+ private final ClientRegistrationRepository clientRegistrationRepository;
- @Override
- protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response,
- Authentication authentication) {
- if (authentication instanceof OAuth2AuthenticationToken oauth) {
- final var clientRegistration = clientRegistrationRepository
- .findByRegistrationId(oauth.getAuthorizedClientRegistrationId());
- return uriBuilder.getLogoutRequestUri(clientRegistration, oauth.getName());
- }
- return null;
- }
+ @Override
+ protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
+ if (authentication instanceof OAuth2AuthenticationToken oauth) {
+ final var clientRegistration = clientRegistrationRepository.findByRegistrationId(oauth.getAuthorizedClientRegistrationId());
+ return uriBuilder.getLogoutRequestUri(clientRegistration, oauth.getName());
+ }
+ return null;
+ }
}
\ No newline at end of file
diff --git a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ClientExpressionInterceptUrlRegistryPostProcessor.java b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ClientExpressionInterceptUrlRegistryPostProcessor.java
index a56ce29f9..eedb75637 100644
--- a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ClientExpressionInterceptUrlRegistryPostProcessor.java
+++ b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ClientExpressionInterceptUrlRegistryPostProcessor.java
@@ -4,7 +4,6 @@
* Post processor for access control in Java configuration.
*
* @author Jerome Wacongne ch4mp@c4-soft.com
- *
*/
public interface ClientExpressionInterceptUrlRegistryPostProcessor extends ExpressionInterceptUrlRegistryPostProcessor {
}
\ No newline at end of file
diff --git a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ClientHttpSecurityPostProcessor.java b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ClientHttpSecurityPostProcessor.java
index 01c221d4e..0219f1f4e 100644
--- a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ClientHttpSecurityPostProcessor.java
+++ b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ClientHttpSecurityPostProcessor.java
@@ -1,11 +1,9 @@
package com.c4_soft.springaddons.security.oauth2.config.synchronised;
/**
- * A post-processor to override anything from spring-addons client security
- * filter-chain auto-configuration.
+ * A post-processor to override anything from spring-addons client security filter-chain auto-configuration.
*
* @author Jerome Wacongne ch4mp@c4-soft.com
- *
*/
public interface ClientHttpSecurityPostProcessor extends ServerHttpSecurityPostProcessor {
}
\ No newline at end of file
diff --git a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ExpressionInterceptUrlRegistryPostProcessor.java b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ExpressionInterceptUrlRegistryPostProcessor.java
index c53ab6086..3beea5559 100644
--- a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ExpressionInterceptUrlRegistryPostProcessor.java
+++ b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ExpressionInterceptUrlRegistryPostProcessor.java
@@ -9,9 +9,8 @@
* Customize access-control for routes which where not listed in {@link SpringAddonsSecurityProperties#permitAll}
*
* @author ch4mp
- *
*/
public interface ExpressionInterceptUrlRegistryPostProcessor {
- AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry authorizeHttpRequests(
- AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry registry);
+ AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry
+ authorizeHttpRequests(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry registry);
}
diff --git a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/HttpServletRequestSupport.java b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/HttpServletRequestSupport.java
index 1529e8d61..e8ee54062 100644
--- a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/HttpServletRequestSupport.java
+++ b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/HttpServletRequestSupport.java
@@ -21,100 +21,91 @@
* Support class to statically access current request.
*
*
- * It is mainly intended at parsing additional headers when authorizing
- * requests.
+ * It is mainly intended at parsing additional headers when authorizing requests.
*
*
* @author ch4mp
*/
public class HttpServletRequestSupport {
- /**
- * @return the request in current context
- */
- public static Optional getRequest() {
- RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
- if (requestAttributes instanceof ServletRequestAttributes attr) {
- return Optional.ofNullable(attr.getRequest());
- }
- return Optional.empty();
- }
-
- public static Optional getSession() {
- return getRequest().flatMap(req -> Optional.ofNullable(req.getSession()));
- }
-
- public static Object getSessionAttribute(String name) {
- return getSession().map(session -> session.getAttribute(name)).orElse(null);
- }
-
- public static void setSessionAttribute(String name, Object value) {
- getSession().ifPresent(session -> session.setAttribute(name, value));
- }
-
- /**
- * @param headerName name of the header to retrieve
- * @return the unique value for the given header in current request
- * @throws MissingHeaderException if no non-empty value is found for that
- * header
- * @throws MultiValuedHeaderException if more than one non-empty value is found
- * for that header
- */
- public static String getUniqueRequestHeader(String headerName)
- throws MissingHeaderException, MultiValuedHeaderException {
- final var headers = getNonEmptyRequestHeaderValues(headerName);
- if (headers.size() < 1) {
- throw new MissingHeaderException(headerName);
- }
- if (headers.size() > 1) {
- throw new MultiValuedHeaderException(headerName);
- }
- return headers.get(0);
- }
-
- /**
- * @param headerName the name of the header to retrieve
- * @return a stream of non empty values for a given header from the request in
- * current context
- */
- public static List getNonEmptyRequestHeaderValues(String headerName) {
- return getRequest()
- .map(
- req -> StreamSupport
- .stream(Spliterators.spliteratorUnknownSize(req.getHeaders(headerName).asIterator(),
- Spliterator.ORDERED), false)
- .filter(StringUtils::hasLength)
- .toList())
- .orElse(List.of());
- }
-
- @ResponseStatus(code = HttpStatus.UNAUTHORIZED)
- public static class MissingHeaderException extends RuntimeException {
- private static final long serialVersionUID = -4894061353773464761L;
-
- public MissingHeaderException(String headerName) {
- super(headerName + " is missing");
- assert (StringUtils.hasText(headerName));
- }
- }
-
- @ResponseStatus(code = HttpStatus.UNAUTHORIZED)
- public static class MultiValuedHeaderException extends RuntimeException {
- private static final long serialVersionUID = 1654993007508549674L;
-
- public MultiValuedHeaderException(String headerName) {
- super(headerName + " is not unique");
- assert (StringUtils.hasText(headerName));
- }
- }
-
- @ResponseStatus(code = HttpStatus.UNAUTHORIZED)
- public static class InvalidHeaderException extends RuntimeException {
- private static final long serialVersionUID = -6233252290377524340L;
-
- public InvalidHeaderException(String headerName) {
- super(headerName + " is not valid");
- assert (StringUtils.hasText(headerName));
- }
- }
+ /**
+ * @return the request in current context
+ */
+ public static Optional getRequest() {
+ RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
+ if (requestAttributes instanceof ServletRequestAttributes attr) {
+ return Optional.ofNullable(attr.getRequest());
+ }
+ return Optional.empty();
+ }
+
+ public static Optional getSession() {
+ return getRequest().flatMap(req -> Optional.ofNullable(req.getSession()));
+ }
+
+ public static Object getSessionAttribute(String name) {
+ return getSession().map(session -> session.getAttribute(name)).orElse(null);
+ }
+
+ public static void setSessionAttribute(String name, Object value) {
+ getSession().ifPresent(session -> session.setAttribute(name, value));
+ }
+
+ /**
+ * @param headerName name of the header to retrieve
+ * @return the unique value for the given header in current request
+ * @throws MissingHeaderException if no non-empty value is found for that header
+ * @throws MultiValuedHeaderException if more than one non-empty value is found for that header
+ */
+ public static String getUniqueRequestHeader(String headerName) throws MissingHeaderException, MultiValuedHeaderException {
+ final var headers = getNonEmptyRequestHeaderValues(headerName);
+ if (headers.size() < 1) {
+ throw new MissingHeaderException(headerName);
+ }
+ if (headers.size() > 1) {
+ throw new MultiValuedHeaderException(headerName);
+ }
+ return headers.get(0);
+ }
+
+ /**
+ * @param headerName the name of the header to retrieve
+ * @return a stream of non empty values for a given header from the request in current context
+ */
+ public static List getNonEmptyRequestHeaderValues(String headerName) {
+ return getRequest().map(
+ req -> StreamSupport.stream(Spliterators.spliteratorUnknownSize(req.getHeaders(headerName).asIterator(), Spliterator.ORDERED), false)
+ .filter(StringUtils::hasLength).toList())
+ .orElse(List.of());
+ }
+
+ @ResponseStatus(code = HttpStatus.UNAUTHORIZED)
+ public static class MissingHeaderException extends RuntimeException {
+ private static final long serialVersionUID = -4894061353773464761L;
+
+ public MissingHeaderException(String headerName) {
+ super(headerName + " is missing");
+ assert (StringUtils.hasText(headerName));
+ }
+ }
+
+ @ResponseStatus(code = HttpStatus.UNAUTHORIZED)
+ public static class MultiValuedHeaderException extends RuntimeException {
+ private static final long serialVersionUID = 1654993007508549674L;
+
+ public MultiValuedHeaderException(String headerName) {
+ super(headerName + " is not unique");
+ assert (StringUtils.hasText(headerName));
+ }
+ }
+
+ @ResponseStatus(code = HttpStatus.UNAUTHORIZED)
+ public static class InvalidHeaderException extends RuntimeException {
+ private static final long serialVersionUID = -6233252290377524340L;
+
+ public InvalidHeaderException(String headerName) {
+ super(headerName + " is not valid");
+ assert (StringUtils.hasText(headerName));
+ }
+ }
}
\ No newline at end of file
diff --git a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/OAuth2AuthenticationFactory.java b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/OAuth2AuthenticationFactory.java
index 45265723e..7526645d9 100644
--- a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/OAuth2AuthenticationFactory.java
+++ b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/OAuth2AuthenticationFactory.java
@@ -5,5 +5,5 @@
import org.springframework.security.authentication.AbstractAuthenticationToken;
public interface OAuth2AuthenticationFactory {
- AbstractAuthenticationToken build(String bearerString, Map claims);
+ AbstractAuthenticationToken build(String bearerString, Map claims);
}
diff --git a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ResourceServerExpressionInterceptUrlRegistryPostProcessor.java b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ResourceServerExpressionInterceptUrlRegistryPostProcessor.java
index e3b624a01..1636cc57a 100644
--- a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ResourceServerExpressionInterceptUrlRegistryPostProcessor.java
+++ b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ResourceServerExpressionInterceptUrlRegistryPostProcessor.java
@@ -1,5 +1,4 @@
package com.c4_soft.springaddons.security.oauth2.config.synchronised;
-public interface ResourceServerExpressionInterceptUrlRegistryPostProcessor
- extends ExpressionInterceptUrlRegistryPostProcessor {
+public interface ResourceServerExpressionInterceptUrlRegistryPostProcessor extends ExpressionInterceptUrlRegistryPostProcessor {
}
diff --git a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ResourceServerHttpSecurityPostProcessor.java b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ResourceServerHttpSecurityPostProcessor.java
index fdd466cce..e2694529e 100644
--- a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ResourceServerHttpSecurityPostProcessor.java
+++ b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ResourceServerHttpSecurityPostProcessor.java
@@ -3,12 +3,10 @@
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
/**
- * Process {@link HttpSecurity} of default security filter-chain after it was
- * processed by spring-addons.
- * This enables to override anything that was auto-configured (or add to it).
+ * Process {@link HttpSecurity} of default security filter-chain after it was processed by spring-addons. This enables to override anything that was
+ * auto-configured (or add to it).
*
* @author ch4mp
- *
*/
public interface ResourceServerHttpSecurityPostProcessor extends ServerHttpSecurityPostProcessor {
}
diff --git a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ServerHttpSecurityPostProcessor.java b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ServerHttpSecurityPostProcessor.java
index f68165db6..9c5530f72 100644
--- a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ServerHttpSecurityPostProcessor.java
+++ b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ServerHttpSecurityPostProcessor.java
@@ -3,5 +3,5 @@
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
public interface ServerHttpSecurityPostProcessor {
- HttpSecurity process(HttpSecurity httpSecurity) throws Exception;
+ HttpSecurity process(HttpSecurity httpSecurity) throws Exception;
}
diff --git a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ServletConfigurationSupport.java b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ServletConfigurationSupport.java
index 0818e6d76..0b2eabdb9 100644
--- a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ServletConfigurationSupport.java
+++ b/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ServletConfigurationSupport.java
@@ -29,140 +29,131 @@
public class ServletConfigurationSupport {
- public static HttpSecurity configureResourceServer(
- HttpSecurity http,
- ServerProperties serverProperties,
- SpringAddonsSecurityProperties addonsResourceServerProperties,
- ResourceServerExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor,
- ResourceServerHttpSecurityPostProcessor httpPostProcessor) throws Exception {
-
- ServletConfigurationSupport.configureCors(http, addonsResourceServerProperties.getCors());
- ServletConfigurationSupport.configureState(http, addonsResourceServerProperties.isStatlessSessions(),
- addonsResourceServerProperties.getCsrf());
- ServletConfigurationSupport.configureAccess(http, addonsResourceServerProperties.getPermitAll(),
- authorizePostProcessor);
-
- if (!addonsResourceServerProperties.isRedirectToLoginIfUnauthorizedOnRestrictedContent()) {
- http.exceptionHandling(exceptionHandling -> exceptionHandling
- .authenticationEntryPoint((request, response, authException) -> {
- response.addHeader(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Restricted Content\"");
- response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
- }));
- }
-
- if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) {
- http.requiresChannel(channel -> channel.anyRequest().requiresSecure());
- }
-
- return httpPostProcessor.process(http);
- }
-
- public static HttpSecurity configureClient(
- HttpSecurity http,
- ServerProperties serverProperties,
- SpringAddonsOAuth2ClientProperties addonsClientProperties,
- ClientExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor,
- ClientHttpSecurityPostProcessor httpPostProcessor) throws Exception {
-
- ServletConfigurationSupport.configureCors(http, addonsClientProperties.getCors());
- ServletConfigurationSupport.configureState(http, false, addonsClientProperties.getCsrf());
- ServletConfigurationSupport.configureAccess(http, addonsClientProperties.getPermitAll(),
- authorizePostProcessor);
-
- if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) {
- http.requiresChannel(channel -> channel.anyRequest().requiresSecure());
- }
-
- return httpPostProcessor.process(http);
- }
-
- public static HttpSecurity configureAccess(HttpSecurity http, String[] permitAll,
- ExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor) throws Exception {
- if (permitAll.length > 0) {
- http.anonymous(withDefaults());
- http.authorizeHttpRequests(registry -> authorizePostProcessor
- .authorizeHttpRequests(registry.requestMatchers(permitAll).permitAll()));
- } else {
- http.authorizeHttpRequests(registry -> authorizePostProcessor.authorizeHttpRequests(registry));
- }
- return http;
- }
-
- public static HttpSecurity configureCors(HttpSecurity http, CorsProperties[] corsProperties) throws Exception {
- if (corsProperties.length == 0) {
- http.cors(cors -> cors.disable());
- } else {
- final var source = new UrlBasedCorsConfigurationSource();
- for (final var corsProps : corsProperties) {
- final var configuration = new CorsConfiguration();
- configuration.setAllowedOrigins(Arrays.asList(corsProps.getAllowedOrigins()));
- configuration.setAllowedMethods(Arrays.asList(corsProps.getAllowedMethods()));
- configuration.setAllowedHeaders(Arrays.asList(corsProps.getAllowedHeaders()));
- configuration.setExposedHeaders(Arrays.asList(corsProps.getExposedHeaders()));
- source.registerCorsConfiguration(corsProps.getPath(), configuration);
- }
- http.cors(cors -> cors.configurationSource(source));
- }
- return http;
- }
-
- public static HttpSecurity configureState(
- HttpSecurity http,
- boolean isStatless,
- SpringAddonsSecurityProperties.Csrf csrfEnum) throws Exception {
-
- if (isStatless) {
- http.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
- }
-
- http.csrf(configurer -> {
- final var delegate = new XorCsrfTokenRequestAttributeHandler();
- delegate.setCsrfRequestAttributeName("_csrf");
- switch (csrfEnum) {
- case DISABLE:
- configurer.disable();
- break;
- case DEFAULT:
- if (isStatless) {
- configurer.disable();
- }
- break;
- case SESSION:
- break;
- case COOKIE_HTTP_ONLY:
- configurer.csrfTokenRepository(new CookieCsrfTokenRepository())
- .csrfTokenRequestHandler(delegate::handle);
- http.addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class);
- break;
- case COOKIE_ACCESSIBLE_FROM_JS:
- // Adapted from
- // https://docs.spring.io/spring-security/reference/5.8/migration/servlet/exploits.html#_i_am_using_angularjs_or_another_javascript_framework
- configurer.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
- .csrfTokenRequestHandler(delegate::handle);
- http.addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class);
- break;
- }
- });
-
- return http;
- }
-
- /**
- * https://docs.spring.io/spring-security/reference/5.8/migration/servlet/exploits.html#_i_am_using_a_single_page_application_with_cookiecsrftokenrepository
- *
- */
- private static final class CsrfCookieFilter extends OncePerRequestFilter {
-
- @Override
- protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
- FilterChain filterChain)
- throws ServletException, IOException {
- CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
- // Render the token value to a cookie by causing the deferred token to be loaded
- csrfToken.getToken();
-
- filterChain.doFilter(request, response);
- }
-
- }
+ public static HttpSecurity configureResourceServer(
+ HttpSecurity http,
+ ServerProperties serverProperties,
+ SpringAddonsSecurityProperties addonsResourceServerProperties,
+ ResourceServerExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor,
+ ResourceServerHttpSecurityPostProcessor httpPostProcessor)
+ throws Exception {
+
+ ServletConfigurationSupport.configureCors(http, addonsResourceServerProperties.getCors());
+ ServletConfigurationSupport.configureState(http, addonsResourceServerProperties.isStatlessSessions(), addonsResourceServerProperties.getCsrf());
+ ServletConfigurationSupport.configureAccess(http, addonsResourceServerProperties.getPermitAll(), authorizePostProcessor);
+
+ if (!addonsResourceServerProperties.isRedirectToLoginIfUnauthorizedOnRestrictedContent()) {
+ http.exceptionHandling(exceptionHandling -> exceptionHandling.authenticationEntryPoint((request, response, authException) -> {
+ response.addHeader(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Restricted Content\"");
+ response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
+ }));
+ }
+
+ if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) {
+ http.requiresChannel(channel -> channel.anyRequest().requiresSecure());
+ }
+
+ return httpPostProcessor.process(http);
+ }
+
+ public static HttpSecurity configureClient(
+ HttpSecurity http,
+ ServerProperties serverProperties,
+ SpringAddonsOAuth2ClientProperties addonsClientProperties,
+ ClientExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor,
+ ClientHttpSecurityPostProcessor httpPostProcessor)
+ throws Exception {
+
+ ServletConfigurationSupport.configureCors(http, addonsClientProperties.getCors());
+ ServletConfigurationSupport.configureState(http, false, addonsClientProperties.getCsrf());
+ ServletConfigurationSupport.configureAccess(http, addonsClientProperties.getPermitAll(), authorizePostProcessor);
+
+ if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) {
+ http.requiresChannel(channel -> channel.anyRequest().requiresSecure());
+ }
+
+ return httpPostProcessor.process(http);
+ }
+
+ public static HttpSecurity configureAccess(HttpSecurity http, String[] permitAll, ExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor)
+ throws Exception {
+ if (permitAll.length > 0) {
+ http.anonymous(withDefaults());
+ http.authorizeHttpRequests(registry -> authorizePostProcessor.authorizeHttpRequests(registry.requestMatchers(permitAll).permitAll()));
+ } else {
+ http.authorizeHttpRequests(registry -> authorizePostProcessor.authorizeHttpRequests(registry));
+ }
+ return http;
+ }
+
+ public static HttpSecurity configureCors(HttpSecurity http, CorsProperties[] corsProperties) throws Exception {
+ if (corsProperties.length == 0) {
+ http.cors(cors -> cors.disable());
+ } else {
+ final var source = new UrlBasedCorsConfigurationSource();
+ for (final var corsProps : corsProperties) {
+ final var configuration = new CorsConfiguration();
+ configuration.setAllowedOrigins(Arrays.asList(corsProps.getAllowedOrigins()));
+ configuration.setAllowedMethods(Arrays.asList(corsProps.getAllowedMethods()));
+ configuration.setAllowedHeaders(Arrays.asList(corsProps.getAllowedHeaders()));
+ configuration.setExposedHeaders(Arrays.asList(corsProps.getExposedHeaders()));
+ source.registerCorsConfiguration(corsProps.getPath(), configuration);
+ }
+ http.cors(cors -> cors.configurationSource(source));
+ }
+ return http;
+ }
+
+ public static HttpSecurity configureState(HttpSecurity http, boolean isStatless, SpringAddonsSecurityProperties.Csrf csrfEnum) throws Exception {
+
+ if (isStatless) {
+ http.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
+ }
+
+ http.csrf(configurer -> {
+ final var delegate = new XorCsrfTokenRequestAttributeHandler();
+ delegate.setCsrfRequestAttributeName("_csrf");
+ switch (csrfEnum) {
+ case DISABLE:
+ configurer.disable();
+ break;
+ case DEFAULT:
+ if (isStatless) {
+ configurer.disable();
+ }
+ break;
+ case SESSION:
+ break;
+ case COOKIE_HTTP_ONLY:
+ configurer.csrfTokenRepository(new CookieCsrfTokenRepository()).csrfTokenRequestHandler(delegate::handle);
+ http.addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class);
+ break;
+ case COOKIE_ACCESSIBLE_FROM_JS:
+ // Adapted from
+ // https://docs.spring.io/spring-security/reference/5.8/migration/servlet/exploits.html#_i_am_using_angularjs_or_another_javascript_framework
+ configurer.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).csrfTokenRequestHandler(delegate::handle);
+ http.addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class);
+ break;
+ }
+ });
+
+ return http;
+ }
+
+ /**
+ * https://docs.spring.io/spring-security/reference/5.8/migration/servlet/exploits.html#_i_am_using_a_single_page_application_with_cookiecsrftokenrepository
+ */
+ private static final class CsrfCookieFilter extends OncePerRequestFilter {
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
+ throws ServletException,
+ IOException {
+ CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
+ // Render the token value to a cookie by causing the deferred token to be loaded
+ csrfToken.getToken();
+
+ filterChain.doFilter(request, response);
+ }
+
+ }
}
diff --git a/webmvc/spring-addons-webmvc-introspecting-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsSecurityBeans.java b/webmvc/spring-addons-webmvc-introspecting-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsSecurityBeans.java
index 04f4e51db..18987aac6 100644
--- a/webmvc/spring-addons-webmvc-introspecting-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsSecurityBeans.java
+++ b/webmvc/spring-addons-webmvc-introspecting-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsSecurityBeans.java
@@ -19,18 +19,16 @@
@Import({ SpringAddonsSecurityProperties.class })
public class AddonsSecurityBeans {
- /**
- * Retrieves granted authorities from the introspected token attributes,
- * according to configuration set for the issuer set in this
- * attributes
- *
- * @param securityProperties
- * @return
- */
- @ConditionalOnMissingBean
- @Bean
- OAuth2AuthoritiesConverter authoritiesConverter(SpringAddonsSecurityProperties addonsProperties) {
- log.debug("Building default SimpleJwtGrantedAuthoritiesConverter with: {}", addonsProperties);
- return new ConfigurableClaimSet2AuthoritiesConverter(addonsProperties);
- }
+ /**
+ * Retrieves granted authorities from the introspected token attributes, according to configuration set for the issuer set in this attributes
+ *
+ * @param securityProperties
+ * @return
+ */
+ @ConditionalOnMissingBean
+ @Bean
+ OAuth2AuthoritiesConverter authoritiesConverter(SpringAddonsSecurityProperties addonsProperties) {
+ log.debug("Building default SimpleJwtGrantedAuthoritiesConverter with: {}", addonsProperties);
+ return new ConfigurableClaimSet2AuthoritiesConverter(addonsProperties);
+ }
}
\ No newline at end of file
diff --git a/webmvc/spring-addons-webmvc-introspecting-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsWebSecurityBeans.java b/webmvc/spring-addons-webmvc-introspecting-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsWebSecurityBeans.java
index a0c08706f..71a611d6a 100644
--- a/webmvc/spring-addons-webmvc-introspecting-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsWebSecurityBeans.java
+++ b/webmvc/spring-addons-webmvc-introspecting-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsWebSecurityBeans.java
@@ -43,37 +43,24 @@
/**
*
* Usage
- * If not using spring-boot, @Import or @ComponentScan this class. All
- * beans defined here are @ConditionalOnMissingBean =>
- * just define your own @Beans to override.
+ * If not using spring-boot, @Import or @ComponentScan this class. All beans defined here are @ConditionalOnMissingBean => just define your own
+ * @Beans to override.
*
*
* Provided @Beans
*
*
- * - springAddonsResourceServerSecurityFilterChain: applies CORS, CSRF,
- * anonymous, sessionCreationPolicy, SSL, redirect and 401 instead of redirect
- * to login as defined in springAddonsResourceServerSecurityFilterChain: applies CORS, CSRF, anonymous, sessionCreationPolicy, SSL, redirect and 401 instead of redirect to login
+ * as defined in SpringAddonsSecurityProperties
- * - authorizePostProcessor: a bean of type
- * {@link ExpressionInterceptUrlRegistryPostProcessor} to fine
- * tune access
- * control from java configuration. It applies to all routes not listed in
- * "permit-all" property configuration. Default requires users to be
- * authenticated. This is a bean to provide in your application configuration
- * if you prefer to define fine-grained access control rules with Java
- * configuration rather than methods security.
- * - httpPostProcessor: a bean of type
- * {@link ResourceServerHttpSecurityPostProcessor} to
- * override anything from above auto-configuration. It is called just before the
- * security filter-chain is returned. Default is a no-op.
- * - introspectionAuthenticationConverter: a converter from a successful
- * introspection to something inheriting from
- * {@link AbstractAuthenticationToken}. The default instantiate a
- * `BearerTokenAuthentication` with authorities mapping as configured for the
- * issuer declared in the introspected claims. The easiest to override the type
- * of {@link AbstractAuthenticationToken}, is to provide with an
- * {@link OAuth2AuthenticationFactory} bean.
+ * - authorizePostProcessor: a bean of type {@link ExpressionInterceptUrlRegistryPostProcessor} to fine tune access control from java configuration. It
+ * applies to all routes not listed in "permit-all" property configuration. Default requires users to be authenticated. This is a bean to provide in your
+ * application configuration if you prefer to define fine-grained access control rules with Java configuration rather than methods security.
+ * - httpPostProcessor: a bean of type {@link ResourceServerHttpSecurityPostProcessor} to override anything from above auto-configuration. It is called just
+ * before the security filter-chain is returned. Default is a no-op.
+ * - introspectionAuthenticationConverter: a converter from a successful introspection to something inheriting from {@link AbstractAuthenticationToken}. The
+ * default instantiate a `BearerTokenAuthentication` with authorities mapping as configured for the issuer declared in the introspected claims. The easiest to
+ * override the type of {@link AbstractAuthenticationToken}, is to provide with an {@link OAuth2AuthenticationFactory} bean.
*
*
* @author Jerome Wacongne ch4mp@c4-soft.com
@@ -85,143 +72,118 @@
@Import({ AddonsSecurityBeans.class })
public class AddonsWebSecurityBeans {
- /**
- *
- * Applies SpringAddonsSecurityProperties to web security config. Be aware that
- * defining a {@link SecurityWebFilterChain} bean with no security matcher and
- * an order higher than LOWEST_PRECEDENCE will disable most of this lib
- * auto-configuration for OpenID resource-servers.
- *
- *
- * You should consider to set security matcher to all other
- * {@link SecurityWebFilterChain} beans and provide
- * a {@link ServerHttpSecurityPostProcessor} bean to override anything from this
- * bean
- *
- * .
- *
- * @param http HTTP security to configure
- * @param serverProperties Spring "server" configuration
- * properties
- * @param addonsProperties "com.c4-soft.springaddons.security"
- * configuration properties
- * @param authorizePostProcessor Hook to override access-control
- * rules for all path that are not
- * listed in "permit-all"
- * @param httpPostProcessor Hook to override all or part of
- * HttpSecurity auto-configuration
- * @param introspectionAuthenticationConverter Converts successful introspection
- * result into an
- * {@link Authentication}
- * @return A default {@link SecurityWebFilterChain} for servlet resource-servers
- * with access-token introspection (matches all unmatched routes with
- * lowest precedence)
- */
- @Order(Ordered.LOWEST_PRECEDENCE)
- @Bean
- SecurityFilterChain springAddonsResourceServerSecurityFilterChain(
- HttpSecurity http,
- ServerProperties serverProperties,
- SpringAddonsSecurityProperties addonsProperties,
- ResourceServerExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor,
- ResourceServerHttpSecurityPostProcessor httpPostProcessor,
- OpaqueTokenAuthenticationConverter introspectionAuthenticationConverter,
- OpaqueTokenIntrospector opaqueTokenIntrospector)
- throws Exception {
- http.oauth2ResourceServer(server -> server.opaqueToken(ot -> {
- ot.introspector(opaqueTokenIntrospector);
- ot.authenticationConverter(introspectionAuthenticationConverter);
- }));
+ /**
+ *
+ * Applies SpringAddonsSecurityProperties to web security config. Be aware that defining a {@link SecurityWebFilterChain} bean with no security matcher and
+ * an order higher than LOWEST_PRECEDENCE will disable most of this lib auto-configuration for OpenID resource-servers.
+ *
+ *
+ * You should consider to set security matcher to all other {@link SecurityWebFilterChain} beans and provide a {@link ServerHttpSecurityPostProcessor} bean
+ * to override anything from this bean
+ *
+ * .
+ *
+ * @param http HTTP security to configure
+ * @param serverProperties Spring "server" configuration properties
+ * @param addonsProperties "com.c4-soft.springaddons.security" configuration properties
+ * @param authorizePostProcessor Hook to override access-control rules for all path that are not listed in "permit-all"
+ * @param httpPostProcessor Hook to override all or part of HttpSecurity auto-configuration
+ * @param introspectionAuthenticationConverter Converts successful introspection result into an {@link Authentication}
+ * @return A default {@link SecurityWebFilterChain} for servlet resource-servers with access-token introspection
+ * (matches all unmatched routes with lowest precedence)
+ */
+ @Order(Ordered.LOWEST_PRECEDENCE)
+ @Bean
+ SecurityFilterChain springAddonsResourceServerSecurityFilterChain(
+ HttpSecurity http,
+ ServerProperties serverProperties,
+ SpringAddonsSecurityProperties addonsProperties,
+ ResourceServerExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor,
+ ResourceServerHttpSecurityPostProcessor httpPostProcessor,
+ OpaqueTokenAuthenticationConverter introspectionAuthenticationConverter,
+ OpaqueTokenIntrospector opaqueTokenIntrospector)
+ throws Exception {
+ http.oauth2ResourceServer(server -> server.opaqueToken(ot -> {
+ ot.introspector(opaqueTokenIntrospector);
+ ot.authenticationConverter(introspectionAuthenticationConverter);
+ }));
- ServletConfigurationSupport.configureResourceServer(http, serverProperties, addonsProperties,
- authorizePostProcessor, httpPostProcessor);
+ ServletConfigurationSupport.configureResourceServer(http, serverProperties, addonsProperties, authorizePostProcessor, httpPostProcessor);
- return http.build();
- }
+ return http.build();
+ }
- /**
- * Hook to override security rules for all path that are not listed in
- * "permit-all". Default is isAuthenticated().
- *
- * @return a hook to override security rules for all path that are not listed in
- * "permit-all". Default is isAuthenticated().
- */
- @ConditionalOnMissingBean
- @Bean
- ResourceServerExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor() {
- return registry -> registry.anyRequest().authenticated();
- }
+ /**
+ * Hook to override security rules for all path that are not listed in "permit-all". Default is isAuthenticated().
+ *
+ * @return a hook to override security rules for all path that are not listed in "permit-all". Default is isAuthenticated().
+ */
+ @ConditionalOnMissingBean
+ @Bean
+ ResourceServerExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor() {
+ return registry -> registry.anyRequest().authenticated();
+ }
- /**
- * Hook to override all or part of HttpSecurity auto-configuration.
- * Called after spring-addons configuration was applied so that you can
- * modify anything
- *
- * @return a hook to override all or part of HttpSecurity auto-configuration.
- * Called after spring-addons configuration was applied so that you can
- * modify anything
- */
- @ConditionalOnMissingBean
- @Bean
- ResourceServerHttpSecurityPostProcessor httpPostProcessor() {
- return httpSecurity -> httpSecurity;
- }
+ /**
+ * Hook to override all or part of HttpSecurity auto-configuration. Called after spring-addons configuration was applied so that you can modify anything
+ *
+ * @return a hook to override all or part of HttpSecurity auto-configuration. Called after spring-addons configuration was applied so that you can modify
+ * anything
+ */
+ @ConditionalOnMissingBean
+ @Bean
+ ResourceServerHttpSecurityPostProcessor httpPostProcessor() {
+ return httpSecurity -> httpSecurity;
+ }
- CorsConfigurationSource corsConfig(CorsProperties[] corsProperties) {
- log.debug("Building default CorsConfigurationSource with: {}", Stream.of(corsProperties).toList());
- final var source = new UrlBasedCorsConfigurationSource();
- for (final var corsProps : corsProperties) {
- final var configuration = new CorsConfiguration();
- configuration.setAllowedOrigins(Arrays.asList(corsProps.getAllowedOrigins()));
- configuration.setAllowedMethods(Arrays.asList(corsProps.getAllowedMethods()));
- configuration.setAllowedHeaders(Arrays.asList(corsProps.getAllowedHeaders()));
- configuration.setExposedHeaders(Arrays.asList(corsProps.getExposedHeaders()));
- source.registerCorsConfiguration(corsProps.getPath(), configuration);
- }
- return source;
- }
+ CorsConfigurationSource corsConfig(CorsProperties[] corsProperties) {
+ log.debug("Building default CorsConfigurationSource with: {}", Stream.of(corsProperties).toList());
+ final var source = new UrlBasedCorsConfigurationSource();
+ for (final var corsProps : corsProperties) {
+ final var configuration = new CorsConfiguration();
+ configuration.setAllowedOrigins(Arrays.asList(corsProps.getAllowedOrigins()));
+ configuration.setAllowedMethods(Arrays.asList(corsProps.getAllowedMethods()));
+ configuration.setAllowedHeaders(Arrays.asList(corsProps.getAllowedHeaders()));
+ configuration.setExposedHeaders(Arrays.asList(corsProps.getExposedHeaders()));
+ source.registerCorsConfiguration(corsProps.getPath(), configuration);
+ }
+ return source;
+ }
- /**
- * Converter bean from successful introspection result to an
- * {@link Authentication} instance
- *
- * @param authoritiesConverter converts access-token claims into Spring
- * authorities
- * @param authenticationFactory builds an {@link Authentication} instance from
- * access-token string and claims
- * @return a converter from successful introspection result to an
- * {@link Authentication} instance
- */
- @SuppressWarnings("unchecked")
- @ConditionalOnMissingBean
- @Bean
- OpaqueTokenAuthenticationConverter introspectionAuthenticationConverter(
- Converter, Collection extends GrantedAuthority>> authoritiesConverter,
- Optional authenticationFactory,
- SpringAddonsSecurityProperties addonsProperties,
- OAuth2ResourceServerProperties resourceServerProperties) {
- return (String introspectedToken, OAuth2AuthenticatedPrincipal authenticatedPrincipal) -> {
- return authenticationFactory
- .map(af -> af.build(introspectedToken, authenticatedPrincipal.getAttributes())).orElse(
- new BearerTokenAuthentication(
- new OAuth2IntrospectionAuthenticatedPrincipal(
- new OpenidClaimSet(authenticatedPrincipal.getAttributes(),
- Stream.of(addonsProperties.getIssuers())
- .filter(issProps -> resourceServerProperties
- .getOpaquetoken().getIntrospectionUri()
- .contains(issProps.getLocation().toString()))
- .findAny().orElse(addonsProperties.getIssuers()[0])
- .getUsernameClaim())
- .getName(),
- authenticatedPrincipal.getAttributes(),
- (Collection) authenticatedPrincipal.getAuthorities()),
- new OAuth2AccessToken(
- OAuth2AccessToken.TokenType.BEARER,
- introspectedToken,
- authenticatedPrincipal.getAttribute(OAuth2TokenIntrospectionClaimNames.IAT),
- authenticatedPrincipal
- .getAttribute(OAuth2TokenIntrospectionClaimNames.EXP)),
- authoritiesConverter.convert(authenticatedPrincipal.getAttributes())));
- };
- }
+ /**
+ * Converter bean from successful introspection result to an {@link Authentication} instance
+ *
+ * @param authoritiesConverter converts access-token claims into Spring authorities
+ * @param authenticationFactory builds an {@link Authentication} instance from access-token string and claims
+ * @return a converter from successful introspection result to an {@link Authentication} instance
+ */
+ @SuppressWarnings("unchecked")
+ @ConditionalOnMissingBean
+ @Bean
+ OpaqueTokenAuthenticationConverter introspectionAuthenticationConverter(
+ Converter, Collection extends GrantedAuthority>> authoritiesConverter,
+ Optional authenticationFactory,
+ SpringAddonsSecurityProperties addonsProperties,
+ OAuth2ResourceServerProperties resourceServerProperties) {
+ return (String introspectedToken, OAuth2AuthenticatedPrincipal authenticatedPrincipal) -> {
+ return authenticationFactory.map(af -> af.build(introspectedToken, authenticatedPrincipal.getAttributes())).orElse(
+ new BearerTokenAuthentication(
+ new OAuth2IntrospectionAuthenticatedPrincipal(
+ new OpenidClaimSet(
+ authenticatedPrincipal.getAttributes(),
+ Stream.of(addonsProperties.getIssuers())
+ .filter(
+ issProps -> resourceServerProperties.getOpaquetoken().getIntrospectionUri()
+ .contains(issProps.getLocation().toString()))
+ .findAny().orElse(addonsProperties.getIssuers()[0]).getUsernameClaim()).getName(),
+ authenticatedPrincipal.getAttributes(),
+ (Collection) authenticatedPrincipal.getAuthorities()),
+ new OAuth2AccessToken(
+ OAuth2AccessToken.TokenType.BEARER,
+ introspectedToken,
+ authenticatedPrincipal.getAttribute(OAuth2TokenIntrospectionClaimNames.IAT),
+ authenticatedPrincipal.getAttribute(OAuth2TokenIntrospectionClaimNames.EXP)),
+ authoritiesConverter.convert(authenticatedPrincipal.getAttributes())));
+ };
+ }
}
\ No newline at end of file
diff --git a/webmvc/spring-addons-webmvc-introspecting-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/introspecting/AutoConfigureAddonsWebSecurity.java b/webmvc/spring-addons-webmvc-introspecting-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/introspecting/AutoConfigureAddonsWebSecurity.java
index 063c72f27..1a9276ad6 100644
--- a/webmvc/spring-addons-webmvc-introspecting-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/introspecting/AutoConfigureAddonsWebSecurity.java
+++ b/webmvc/spring-addons-webmvc-introspecting-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/introspecting/AutoConfigureAddonsWebSecurity.java
@@ -13,8 +13,8 @@
/**
*
- * Auto-configures {@link AddonsSecurityBeans} and {@link AddonsWebSecurityBeans}. To be used to test controllers but not services or
- * repositories (web context is not desired in that case).
+ * Auto-configures {@link AddonsSecurityBeans} and {@link AddonsWebSecurityBeans}. To be used to test controllers but not services or repositories (web context
+ * is not desired in that case).
*
* See {@link AutoConfigureAddonsSecurity}
*
diff --git a/webmvc/spring-addons-webmvc-jwt-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsSecurityBeans.java b/webmvc/spring-addons-webmvc-jwt-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsSecurityBeans.java
index 94417487d..5edda2d69 100644
--- a/webmvc/spring-addons-webmvc-jwt-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsSecurityBeans.java
+++ b/webmvc/spring-addons-webmvc-jwt-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsSecurityBeans.java
@@ -19,17 +19,16 @@
@Import({ SpringAddonsSecurityProperties.class })
public class AddonsSecurityBeans {
- /**
- * Retrieves granted authorities from the Jwt (from its private claims or with
- * the help of an external service)
- *
- * @param securityProperties
- * @return
- */
- @ConditionalOnMissingBean
- @Bean
- OAuth2AuthoritiesConverter authoritiesConverter(SpringAddonsSecurityProperties addonsProperties) {
- log.debug("Building default SimpleJwtGrantedAuthoritiesConverter with: {}", addonsProperties);
- return new ConfigurableClaimSet2AuthoritiesConverter(addonsProperties);
- }
+ /**
+ * Retrieves granted authorities from the Jwt (from its private claims or with the help of an external service)
+ *
+ * @param securityProperties
+ * @return
+ */
+ @ConditionalOnMissingBean
+ @Bean
+ OAuth2AuthoritiesConverter authoritiesConverter(SpringAddonsSecurityProperties addonsProperties) {
+ log.debug("Building default SimpleJwtGrantedAuthoritiesConverter with: {}", addonsProperties);
+ return new ConfigurableClaimSet2AuthoritiesConverter(addonsProperties);
+ }
}
\ No newline at end of file
diff --git a/webmvc/spring-addons-webmvc-jwt-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsWebSecurityBeans.java b/webmvc/spring-addons-webmvc-jwt-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsWebSecurityBeans.java
index fa9306953..2d4ed55f3 100644
--- a/webmvc/spring-addons-webmvc-jwt-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsWebSecurityBeans.java
+++ b/webmvc/spring-addons-webmvc-jwt-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/AddonsWebSecurityBeans.java
@@ -48,42 +48,28 @@
/**
*
* Usage
- * If not using spring-boot, @Import or @ComponentScan this class. All
- * beans defined here are @ConditionalOnMissingBean =>
- * just define your own @Beans to override.
+ * If not using spring-boot, @Import or @ComponentScan this class. All beans defined here are @ConditionalOnMissingBean => just define your own
+ * @Beans to override.
*
*
* Provided @Beans
*
*
- * - springAddonsResourceServerSecurityFilterChain: applies CORS, CSRF,
- * anonymous, sessionCreationPolicy, SSL, redirect and 401 instead of redirect
- * to login as defined in springAddonsResourceServerSecurityFilterChain: applies CORS, CSRF, anonymous, sessionCreationPolicy, SSL, redirect and 401 instead of redirect to login
+ * as defined in SpringAddonsSecurityProperties
- * - authorizePostProcessor: a bean of type
- * {@link ResourceServerExpressionInterceptUrlRegistryPostProcessor} to fine
- * tune access
- * control from java configuration. It applies to all routes not listed in
- * "permit-all" property configuration. Default requires users to be
- * authenticated. This is a bean to provide in your application configuration
- * if you prefer to define fine-grained access control rules with Java
- * configuration rather than methods security.
- * - httpPostProcessor: a bean of type
- * {@link ResourceServerHttpSecurityPostProcessor} to
- * override anything from above auto-configuration. It is called just before the
- * security filter-chain is returned. Default is a no-op.
- * - jwtAuthenticationConverter: a converter from a {@link Jwt} to something
- * inheriting from {@link AbstractAuthenticationToken}. The default instantiate
- * a {@link JwtAuthenticationToken} with username and authorities as configured
- * for the issuer of thi token. The easiest to override the type of
- * {@link AbstractAuthenticationToken}, is to provide with an
- * {@link OAuth2AuthenticationFactory} bean.
- * - authenticationManagerResolver: to accept authorities from more than one
- * issuer, the recommended way is to provide an
- * {@link AuthenticationManagerResolver} supporting it.
- * Default keeps a
- * {@link JwtAuthenticationProvider} with its own {@link JwtDecoder} for each
- * issuer.
+ * - authorizePostProcessor: a bean of type {@link ResourceServerExpressionInterceptUrlRegistryPostProcessor} to fine tune access control from java
+ * configuration. It applies to all routes not listed in "permit-all" property configuration. Default requires users to be authenticated. This is a bean to
+ * provide in your application configuration if you prefer to define fine-grained access control rules with Java configuration rather than methods
+ * security.
+ * - httpPostProcessor: a bean of type {@link ResourceServerHttpSecurityPostProcessor} to override anything from above auto-configuration. It is called just
+ * before the security filter-chain is returned. Default is a no-op.
+ * - jwtAuthenticationConverter: a converter from a {@link Jwt} to something inheriting from {@link AbstractAuthenticationToken}. The default instantiate a
+ * {@link JwtAuthenticationToken} with username and authorities as configured for the issuer of thi token. The easiest to override the type of
+ * {@link AbstractAuthenticationToken}, is to provide with an {@link OAuth2AuthenticationFactory} bean.
+ * - authenticationManagerResolver: to accept authorities from more than one issuer, the recommended way is to provide an
+ * {@link AuthenticationManagerResolver} supporting it. Default keeps a {@link JwtAuthenticationProvider} with its own {@link JwtDecoder}
+ * for each issuer.
*
*
* @author Jerome Wacongne ch4mp@c4-soft.com
@@ -94,149 +80,120 @@
@Slf4j
@Import({ AddonsSecurityBeans.class })
public class AddonsWebSecurityBeans {
- /**
- *
- * Applies SpringAddonsSecurityProperties to web security config. Be aware that
- * defining a {@link SecurityWebFilterChain} bean with no
- * security matcher and an order higher than LOWEST_PRECEDENCE will disable most
- * of this lib auto-configuration for OpenID resource-servers.
- *
- *
- * You should consider to set security matcher to all other
- * {@link SecurityWebFilterChain} beans and provide a
- * {@link ServerHttpSecurityPostProcessor} bean to override anything from this
- * bean
- *
- * .
- *
- * @param http HTTP security to configure
- * @param serverProperties Spring "server" configuration properties
- * @param addonsProperties "com.c4-soft.springaddons.security"
- * configuration properties
- * @param authorizePostProcessor Hook to override access-control rules
- * for all path that are not listed in
- * "permit-all"
- * @param httpPostProcessor Hook to override all or part of
- * HttpSecurity auto-configuration
- * @param authenticationManagerResolver Converts successful JWT decoding result
- * into an {@link Authentication}
- * @return A default {@link SecurityWebFilterChain} for servlet resource-servers
- * with JWT decoder (matches all
- * unmatched routes with lowest precedence)
- */
- @Order(Ordered.LOWEST_PRECEDENCE)
- @Bean
- SecurityFilterChain springAddonsResourceServerSecurityFilterChain(
- HttpSecurity http,
- ServerProperties serverProperties,
- SpringAddonsSecurityProperties addonsProperties,
- ResourceServerExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor,
- ResourceServerHttpSecurityPostProcessor httpPostProcessor,
- AuthenticationManagerResolver authenticationManagerResolver)
- throws Exception {
- http.oauth2ResourceServer(oauth2 -> oauth2.authenticationManagerResolver(authenticationManagerResolver));
-
- ServletConfigurationSupport.configureResourceServer(http, serverProperties, addonsProperties,
- authorizePostProcessor, httpPostProcessor);
-
- return http.build();
- }
-
- /**
- * hook to override security rules for all path that are not listed in
- * "permit-all". Default is isAuthenticated().
- *
- * @return a hook to override security rules for all path that are not listed in
- * "permit-all". Default is isAuthenticated().
- */
- @ConditionalOnMissingBean
- @Bean
- ResourceServerExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor() {
- return registry -> registry.anyRequest().authenticated();
- }
-
- /**
- * Hook to override all or part of HttpSecurity auto-configuration. Called after
- * spring-addons configuration was applied so that you can
- * modify anything
- *
- * @return a hook to override all or part of HttpSecurity auto-configuration.
- * Called after spring-addons configuration was applied so that
- * you can modify anything
- */
- @ConditionalOnMissingBean
- @Bean
- ResourceServerHttpSecurityPostProcessor httpPostProcessor() {
- return httpSecurity -> httpSecurity;
- }
-
- CorsConfigurationSource corsConfig(CorsProperties[] corsProperties) {
- log.debug("Building default CorsConfigurationSource with: {}", Stream.of(corsProperties).toList());
- final var source = new UrlBasedCorsConfigurationSource();
- for (final var corsProps : corsProperties) {
- final var configuration = new CorsConfiguration();
- configuration.setAllowedOrigins(Arrays.asList(corsProps.getAllowedOrigins()));
- configuration.setAllowedMethods(Arrays.asList(corsProps.getAllowedMethods()));
- configuration.setAllowedHeaders(Arrays.asList(corsProps.getAllowedHeaders()));
- configuration.setExposedHeaders(Arrays.asList(corsProps.getExposedHeaders()));
- source.registerCorsConfiguration(corsProps.getPath(), configuration);
- }
- return source;
- }
-
- public static interface Jwt2AuthenticationConverter
- extends Converter {
- }
-
- /**
- * Converter bean from {@link Jwt} to {@link AbstractAuthenticationToken}
- *
- * @param authoritiesConverter converts access-token claims into Spring
- * authorities
- * @param securityProperties Spring "spring.security" configuration
- * properties
- * @param authenticationFactory builds an {@link Authentication} instance from
- * access-token string and claims
- * @return a converter from {@link Jwt} to {@link AbstractAuthenticationToken}
- */
- @ConditionalOnMissingBean
- @Bean
- Jwt2AuthenticationConverter extends AbstractAuthenticationToken> jwtAuthenticationConverter(
- Converter, Collection extends GrantedAuthority>> authoritiesConverter,
- SpringAddonsSecurityProperties addonsProperties,
- Optional authenticationFactory) {
- return jwt -> authenticationFactory.map(af -> af.build(jwt.getTokenValue(), jwt.getClaims())).orElse(
- new JwtAuthenticationToken(
- jwt,
- authoritiesConverter.convert(jwt.getClaims()),
- new OpenidClaimSet(jwt.getClaims(),
- addonsProperties.getIssuerProperties(jwt.getIssuer()).getUsernameClaim()).getName()));
- }
-
- /**
- * Provides with multi-tenancy: builds a
- * AuthenticationManagerResolver
- * per provided OIDC issuer URI
- *
- * @param auth2ResourceServerProperties "spring.security.oauth2.resourceserver"
- * configuration properties
- * @param addonsProperties "com.c4-soft.springaddons.security"
- * configuration properties
- * @param jwtAuthenticationConverter converts from a {@link Jwt} to an
- * {@link Authentication} implementation
- * @return Multi-tenant
- * {@link AuthenticationManagerResolver} (one for
- * each configured issuer)
- */
- @ConditionalOnMissingBean
- @Bean
- AuthenticationManagerResolver authenticationManagerResolver(
- OAuth2ResourceServerProperties auth2ResourceServerProperties,
- SpringAddonsSecurityProperties addonsProperties,
- Converter jwtAuthenticationConverter) {
- final var jwtProps = Optional.ofNullable(auth2ResourceServerProperties)
- .map(OAuth2ResourceServerProperties::getJwt);
- // @formatter:off
+ /**
+ *
+ * Applies SpringAddonsSecurityProperties to web security config. Be aware that defining a {@link SecurityWebFilterChain} bean with no security matcher and
+ * an order higher than LOWEST_PRECEDENCE will disable most of this lib auto-configuration for OpenID resource-servers.
+ *
+ *
+ * You should consider to set security matcher to all other {@link SecurityWebFilterChain} beans and provide a {@link ServerHttpSecurityPostProcessor} bean
+ * to override anything from this bean
+ *
+ * .
+ *
+ * @param http HTTP security to configure
+ * @param serverProperties Spring "server" configuration properties
+ * @param addonsProperties "com.c4-soft.springaddons.security" configuration properties
+ * @param authorizePostProcessor Hook to override access-control rules for all path that are not listed in "permit-all"
+ * @param httpPostProcessor Hook to override all or part of HttpSecurity auto-configuration
+ * @param authenticationManagerResolver Converts successful JWT decoding result into an {@link Authentication}
+ * @return A default {@link SecurityWebFilterChain} for servlet resource-servers with JWT decoder (matches all unmatched
+ * routes with lowest precedence)
+ */
+ @Order(Ordered.LOWEST_PRECEDENCE)
+ @Bean
+ SecurityFilterChain springAddonsResourceServerSecurityFilterChain(
+ HttpSecurity http,
+ ServerProperties serverProperties,
+ SpringAddonsSecurityProperties addonsProperties,
+ ResourceServerExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor,
+ ResourceServerHttpSecurityPostProcessor httpPostProcessor,
+ AuthenticationManagerResolver authenticationManagerResolver)
+ throws Exception {
+ http.oauth2ResourceServer(oauth2 -> oauth2.authenticationManagerResolver(authenticationManagerResolver));
+
+ ServletConfigurationSupport.configureResourceServer(http, serverProperties, addonsProperties, authorizePostProcessor, httpPostProcessor);
+
+ return http.build();
+ }
+
+ /**
+ * hook to override security rules for all path that are not listed in "permit-all". Default is isAuthenticated().
+ *
+ * @return a hook to override security rules for all path that are not listed in "permit-all". Default is isAuthenticated().
+ */
+ @ConditionalOnMissingBean
+ @Bean
+ ResourceServerExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor() {
+ return registry -> registry.anyRequest().authenticated();
+ }
+
+ /**
+ * Hook to override all or part of HttpSecurity auto-configuration. Called after spring-addons configuration was applied so that you can modify anything
+ *
+ * @return a hook to override all or part of HttpSecurity auto-configuration. Called after spring-addons configuration was applied so that you can modify
+ * anything
+ */
+ @ConditionalOnMissingBean
+ @Bean
+ ResourceServerHttpSecurityPostProcessor httpPostProcessor() {
+ return httpSecurity -> httpSecurity;
+ }
+
+ CorsConfigurationSource corsConfig(CorsProperties[] corsProperties) {
+ log.debug("Building default CorsConfigurationSource with: {}", Stream.of(corsProperties).toList());
+ final var source = new UrlBasedCorsConfigurationSource();
+ for (final var corsProps : corsProperties) {
+ final var configuration = new CorsConfiguration();
+ configuration.setAllowedOrigins(Arrays.asList(corsProps.getAllowedOrigins()));
+ configuration.setAllowedMethods(Arrays.asList(corsProps.getAllowedMethods()));
+ configuration.setAllowedHeaders(Arrays.asList(corsProps.getAllowedHeaders()));
+ configuration.setExposedHeaders(Arrays.asList(corsProps.getExposedHeaders()));
+ source.registerCorsConfiguration(corsProps.getPath(), configuration);
+ }
+ return source;
+ }
+
+ public static interface Jwt2AuthenticationConverter extends Converter {
+ }
+
+ /**
+ * Converter bean from {@link Jwt} to {@link AbstractAuthenticationToken}
+ *
+ * @param authoritiesConverter converts access-token claims into Spring authorities
+ * @param securityProperties Spring "spring.security" configuration properties
+ * @param authenticationFactory builds an {@link Authentication} instance from access-token string and claims
+ * @return a converter from {@link Jwt} to {@link AbstractAuthenticationToken}
+ */
+ @ConditionalOnMissingBean
+ @Bean
+ Jwt2AuthenticationConverter extends AbstractAuthenticationToken> jwtAuthenticationConverter(
+ Converter, Collection extends GrantedAuthority>> authoritiesConverter,
+ SpringAddonsSecurityProperties addonsProperties,
+ Optional authenticationFactory) {
+ return jwt -> authenticationFactory.map(af -> af.build(jwt.getTokenValue(), jwt.getClaims())).orElse(
+ new JwtAuthenticationToken(
+ jwt,
+ authoritiesConverter.convert(jwt.getClaims()),
+ new OpenidClaimSet(jwt.getClaims(), addonsProperties.getIssuerProperties(jwt.getIssuer()).getUsernameClaim()).getName()));
+ }
+
+ /**
+ * Provides with multi-tenancy: builds a AuthenticationManagerResolver per provided OIDC issuer URI
+ *
+ * @param auth2ResourceServerProperties "spring.security.oauth2.resourceserver" configuration properties
+ * @param addonsProperties "com.c4-soft.springaddons.security" configuration properties
+ * @param jwtAuthenticationConverter converts from a {@link Jwt} to an {@link Authentication} implementation
+ * @return Multi-tenant {@link AuthenticationManagerResolver} (one for each configured issuer)
+ */
+ @ConditionalOnMissingBean
+ @Bean
+ AuthenticationManagerResolver authenticationManagerResolver(
+ OAuth2ResourceServerProperties auth2ResourceServerProperties,
+ SpringAddonsSecurityProperties addonsProperties,
+ Converter jwtAuthenticationConverter) {
+ final var jwtProps = Optional.ofNullable(auth2ResourceServerProperties).map(OAuth2ResourceServerProperties::getJwt);
+ // @formatter:off
Optional.ofNullable(jwtProps.map(OAuth2ResourceServerProperties.Jwt::getIssuerUri)).orElse(jwtProps.map(OAuth2ResourceServerProperties.Jwt::getJwkSetUri))
.filter(StringUtils::hasLength)
.ifPresent(jwtConf -> {
@@ -244,22 +201,21 @@ AuthenticationManagerResolver authenticationManagerResolver(
});
// @formatter:on
- final Map jwtManagers = Stream.of(addonsProperties.getIssuers())
- .collect(Collectors.toMap(issuer -> issuer.getLocation().toString(), issuer -> {
- JwtDecoder decoder = issuer.getJwkSetUri() != null
- && StringUtils.hasLength(issuer.getJwkSetUri().toString())
- ? NimbusJwtDecoder.withJwkSetUri(issuer.getJwkSetUri().toString()).build()
- : JwtDecoders.fromIssuerLocation(issuer.getLocation().toString());
- var provider = new JwtAuthenticationProvider(decoder);
- provider.setJwtAuthenticationConverter(jwtAuthenticationConverter);
- return provider::authenticate;
- }));
-
- log.debug(
- "Building default JwtIssuerAuthenticationManagerResolver with: ",
- auth2ResourceServerProperties.getJwt(),
- Stream.of(addonsProperties.getIssuers()).toList());
-
- return new JwtIssuerAuthenticationManagerResolver((AuthenticationManagerResolver) jwtManagers::get);
- }
+ final Map jwtManagers =
+ Stream.of(addonsProperties.getIssuers()).collect(Collectors.toMap(issuer -> issuer.getLocation().toString(), issuer -> {
+ JwtDecoder decoder = issuer.getJwkSetUri() != null && StringUtils.hasLength(issuer.getJwkSetUri().toString())
+ ? NimbusJwtDecoder.withJwkSetUri(issuer.getJwkSetUri().toString()).build()
+ : JwtDecoders.fromIssuerLocation(issuer.getLocation().toString());
+ var provider = new JwtAuthenticationProvider(decoder);
+ provider.setJwtAuthenticationConverter(jwtAuthenticationConverter);
+ return provider::authenticate;
+ }));
+
+ log.debug(
+ "Building default JwtIssuerAuthenticationManagerResolver with: ",
+ auth2ResourceServerProperties.getJwt(),
+ Stream.of(addonsProperties.getIssuers()).toList());
+
+ return new JwtIssuerAuthenticationManagerResolver((AuthenticationManagerResolver) jwtManagers::get);
+ }
}
\ No newline at end of file
diff --git a/webmvc/spring-addons-webmvc-jwt-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webmvc/jwt/AutoConfigureAddonsWebSecurity.java b/webmvc/spring-addons-webmvc-jwt-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webmvc/jwt/AutoConfigureAddonsWebSecurity.java
index 4babb2ca7..64c32f710 100644
--- a/webmvc/spring-addons-webmvc-jwt-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webmvc/jwt/AutoConfigureAddonsWebSecurity.java
+++ b/webmvc/spring-addons-webmvc-jwt-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/webmvc/jwt/AutoConfigureAddonsWebSecurity.java
@@ -14,10 +14,8 @@
/**
*
- * Auto-configures {@link AddonsSecurityBeans} and
- * {@link AddonsWebSecurityBeans}. To be used to test controllers but not
- * services or
- * repositories (web context is not desired in that case).
+ * Auto-configures {@link AddonsSecurityBeans} and {@link AddonsWebSecurityBeans}. To be used to test controllers but not services or repositories (web context
+ * is not desired in that case).
*
* See {@link AutoConfigureAddonsSecurity}
*
@@ -26,7 +24,6 @@
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@AutoConfigureAddonsSecurity
-@ImportAutoConfiguration({ AddonsWebSecurityBeans.class, SpringAddonsOAuth2ClientBeans.class,
- AddonsWebmvcTestConf.class })
+@ImportAutoConfiguration({ AddonsWebSecurityBeans.class, SpringAddonsOAuth2ClientBeans.class, AddonsWebmvcTestConf.class })
public @interface AutoConfigureAddonsWebSecurity {
}
diff --git a/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/AddonsWebmvcTestConf.java b/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/AddonsWebmvcTestConf.java
index f5177a54c..af84c9c3a 100644
--- a/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/AddonsWebmvcTestConf.java
+++ b/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/AddonsWebmvcTestConf.java
@@ -1,14 +1,13 @@
/*
* Copyright 2020 Jérôme Wacongne
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may
- * obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the
+ * License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
- * and limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
package com.c4_soft.springaddons.security.oauth2.test.mockmvc;
@@ -60,137 +59,138 @@
@Import({ MockMvcProperties.class })
public class AddonsWebmvcTestConf {
- @MockBean
- JwtDecoder jwtDecoder;
-
- @MockBean
- AuthenticationManagerResolver jwtIssuerAuthenticationManagerResolver;
-
- @MockBean
- OpaqueTokenIntrospector introspector;
-
- @ConditionalOnMissingBean
- @Bean
- InMemoryClientRegistrationRepository clientRegistrationRepository() {
- final var clientRegistrationRepository = mock(InMemoryClientRegistrationRepository.class);
- when(clientRegistrationRepository.iterator()).thenReturn(new ArrayList().iterator());
- when(clientRegistrationRepository.spliterator()).thenReturn(new ArrayList().spliterator());
- return clientRegistrationRepository;
- }
-
- @MockBean
- OAuth2AuthorizedClientService oAuth2AuthorizedClientService;
-
- @Bean
- SerializationHelper serializationHelper(ObjectFactory messageConverters) {
- return new SerializationHelper(messageConverters);
- }
-
- @Bean
- @Scope("prototype")
- MockMvcSupport mockMvcSupport(
- MockMvc mockMvc,
- SerializationHelper serializationHelper,
- MockMvcProperties mockMvcProperties,
- ServerProperties serverProperties,
- SpringAddonsSecurityProperties addonsProperties) {
- return new MockMvcSupport(mockMvc, serializationHelper, mockMvcProperties, serverProperties,
- addonsProperties);
- }
-
- @ConditionalOnMissingBean
- @Bean
- OAuth2AuthoritiesConverter claimSet2AuthoritiesConverter() {
- return mock(OAuth2AuthoritiesConverter.class);
- }
-
- @ConditionalOnMissingBean
- @Bean
- SecurityFilterChain resourceServerSecurityFilterChain(HttpSecurity http, ServerProperties serverProperties,
- SpringAddonsSecurityProperties addonsProperties,
- ExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor,
- ResourceServerHttpSecurityPostProcessor httpPostProcessor,
- CorsConfigurationSource corsConfigurationSource) throws Exception {
-
- if (addonsProperties.getPermitAll().length > 0) {
- http.anonymous();
- }
-
- if (addonsProperties.getCors().length > 0) {
- http.cors().configurationSource(corsConfigurationSource);
- } else {
- http.cors().disable();
- }
-
- switch (addonsProperties.getCsrf()) {
- case DISABLE:
- http.csrf().disable();
- break;
- case DEFAULT:
- if (addonsProperties.isStatlessSessions()) {
- http.csrf().disable();
- } else {
- http.csrf();
- }
- break;
- case SESSION:
- http.csrf();
- break;
- case COOKIE_HTTP_ONLY:
- http.csrf().csrfTokenRepository(new CookieCsrfTokenRepository());
- break;
- case COOKIE_ACCESSIBLE_FROM_JS:
- http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
- .csrfTokenRequestHandler(new XorCsrfTokenRequestAttributeHandler()::handle);
- break;
- }
-
- if (addonsProperties.isStatlessSessions()) {
- http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
- }
-
- if (!addonsProperties.isRedirectToLoginIfUnauthorizedOnRestrictedContent()) {
- http.exceptionHandling().authenticationEntryPoint((request, response, authException) -> {
- response.addHeader(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Restricted Content\"");
- response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
- });
- }
-
- if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) {
- http.requiresChannel().anyRequest().requiresSecure();
- }
-
- authorizePostProcessor.authorizeHttpRequests(
- http.authorizeHttpRequests().requestMatchers(addonsProperties.getPermitAll()).permitAll());
-
- return httpPostProcessor.process(http).build();
- }
-
- @ConditionalOnMissingBean
- @Bean
- ExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor() {
- return registry -> registry.anyRequest().authenticated();
- }
-
- @ConditionalOnMissingBean
- @Bean
- ResourceServerHttpSecurityPostProcessor httpPostProcessor() {
- return httpSecurity -> httpSecurity;
- }
-
- @ConditionalOnMissingBean
- @Bean
- CorsConfigurationSource corsConfigurationSource(SpringAddonsSecurityProperties addonsProperties) {
- final var source = new UrlBasedCorsConfigurationSource();
- for (final var corsProps : addonsProperties.getCors()) {
- final var configuration = new CorsConfiguration();
- configuration.setAllowedOrigins(Arrays.asList(corsProps.getAllowedOrigins()));
- configuration.setAllowedMethods(Arrays.asList(corsProps.getAllowedMethods()));
- configuration.setAllowedHeaders(Arrays.asList(corsProps.getAllowedHeaders()));
- configuration.setExposedHeaders(Arrays.asList(corsProps.getExposedHeaders()));
- source.registerCorsConfiguration(corsProps.getPath(), configuration);
- }
- return source;
- }
+ @MockBean
+ JwtDecoder jwtDecoder;
+
+ @MockBean
+ AuthenticationManagerResolver jwtIssuerAuthenticationManagerResolver;
+
+ @MockBean
+ OpaqueTokenIntrospector introspector;
+
+ @ConditionalOnMissingBean
+ @Bean
+ InMemoryClientRegistrationRepository clientRegistrationRepository() {
+ final var clientRegistrationRepository = mock(InMemoryClientRegistrationRepository.class);
+ when(clientRegistrationRepository.iterator()).thenReturn(new ArrayList().iterator());
+ when(clientRegistrationRepository.spliterator()).thenReturn(new ArrayList().spliterator());
+ return clientRegistrationRepository;
+ }
+
+ @MockBean
+ OAuth2AuthorizedClientService oAuth2AuthorizedClientService;
+
+ @Bean
+ SerializationHelper serializationHelper(ObjectFactory messageConverters) {
+ return new SerializationHelper(messageConverters);
+ }
+
+ @Bean
+ @Scope("prototype")
+ MockMvcSupport mockMvcSupport(
+ MockMvc mockMvc,
+ SerializationHelper serializationHelper,
+ MockMvcProperties mockMvcProperties,
+ ServerProperties serverProperties,
+ SpringAddonsSecurityProperties addonsProperties) {
+ return new MockMvcSupport(mockMvc, serializationHelper, mockMvcProperties, serverProperties, addonsProperties);
+ }
+
+ @ConditionalOnMissingBean
+ @Bean
+ OAuth2AuthoritiesConverter claimSet2AuthoritiesConverter() {
+ return mock(OAuth2AuthoritiesConverter.class);
+ }
+
+ @ConditionalOnMissingBean
+ @Bean
+ SecurityFilterChain resourceServerSecurityFilterChain(
+ HttpSecurity http,
+ ServerProperties serverProperties,
+ SpringAddonsSecurityProperties addonsProperties,
+ ExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor,
+ ResourceServerHttpSecurityPostProcessor httpPostProcessor,
+ CorsConfigurationSource corsConfigurationSource)
+ throws Exception {
+
+ if (addonsProperties.getPermitAll().length > 0) {
+ http.anonymous();
+ }
+
+ if (addonsProperties.getCors().length > 0) {
+ http.cors().configurationSource(corsConfigurationSource);
+ } else {
+ http.cors().disable();
+ }
+
+ switch (addonsProperties.getCsrf()) {
+ case DISABLE:
+ http.csrf().disable();
+ break;
+ case DEFAULT:
+ if (addonsProperties.isStatlessSessions()) {
+ http.csrf().disable();
+ } else {
+ http.csrf();
+ }
+ break;
+ case SESSION:
+ http.csrf();
+ break;
+ case COOKIE_HTTP_ONLY:
+ http.csrf().csrfTokenRepository(new CookieCsrfTokenRepository());
+ break;
+ case COOKIE_ACCESSIBLE_FROM_JS:
+ http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
+ .csrfTokenRequestHandler(new XorCsrfTokenRequestAttributeHandler()::handle);
+ break;
+ }
+
+ if (addonsProperties.isStatlessSessions()) {
+ http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
+ }
+
+ if (!addonsProperties.isRedirectToLoginIfUnauthorizedOnRestrictedContent()) {
+ http.exceptionHandling().authenticationEntryPoint((request, response, authException) -> {
+ response.addHeader(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Restricted Content\"");
+ response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
+ });
+ }
+
+ if (serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled()) {
+ http.requiresChannel().anyRequest().requiresSecure();
+ }
+
+ authorizePostProcessor.authorizeHttpRequests(http.authorizeHttpRequests().requestMatchers(addonsProperties.getPermitAll()).permitAll());
+
+ return httpPostProcessor.process(http).build();
+ }
+
+ @ConditionalOnMissingBean
+ @Bean
+ ExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor() {
+ return registry -> registry.anyRequest().authenticated();
+ }
+
+ @ConditionalOnMissingBean
+ @Bean
+ ResourceServerHttpSecurityPostProcessor httpPostProcessor() {
+ return httpSecurity -> httpSecurity;
+ }
+
+ @ConditionalOnMissingBean
+ @Bean
+ CorsConfigurationSource corsConfigurationSource(SpringAddonsSecurityProperties addonsProperties) {
+ final var source = new UrlBasedCorsConfigurationSource();
+ for (final var corsProps : addonsProperties.getCors()) {
+ final var configuration = new CorsConfiguration();
+ configuration.setAllowedOrigins(Arrays.asList(corsProps.getAllowedOrigins()));
+ configuration.setAllowedMethods(Arrays.asList(corsProps.getAllowedMethods()));
+ configuration.setAllowedHeaders(Arrays.asList(corsProps.getAllowedHeaders()));
+ configuration.setExposedHeaders(Arrays.asList(corsProps.getExposedHeaders()));
+ source.registerCorsConfiguration(corsProps.getPath(), configuration);
+ }
+ return source;
+ }
}
diff --git a/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/AuthenticationRequestPostProcessor.java b/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/AuthenticationRequestPostProcessor.java
index 8f3e1ae76..ab43b7a30 100644
--- a/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/AuthenticationRequestPostProcessor.java
+++ b/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/AuthenticationRequestPostProcessor.java
@@ -1,17 +1,13 @@
/*
* Copyright 2019 Jérôme Wacongne
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the
+ * License at
*
- * https://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
package com.c4_soft.springaddons.security.oauth2.test.mockmvc;
@@ -22,11 +18,10 @@
import com.c4_soft.springaddons.security.oauth2.AuthenticationBuilder;
/**
- *Redundant code for {@link Authentication} MockMvc request post-processors
+ * Redundant code for {@link Authentication} MockMvc request post-processors
*
- * @author Jérôme Wacongne <ch4mp#64;c4-soft.com>
- *
- * @param concrete {@link Authentication} type to build and configure in test security context
+ * @author Jérôme Wacongne <ch4mp#64;c4-soft.com>
+ * @param concrete {@link Authentication} type to build and configure in test security context
*/
public interface AuthenticationRequestPostProcessor extends RequestPostProcessor, AuthenticationBuilder {
@Override
diff --git a/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/MockAuthenticationRequestPostProcessor.java b/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/MockAuthenticationRequestPostProcessor.java
index d2c291a3b..4a9218025 100644
--- a/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/MockAuthenticationRequestPostProcessor.java
+++ b/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/MockAuthenticationRequestPostProcessor.java
@@ -1,14 +1,13 @@
/*
* Copyright 2019 Jérôme Wacongne
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may
- * obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the
+ * License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
- * and limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
package com.c4_soft.springaddons.security.oauth2.test.mockmvc;
diff --git a/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/MockMvcSupport.java b/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/MockMvcSupport.java
index c07e7ba55..edc248f90 100644
--- a/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/MockMvcSupport.java
+++ b/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/MockMvcSupport.java
@@ -1,14 +1,13 @@
/*
* Copyright 2018 Jérôme Wacongne.
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may
- * obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the
+ * License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
- * and limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
package com.c4_soft.springaddons.security.oauth2.test.mockmvc;
@@ -44,633 +43,565 @@
*
* Highlighted features:
*
- * - auto sets "Accept" and "Content-Type" headers according
- * {@code com.c4-soft.springaddons.test.web.default-media-type} and
- * {@code com.c4-soft.springaddons.test.web.default-charset} to test properties,
- * defaulted to {@code application/json} and
- * {@code utf-8}
- * - serializes request body according to Content-type using registered
- * message converters
- * - provides with shortcuts to issue requests in basic but most common cases
- * (no fancy headers, cookies, etc): get, post, patch, put and
- * delete methods
- * - wraps MockMvc {@link MockMvc#perform(RequestBuilder) perform} and exposes
- * request builder helpers for advanced cases (when you need
- * to further customize {@link MockHttpServletRequestBuilder} with cookies or
- * additional headers for instance)
+ * - auto sets "Accept" and "Content-Type" headers according {@code com.c4-soft.springaddons.test.web.default-media-type} and
+ * {@code com.c4-soft.springaddons.test.web.default-charset} to test properties, defaulted to {@code application/json} and {@code utf-8}
+ * - serializes request body according to Content-type using registered message converters
+ * - provides with shortcuts to issue requests in basic but most common cases (no fancy headers, cookies, etc): get, post, patch, put and delete methods
+ * - wraps MockMvc {@link MockMvc#perform(RequestBuilder) perform} and exposes request builder helpers for advanced cases (when you need to further customize
+ * {@link MockHttpServletRequestBuilder} with cookies or additional headers for instance)
*
*
* @author Jérôme Wacongne <ch4mp@c4-soft.com>
*/
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class MockMvcSupport {
- private final MockMvc mockMvc;
-
- private final SerializationHelper conv;
-
- private MediaType mediaType;
-
- private Charset charset;
-
- private boolean isSecure;
-
- private boolean isCsrf;
-
- private final List postProcessors;
-
- /**
- * @param mockMvc wrapped Spring MVC testing helper
- * @param serializationHelper used to serialize payloads to requested
- * {@code Content-type} using Spring registered
- * message converters
- * @param mockMvcProperties default values for media-type, charset and https
- * usage
- */
- public MockMvcSupport(
- MockMvc mockMvc,
- SerializationHelper serializationHelper,
- MockMvcProperties mockMvcProperties,
- ServerProperties serverProperties,
- SpringAddonsSecurityProperties addonsProperties) {
- this.mockMvc = mockMvc;
- this.conv = serializationHelper;
- this.mediaType = MediaType.valueOf(mockMvcProperties.getDefaultMediaType());
- this.charset = Charset.forName(mockMvcProperties.getDefaultCharset());
- this.postProcessors = new ArrayList<>();
- this.isSecure = serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled();
- this.isCsrf = !addonsProperties.getCsrf().equals(Csrf.DISABLE);
- }
-
- /**
- * @param isSecure if true, requests are sent with https instead of http
- * @return
- */
- public MockMvcSupport setSecure(boolean isSecure) {
- this.isSecure = isSecure;
- return this;
- }
-
- /**
- * @param isCsrf should MockMvcRequests be issued with CSRF
- * @return
- */
- public MockMvcSupport setCsrf(boolean isCsrf) {
- this.isCsrf = isCsrf;
- return this;
- }
-
- /**
- * @param mediaType override configured default media-type
- * @return
- */
- public MockMvcSupport setMediaType(MediaType mediaType) {
- this.mediaType = mediaType;
- return this;
- }
-
- /**
- * @param charset override configured default charset
- * @return
- */
- public MockMvcSupport setCharset(Charset charset) {
- this.charset = charset;
- return this;
- }
-
- /**
- * Factory for a generic
- * {@link org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder
- * MockHttpServletRequestBuilder}
- * with relevant "Accept" and "Content-Type" headers. You might prefer to use
- * {@link #getRequestBuilder(MediaType, String, Object...)
- * getRequestBuilder} or alike which go further with request pre-configuration
- * or even {@link #get(MediaType, String, Object...) get},
- * {@link #post(Object, String, Object...)} and so on which issue simple
- * requests in one step.
- *
- * @param accept should be non-empty when issuing response with body (GET,
- * POST, OPTION), none otherwise
- * @param charset char-set to be used for serialized payloads
- * @param method whatever HTTP verb you need
- * @param urlTemplate end-point to be requested
- * @param uriVars end-point template placeholders values
- * @return a request builder with minimal info you can tweak further: add
- * headers, cookies, etc.
- */
- public MockHttpServletRequestBuilder requestBuilder(Optional accept, Optional charset,
- HttpMethod method, String urlTemplate, Object... uriVars) {
- final var builder = request(method, urlTemplate, uriVars);
- accept.ifPresent(builder::accept);
- charset.ifPresent(c -> builder.characterEncoding(c.toString()));
- builder.secure(isSecure);
- if (isCsrf) {
- builder.with(csrf());
- }
- return builder;
- }
-
- /**
- * To be called with fully configured request builder (wraps MockMvc
- * {@link org.springframework.test.web.servlet.MockMvc#perform(org.springframework.test.web.servlet.RequestBuilder)
- * perform}).
- *
- * @param requestBuilder fully configured request
- * @return API answer to be tested
- */
- public ResultActions perform(MockHttpServletRequestBuilder requestBuilder) {
- postProcessors.forEach(requestBuilder::with);
- try {
- return mockMvc.perform(requestBuilder);
- } catch (final Exception e) {
- throw new MockMvcPerformException(e);
- }
- }
-
- /* GET */
- /**
- * Factory providing with a request builder to issue a GET request (with Accept
- * header).
- *
- * @param accept determines request Accept header (and response body
- * format)
- * @param urlTemplate API end-point to call
- * @param uriVars values to feed URL template placeholders
- * @return a request builder to be further configured (additional headers,
- * cookies, etc.)
- */
- public MockHttpServletRequestBuilder getRequestBuilder(MediaType accept, String urlTemplate, Object... uriVars) {
- return requestBuilder(Optional.of(accept), Optional.empty(), HttpMethod.GET, urlTemplate, uriVars);
- }
-
- /**
- * Factory providing with a request builder to issue a GET request (with Accept
- * header defaulted to what this helper is constructed with).
- *
- * @param urlTemplate API end-point to call
- * @param uriVars values to feed URL template placeholders
- * @return a request builder to be further configured (additional headers,
- * cookies, etc.)
- */
- public MockHttpServletRequestBuilder getRequestBuilder(String urlTemplate, Object... uriVars) {
- return getRequestBuilder(mediaType, urlTemplate, uriVars);
- }
-
- /**
- * Shortcut to issue a GET request with minimal headers and submit it.
- *
- * @param accept determines request Accept header (and response body
- * format)
- * @param urlTemplate API endpoint to be requested
- * @param uriVars values to replace endpoint placeholders with
- * @return API response to test
- */
- public ResultActions get(MediaType accept, String urlTemplate, Object... uriVars) {
- return perform(getRequestBuilder(accept, urlTemplate, uriVars));
- }
-
- /**
- * Shortcut to create a builder for a GET request with minimal headers and
- * submit it (Accept header defaulted to what this helper was
- * constructed with).
- *
- * @param urlTemplate API endpoint to be requested
- * @param uriVars values to replace endpoint placeholders with
- * @return API response to test
- */
- public ResultActions get(String urlTemplate, Object... uriVars) {
- return perform(getRequestBuilder(urlTemplate, uriVars));
- }
-
- /* POST */
- /**
- * Factory for a POST request builder containing a body set to payload
- * serialized in given media type (with adequate Content-type header).
- *
- * @param payload to be serialized as body in contentType format
- * @param contentType format to be used for payload serialization
- * @param charset char-set for request and response
- * @param accept how should the response body be serialized (if any)
- * @param urlTemplate API end-point to be requested
- * @param uriVars values to replace end-point placeholders with
- * @param payload type
- * @return Request builder to further configure (cookies, additional headers,
- * etc.)
- */
- public MockHttpServletRequestBuilder postRequestBuilder(T payload, MediaType contentType, Charset charset,
- MediaType accept, String urlTemplate, Object... uriVars) {
- return feed(requestBuilder(Optional.of(accept), Optional.of(charset), HttpMethod.POST, urlTemplate, uriVars),
- payload, contentType, charset);
- }
-
- /**
- * Factory for a POST request builder containing a body set to payload
- * serialized in given media type (with adequate Content-type header).
- *
- * @param payload to be serialized as body in contentType format
- * @param contentType format to be used for payload serialization
- * @param accept how should the response body be serialized (if any)
- * @param urlTemplate API end-point to be requested
- * @param uriVars values to replace end-point placeholders with
- * @param payload type
- * @return Request builder to further configure (cookies, additional headers,
- * etc.)
- */
- public MockHttpServletRequestBuilder postRequestBuilder(T payload, MediaType contentType, MediaType accept,
- String urlTemplate, Object... uriVars) {
- return postRequestBuilder(payload, contentType, charset, accept, urlTemplate, uriVars);
- }
-
- /**
- * Factory for a POST request builder. Body is pre-set to payload. Both
- * Content-type and Accept headers are set to default media-type.
- *
- * @param payload request body
- * @param urlTemplate API end-point
- * @param uriVars values ofr URL template placeholders
- * @param payload type
- * @return Request builder to further configure (cookies, additional headers,
- * etc.)
- */
- public MockHttpServletRequestBuilder postRequestBuilder(T payload, String urlTemplate, Object... uriVars) {
- return postRequestBuilder(payload, mediaType, charset, mediaType, urlTemplate, uriVars);
- }
-
- /**
- * Shortcut to issue a POST request with provided payload as body, using given
- * media-type for serialization (and Content-type header).
- *
- * @param payload POST request body
- * @param contentType media type used to serialize payload and set Content-type
- * header
- * @param accept media-type to be set as Accept header (and response
- * serialization)
- * @param charset char-set for request and response
- * @param urlTemplate API end-point to be called
- * @param uriVars values ofr URL template placeholders
- * @param payload type
- * @return API response to test
- */
- public ResultActions post(T payload, MediaType contentType, Charset charset, MediaType accept,
- String urlTemplate, Object... uriVars) {
- return perform(postRequestBuilder(payload, contentType, charset, accept, urlTemplate, uriVars));
- }
-
- /**
- * Shortcut to issue a POST request with provided payload as body, using given
- * media-type for serialization (and Content-type header).
- *
- * @param payload POST request body
- * @param contentType media type used to serialize payload and set Content-type
- * header
- * @param accept media-type to be set as Accept header (and response
- * serialization)
- * @param urlTemplate API end-point to be called
- * @param uriVars values ofr URL template placeholders
- * @param payload type
- * @return API response to test
- */
- public ResultActions post(T payload, MediaType contentType, MediaType accept, String urlTemplate,
- Object... uriVars) {
- return perform(postRequestBuilder(payload, contentType, accept, urlTemplate, uriVars));
- }
-
- /**
- * Shortcut to issue a POST request with provided payload as body, using default
- * media-type for serialization (and Content-type header).
- *
- * @param payload POST request body
- * @param urlTemplate API end-point to be called
- * @param uriVars values ofr URL template placeholders
- * @param payload type
- * @return API response to test
- */
- public ResultActions post(T payload, String urlTemplate, Object... uriVars) {
- return perform(postRequestBuilder(payload, urlTemplate, uriVars));
- }
-
- /* PUT */
- /**
- * Factory for a POST request builder containing a body.
- *
- * @param payload to be serialized as body in contentType format
- * @param contentType format to be used for payload serialization
- * @param charset char-set for request
- * @param urlTemplate API end-point to be requested
- * @param uriVars values to replace end-point placeholders with
- * @param payload type
- * @return Request builder to further configure (cookies, additional headers,
- * etc.)
- */
- public MockHttpServletRequestBuilder putRequestBuilder(T payload, MediaType contentType, Charset charset,
- String urlTemplate, Object... uriVars) {
- return feed(requestBuilder(Optional.empty(), Optional.of(charset), HttpMethod.PUT, urlTemplate, uriVars),
- payload, contentType, charset);
- }
-
- /**
- * Factory for a POST request builder containing a body.
- *
- * @param payload to be serialized as body in contentType format
- * @param contentType format to be used for payload serialization
- * @param urlTemplate API end-point to be requested
- * @param uriVars values to replace end-point placeholders with
- * @param payload type
- * @return Request builder to further configure (cookies, additional headers,
- * etc.)
- */
- public MockHttpServletRequestBuilder putRequestBuilder(T payload, MediaType contentType, String urlTemplate,
- Object... uriVars) {
- return putRequestBuilder(payload, contentType, charset, urlTemplate, uriVars);
- }
-
- /**
- * Factory for a POST request builder containing a body. Default media-type is
- * used for payload serialization (and Content-type header).
- *
- * @param payload to be serialized as body in contentType format
- * @param urlTemplate API end-point to be requested
- * @param uriVars values to replace end-point placeholders with
- * @param payload type
- * @return Request builder to further configure (cookies, additional headers,
- * etc.)
- */
- public MockHttpServletRequestBuilder putRequestBuilder(T payload, String urlTemplate, Object... uriVars) {
- return putRequestBuilder(payload, mediaType, charset, urlTemplate, uriVars);
- }
-
- /**
- * Shortcut to issue a PUT request.
- *
- * @param payload request body
- * @param contentType payload serialization media-type
- * @param charset char-set for request and response
- * @param urlTemplate API end-point to request
- * @param uriVars values to be used in end-point URL placehoders
- * @param payload type
- * @return API response to be tested
- */
- public ResultActions put(T payload, MediaType contentType, String charset, String urlTemplate,
- Object... uriVars) {
- return perform(putRequestBuilder(payload, contentType, charset, urlTemplate, uriVars));
- }
-
- /**
- * Shortcut to issue a PUT request.
- *
- * @param payload request body
- * @param contentType payload serialization media-type
- * @param urlTemplate API end-point to request
- * @param uriVars values to be used in end-point URL placehoders
- * @param payload type
- * @return API response to be tested
- */
- public ResultActions put(T payload, MediaType contentType, String urlTemplate, Object... uriVars) {
- return perform(putRequestBuilder(payload, contentType, urlTemplate, uriVars));
- }
-
- /**
- * Shortcut to issue a PUT request (with default media-type as Content-type).
- *
- * @param payload request body
- * @param urlTemplate API end-point to request
- * @param uriVars values to be used in end-point URL placehoders
- * @param payload type
- * @return API response to be tested
- */
- public ResultActions put(T payload, String urlTemplate, Object... uriVars) {
- return perform(putRequestBuilder(payload, urlTemplate, uriVars));
- }
-
- /* PATCH */
- /**
- * Factory for a patch request builder (with Content-type already set).
- *
- * @param payload request body
- * @param charset char-set to be used for serialized payloads
- * @param contentType payload serialization format
- * @param urlTemplate API end-point
- * @param uriVars values for end-point placeholders
- * @param payload type
- * @return request builder to further configure (additional headers, cookies,
- * etc.)
- */
- public MockHttpServletRequestBuilder patchRequestBuilder(T payload, MediaType contentType, Charset charset,
- String urlTemplate, Object... uriVars) {
- return feed(requestBuilder(Optional.empty(), Optional.of(charset), HttpMethod.PATCH, urlTemplate, uriVars),
- payload, contentType, charset);
- }
-
- /**
- * Factory for a patch request builder (with Content-type already set).
- *
- * @param payload request body
- * @param contentType payload serialization format
- * @param urlTemplate API end-point
- * @param uriVars values for end-point placeholders
- * @param payload type
- * @return request builder to further configure (additional headers, cookies,
- * etc.)
- */
- public MockHttpServletRequestBuilder patchRequestBuilder(T payload, MediaType contentType, String urlTemplate,
- Object... uriVars) {
- return patchRequestBuilder(payload, contentType, charset, urlTemplate, uriVars);
- }
-
- /**
- * Factory for a patch request builder (with Content-type set to default
- * media-type).
- *
- * @param payload request body
- * @param urlTemplate API end-point
- * @param uriVars values for end-point placeholders
- * @param payload type
- * @return request builder to further configure (additional headers, cookies,
- * etc.)
- */
- public MockHttpServletRequestBuilder patchRequestBuilder(T payload, String urlTemplate, Object... uriVars) {
- return patchRequestBuilder(payload, mediaType, charset, urlTemplate, uriVars);
- }
-
- /**
- * Shortcut to issue a patch request with Content-type header and a body.
- *
- * @param payload request body
- * @param contentType to be used for payload serialization
- * @param charset to be used for payload serialization
- * @param urlTemplate end-point URL
- * @param uriVars values for end-point URL placeholders
- * @param payload type
- * @return API response to be tested
- */
- public ResultActions patch(T payload, MediaType contentType, Charset charset, String urlTemplate,
- Object... uriVars) {
- return perform(patchRequestBuilder(payload, contentType, charset, urlTemplate, uriVars));
- }
-
- /**
- * Shortcut to issue a patch request with Content-type header and a body.
- *
- * @param payload request body
- * @param contentType to be used for payload serialization
- * @param urlTemplate end-point URL
- * @param uriVars values for end-point URL placeholders
- * @param payload type
- * @return API response to be tested
- */
- public ResultActions patch(T payload, MediaType contentType, String urlTemplate, Object... uriVars) {
- return perform(patchRequestBuilder(payload, contentType, urlTemplate, uriVars));
- }
-
- /**
- * Shortcut to issue a patch request with Content-type header and a body (using
- * default media-type).
- *
- * @param payload request body
- * @param urlTemplate end-point URL
- * @param uriVars values for end-point URL placeholders
- * @param payload type
- * @return API response to be tested
- */
- public ResultActions patch(T payload, String urlTemplate, Object... uriVars) {
- return perform(patchRequestBuilder(payload, urlTemplate, uriVars));
- }
-
- /* DELETE */
- /**
- * Factory for a DELETE request builder.
- *
- * @param urlTemplate API end-point
- * @param uriVars values for end-point URL placeholders
- * @return request builder to further configure (additional headers, cookies,
- * etc.)
- */
- public MockHttpServletRequestBuilder deleteRequestBuilder(String urlTemplate, Object... uriVars) {
- return requestBuilder(Optional.empty(), Optional.empty(), HttpMethod.DELETE, urlTemplate, uriVars);
- }
-
- /**
- * Shortcut to issue a DELETE request (no header)
- *
- * @param urlTemplate API end-point
- * @param uriVars values for end-point URL placeholders
- * @return API response to be tested
- */
- public ResultActions delete(String urlTemplate, Object... uriVars) {
- return perform(deleteRequestBuilder(urlTemplate, uriVars));
- }
-
- /* HEAD */
- /**
- * Factory for a HEAD request builder.
- *
- * @param urlTemplate API end-point
- * @param uriVars values for end-point URL placeholders
- * @return request builder to further configure (additional headers, cookies,
- * etc.)
- */
- public MockHttpServletRequestBuilder headRequestBuilder(String urlTemplate, Object... uriVars) {
- return requestBuilder(Optional.empty(), Optional.empty(), HttpMethod.HEAD, urlTemplate, uriVars);
- }
-
- /**
- * Shortcut to issue a HEAD request (no header)
- *
- * @param urlTemplate API end-point
- * @param uriVars values for end-point URL placeholders
- * @return API response to be tested
- */
- public ResultActions head(String urlTemplate, Object... uriVars) {
- return perform(headRequestBuilder(urlTemplate, uriVars));
- }
-
- /* OPTION */
- /**
- * Factory for an OPTION request initialized with an Accept header.
- *
- * @param accept response body media-type
- * @param urlTemplate API end-point
- * @param uriVars values for end-point URL placeholders
- * @return request builder to be further configured (additional headers,
- * cookies, etc.)
- */
- public MockHttpServletRequestBuilder optionRequestBuilder(MediaType accept, String urlTemplate, Object... uriVars) {
- return requestBuilder(Optional.of(accept), Optional.empty(), HttpMethod.OPTIONS, urlTemplate, uriVars);
- }
-
- /**
- * Factory for an OPTION request initialized with an Accept header set to
- * default media-type.
- *
- * @param urlTemplate API end-point
- * @param uriVars values for end-point URL placeholders
- * @return request builder to be further configured (additional headers,
- * cookies, etc.)
- */
- public MockHttpServletRequestBuilder optionRequestBuilder(String urlTemplate, Object... uriVars) {
- return optionRequestBuilder(mediaType, urlTemplate, uriVars);
- }
-
- /**
- * Shortcut to issue an OPTION request with Accept header
- *
- * @param accept response body media-type
- * @param urlTemplate API end-point
- * @param uriVars values for end-point URL placeholders
- * @return API response to be further configured
- */
- public ResultActions option(MediaType accept, String urlTemplate, Object... uriVars) {
- return perform(optionRequestBuilder(accept, urlTemplate, uriVars));
- }
-
- /**
- * Shortcut to issue an OPTION request with default media-type as Accept header
- *
- * @param urlTemplate API end-point
- * @param uriVars values for end-point URL placeholders
- * @return API response to be further configured
- */
- public ResultActions option(String urlTemplate, Object... uriVars) {
- return perform(optionRequestBuilder(urlTemplate, uriVars));
- }
-
- /**
- * Adds serialized payload to request content. Rather low-level, consider using
- * this class
- * {@link org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder
- * MockHttpServletRequestBuilder} factories instead
- * (getRequestBuilder, postRequestBuilder, etc.)
- *
- * @param request builder you want to set body to
- * @param payload object to be serialized as body
- * @param mediaType what format you want payload to be serialized to
- * (corresponding HttpMessageConverter must be registered)
- * @param charset char-set to be used for payload serialization
- * @param payload type
- * @return the request with provided payload as content
- */
- public MockHttpServletRequestBuilder feed(MockHttpServletRequestBuilder request, T payload, MediaType mediaType,
- Charset charset) {
- if (payload == null) {
- return request;
- }
-
- final var msg = conv.outputMessage(payload, new MediaType(mediaType, charset));
- return request.headers(msg.headers).content(msg.out.toByteArray());
- }
-
- public DispatcherServlet getDispatcherServlet() {
- return mockMvc.getDispatcherServlet();
- }
-
- /**
- * @param postProcessor request post-processor to be added to the list of those
- * applied before request is performed
- * @return this {@link MockMvcSupport}
- */
- public MockMvcSupport with(RequestPostProcessor postProcessor) {
- Assert.notNull(postProcessor, "postProcessor is required");
- this.postProcessors.add(postProcessor);
- return this;
- }
+ private final MockMvc mockMvc;
+
+ private final SerializationHelper conv;
+
+ private MediaType mediaType;
+
+ private Charset charset;
+
+ private boolean isSecure;
+
+ private boolean isCsrf;
+
+ private final List postProcessors;
+
+ /**
+ * @param mockMvc wrapped Spring MVC testing helper
+ * @param serializationHelper used to serialize payloads to requested {@code Content-type} using Spring registered message converters
+ * @param mockMvcProperties default values for media-type, charset and https usage
+ */
+ public MockMvcSupport(
+ MockMvc mockMvc,
+ SerializationHelper serializationHelper,
+ MockMvcProperties mockMvcProperties,
+ ServerProperties serverProperties,
+ SpringAddonsSecurityProperties addonsProperties) {
+ this.mockMvc = mockMvc;
+ this.conv = serializationHelper;
+ this.mediaType = MediaType.valueOf(mockMvcProperties.getDefaultMediaType());
+ this.charset = Charset.forName(mockMvcProperties.getDefaultCharset());
+ this.postProcessors = new ArrayList<>();
+ this.isSecure = serverProperties.getSsl() != null && serverProperties.getSsl().isEnabled();
+ this.isCsrf = !addonsProperties.getCsrf().equals(Csrf.DISABLE);
+ }
+
+ /**
+ * @param isSecure if true, requests are sent with https instead of http
+ * @return
+ */
+ public MockMvcSupport setSecure(boolean isSecure) {
+ this.isSecure = isSecure;
+ return this;
+ }
+
+ /**
+ * @param isCsrf should MockMvcRequests be issued with CSRF
+ * @return
+ */
+ public MockMvcSupport setCsrf(boolean isCsrf) {
+ this.isCsrf = isCsrf;
+ return this;
+ }
+
+ /**
+ * @param mediaType override configured default media-type
+ * @return
+ */
+ public MockMvcSupport setMediaType(MediaType mediaType) {
+ this.mediaType = mediaType;
+ return this;
+ }
+
+ /**
+ * @param charset override configured default charset
+ * @return
+ */
+ public MockMvcSupport setCharset(Charset charset) {
+ this.charset = charset;
+ return this;
+ }
+
+ /**
+ * Factory for a generic {@link org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder MockHttpServletRequestBuilder} with relevant
+ * "Accept" and "Content-Type" headers. You might prefer to use {@link #getRequestBuilder(MediaType, String, Object...) getRequestBuilder} or alike which go
+ * further with request pre-configuration or even {@link #get(MediaType, String, Object...) get}, {@link #post(Object, String, Object...)} and so on which
+ * issue simple requests in one step.
+ *
+ * @param accept should be non-empty when issuing response with body (GET, POST, OPTION), none otherwise
+ * @param charset char-set to be used for serialized payloads
+ * @param method whatever HTTP verb you need
+ * @param urlTemplate end-point to be requested
+ * @param uriVars end-point template placeholders values
+ * @return a request builder with minimal info you can tweak further: add headers, cookies, etc.
+ */
+ public
+ MockHttpServletRequestBuilder
+ requestBuilder(Optional accept, Optional charset, HttpMethod method, String urlTemplate, Object... uriVars) {
+ final var builder = request(method, urlTemplate, uriVars);
+ accept.ifPresent(builder::accept);
+ charset.ifPresent(c -> builder.characterEncoding(c.toString()));
+ builder.secure(isSecure);
+ if (isCsrf) {
+ builder.with(csrf());
+ }
+ return builder;
+ }
+
+ /**
+ * To be called with fully configured request builder (wraps MockMvc
+ * {@link org.springframework.test.web.servlet.MockMvc#perform(org.springframework.test.web.servlet.RequestBuilder) perform}).
+ *
+ * @param requestBuilder fully configured request
+ * @return API answer to be tested
+ */
+ public ResultActions perform(MockHttpServletRequestBuilder requestBuilder) {
+ postProcessors.forEach(requestBuilder::with);
+ try {
+ return mockMvc.perform(requestBuilder);
+ } catch (final Exception e) {
+ throw new MockMvcPerformException(e);
+ }
+ }
+
+ /* GET */
+ /**
+ * Factory providing with a request builder to issue a GET request (with Accept header).
+ *
+ * @param accept determines request Accept header (and response body format)
+ * @param urlTemplate API end-point to call
+ * @param uriVars values to feed URL template placeholders
+ * @return a request builder to be further configured (additional headers, cookies, etc.)
+ */
+ public MockHttpServletRequestBuilder getRequestBuilder(MediaType accept, String urlTemplate, Object... uriVars) {
+ return requestBuilder(Optional.of(accept), Optional.empty(), HttpMethod.GET, urlTemplate, uriVars);
+ }
+
+ /**
+ * Factory providing with a request builder to issue a GET request (with Accept header defaulted to what this helper is constructed with).
+ *
+ * @param urlTemplate API end-point to call
+ * @param uriVars values to feed URL template placeholders
+ * @return a request builder to be further configured (additional headers, cookies, etc.)
+ */
+ public MockHttpServletRequestBuilder getRequestBuilder(String urlTemplate, Object... uriVars) {
+ return getRequestBuilder(mediaType, urlTemplate, uriVars);
+ }
+
+ /**
+ * Shortcut to issue a GET request with minimal headers and submit it.
+ *
+ * @param accept determines request Accept header (and response body format)
+ * @param urlTemplate API endpoint to be requested
+ * @param uriVars values to replace endpoint placeholders with
+ * @return API response to test
+ */
+ public ResultActions get(MediaType accept, String urlTemplate, Object... uriVars) {
+ return perform(getRequestBuilder(accept, urlTemplate, uriVars));
+ }
+
+ /**
+ * Shortcut to create a builder for a GET request with minimal headers and submit it (Accept header defaulted to what this helper was constructed with).
+ *
+ * @param urlTemplate API endpoint to be requested
+ * @param uriVars values to replace endpoint placeholders with
+ * @return API response to test
+ */
+ public ResultActions get(String urlTemplate, Object... uriVars) {
+ return perform(getRequestBuilder(urlTemplate, uriVars));
+ }
+
+ /* POST */
+ /**
+ * Factory for a POST request builder containing a body set to payload serialized in given media type (with adequate Content-type header).
+ *
+ * @param payload to be serialized as body in contentType format
+ * @param contentType format to be used for payload serialization
+ * @param charset char-set for request and response
+ * @param accept how should the response body be serialized (if any)
+ * @param urlTemplate API end-point to be requested
+ * @param uriVars values to replace end-point placeholders with
+ * @param payload type
+ * @return Request builder to further configure (cookies, additional headers, etc.)
+ */
+ public <
+ T>
+ MockHttpServletRequestBuilder
+ postRequestBuilder(T payload, MediaType contentType, Charset charset, MediaType accept, String urlTemplate, Object... uriVars) {
+ return feed(requestBuilder(Optional.of(accept), Optional.of(charset), HttpMethod.POST, urlTemplate, uriVars), payload, contentType, charset);
+ }
+
+ /**
+ * Factory for a POST request builder containing a body set to payload serialized in given media type (with adequate Content-type header).
+ *
+ * @param payload to be serialized as body in contentType format
+ * @param contentType format to be used for payload serialization
+ * @param accept how should the response body be serialized (if any)
+ * @param urlTemplate API end-point to be requested
+ * @param uriVars values to replace end-point placeholders with
+ * @param payload type
+ * @return Request builder to further configure (cookies, additional headers, etc.)
+ */
+ public MockHttpServletRequestBuilder postRequestBuilder(T payload, MediaType contentType, MediaType accept, String urlTemplate, Object... uriVars) {
+ return postRequestBuilder(payload, contentType, charset, accept, urlTemplate, uriVars);
+ }
+
+ /**
+ * Factory for a POST request builder. Body is pre-set to payload. Both Content-type and Accept headers are set to default media-type.
+ *
+ * @param payload request body
+ * @param urlTemplate API end-point
+ * @param uriVars values ofr URL template placeholders
+ * @param payload type
+ * @return Request builder to further configure (cookies, additional headers, etc.)
+ */
+ public MockHttpServletRequestBuilder postRequestBuilder(T payload, String urlTemplate, Object... uriVars) {
+ return postRequestBuilder(payload, mediaType, charset, mediaType, urlTemplate, uriVars);
+ }
+
+ /**
+ * Shortcut to issue a POST request with provided payload as body, using given media-type for serialization (and Content-type header).
+ *
+ * @param payload POST request body
+ * @param contentType media type used to serialize payload and set Content-type header
+ * @param accept media-type to be set as Accept header (and response serialization)
+ * @param charset char-set for request and response
+ * @param urlTemplate API end-point to be called
+ * @param uriVars values ofr URL template placeholders
+ * @param payload type
+ * @return API response to test
+ */
+ public ResultActions post(T payload, MediaType contentType, Charset charset, MediaType accept, String urlTemplate, Object... uriVars) {
+ return perform(postRequestBuilder(payload, contentType, charset, accept, urlTemplate, uriVars));
+ }
+
+ /**
+ * Shortcut to issue a POST request with provided payload as body, using given media-type for serialization (and Content-type header).
+ *
+ * @param payload POST request body
+ * @param contentType media type used to serialize payload and set Content-type header
+ * @param accept media-type to be set as Accept header (and response serialization)
+ * @param urlTemplate API end-point to be called
+ * @param uriVars values ofr URL template placeholders
+ * @param payload type
+ * @return API response to test
+ */
+ public ResultActions post(T payload, MediaType contentType, MediaType accept, String urlTemplate, Object... uriVars) {
+ return perform(postRequestBuilder(payload, contentType, accept, urlTemplate, uriVars));
+ }
+
+ /**
+ * Shortcut to issue a POST request with provided payload as body, using default media-type for serialization (and Content-type header).
+ *
+ * @param payload POST request body
+ * @param urlTemplate API end-point to be called
+ * @param uriVars values ofr URL template placeholders
+ * @param payload type
+ * @return API response to test
+ */
+ public ResultActions post(T payload, String urlTemplate, Object... uriVars) {
+ return perform(postRequestBuilder(payload, urlTemplate, uriVars));
+ }
+
+ /* PUT */
+ /**
+ * Factory for a POST request builder containing a body.
+ *
+ * @param payload to be serialized as body in contentType format
+ * @param contentType format to be used for payload serialization
+ * @param charset char-set for request
+ * @param urlTemplate API end-point to be requested
+ * @param uriVars values to replace end-point placeholders with
+ * @param payload type
+ * @return Request builder to further configure (cookies, additional headers, etc.)
+ */
+ public MockHttpServletRequestBuilder putRequestBuilder(T payload, MediaType contentType, Charset charset, String urlTemplate, Object... uriVars) {
+ return feed(requestBuilder(Optional.empty(), Optional.of(charset), HttpMethod.PUT, urlTemplate, uriVars), payload, contentType, charset);
+ }
+
+ /**
+ * Factory for a POST request builder containing a body.
+ *
+ * @param payload to be serialized as body in contentType format
+ * @param contentType format to be used for payload serialization
+ * @param urlTemplate API end-point to be requested
+ * @param uriVars values to replace end-point placeholders with
+ * @param payload type
+ * @return Request builder to further configure (cookies, additional headers, etc.)
+ */
+ public MockHttpServletRequestBuilder putRequestBuilder(T payload, MediaType contentType, String urlTemplate, Object... uriVars) {
+ return putRequestBuilder(payload, contentType, charset, urlTemplate, uriVars);
+ }
+
+ /**
+ * Factory for a POST request builder containing a body. Default media-type is used for payload serialization (and Content-type header).
+ *
+ * @param payload to be serialized as body in contentType format
+ * @param urlTemplate API end-point to be requested
+ * @param uriVars values to replace end-point placeholders with
+ * @param payload type
+ * @return Request builder to further configure (cookies, additional headers, etc.)
+ */
+ public MockHttpServletRequestBuilder putRequestBuilder(T payload, String urlTemplate, Object... uriVars) {
+ return putRequestBuilder(payload, mediaType, charset, urlTemplate, uriVars);
+ }
+
+ /**
+ * Shortcut to issue a PUT request.
+ *
+ * @param payload request body
+ * @param contentType payload serialization media-type
+ * @param charset char-set for request and response
+ * @param urlTemplate API end-point to request
+ * @param uriVars values to be used in end-point URL placehoders
+ * @param payload type
+ * @return API response to be tested
+ */
+ public ResultActions put(T payload, MediaType contentType, String charset, String urlTemplate, Object... uriVars) {
+ return perform(putRequestBuilder(payload, contentType, charset, urlTemplate, uriVars));
+ }
+
+ /**
+ * Shortcut to issue a PUT request.
+ *
+ * @param payload request body
+ * @param contentType payload serialization media-type
+ * @param urlTemplate API end-point to request
+ * @param uriVars values to be used in end-point URL placehoders
+ * @param payload type
+ * @return API response to be tested
+ */
+ public ResultActions put(T payload, MediaType contentType, String urlTemplate, Object... uriVars) {
+ return perform(putRequestBuilder(payload, contentType, urlTemplate, uriVars));
+ }
+
+ /**
+ * Shortcut to issue a PUT request (with default media-type as Content-type).
+ *
+ * @param payload request body
+ * @param urlTemplate API end-point to request
+ * @param uriVars values to be used in end-point URL placehoders
+ * @param payload type
+ * @return API response to be tested
+ */
+ public ResultActions put(T payload, String urlTemplate, Object... uriVars) {
+ return perform(putRequestBuilder(payload, urlTemplate, uriVars));
+ }
+
+ /* PATCH */
+ /**
+ * Factory for a patch request builder (with Content-type already set).
+ *
+ * @param payload request body
+ * @param charset char-set to be used for serialized payloads
+ * @param contentType payload serialization format
+ * @param urlTemplate API end-point
+ * @param uriVars values for end-point placeholders
+ * @param payload type
+ * @return request builder to further configure (additional headers, cookies, etc.)
+ */
+ public MockHttpServletRequestBuilder patchRequestBuilder(T payload, MediaType contentType, Charset charset, String urlTemplate, Object... uriVars) {
+ return feed(requestBuilder(Optional.empty(), Optional.of(charset), HttpMethod.PATCH, urlTemplate, uriVars), payload, contentType, charset);
+ }
+
+ /**
+ * Factory for a patch request builder (with Content-type already set).
+ *
+ * @param payload request body
+ * @param contentType payload serialization format
+ * @param urlTemplate API end-point
+ * @param uriVars values for end-point placeholders
+ * @param payload type
+ * @return request builder to further configure (additional headers, cookies, etc.)
+ */
+ public MockHttpServletRequestBuilder patchRequestBuilder(T payload, MediaType contentType, String urlTemplate, Object... uriVars) {
+ return patchRequestBuilder(payload, contentType, charset, urlTemplate, uriVars);
+ }
+
+ /**
+ * Factory for a patch request builder (with Content-type set to default media-type).
+ *
+ * @param payload request body
+ * @param urlTemplate API end-point
+ * @param uriVars values for end-point placeholders
+ * @param payload type
+ * @return request builder to further configure (additional headers, cookies, etc.)
+ */
+ public MockHttpServletRequestBuilder patchRequestBuilder(T payload, String urlTemplate, Object... uriVars) {
+ return patchRequestBuilder(payload, mediaType, charset, urlTemplate, uriVars);
+ }
+
+ /**
+ * Shortcut to issue a patch request with Content-type header and a body.
+ *
+ * @param payload request body
+ * @param contentType to be used for payload serialization
+ * @param charset to be used for payload serialization
+ * @param urlTemplate end-point URL
+ * @param uriVars values for end-point URL placeholders
+ * @param payload type
+ * @return API response to be tested
+ */
+ public ResultActions patch(T payload, MediaType contentType, Charset charset, String urlTemplate, Object... uriVars) {
+ return perform(patchRequestBuilder(payload, contentType, charset, urlTemplate, uriVars));
+ }
+
+ /**
+ * Shortcut to issue a patch request with Content-type header and a body.
+ *
+ * @param payload request body
+ * @param contentType to be used for payload serialization
+ * @param urlTemplate end-point URL
+ * @param uriVars values for end-point URL placeholders
+ * @param payload type
+ * @return API response to be tested
+ */
+ public ResultActions patch(T payload, MediaType contentType, String urlTemplate, Object... uriVars) {
+ return perform(patchRequestBuilder(payload, contentType, urlTemplate, uriVars));
+ }
+
+ /**
+ * Shortcut to issue a patch request with Content-type header and a body (using default media-type).
+ *
+ * @param payload request body
+ * @param urlTemplate end-point URL
+ * @param uriVars values for end-point URL placeholders
+ * @param payload type
+ * @return API response to be tested
+ */
+ public ResultActions patch(T payload, String urlTemplate, Object... uriVars) {
+ return perform(patchRequestBuilder(payload, urlTemplate, uriVars));
+ }
+
+ /* DELETE */
+ /**
+ * Factory for a DELETE request builder.
+ *
+ * @param urlTemplate API end-point
+ * @param uriVars values for end-point URL placeholders
+ * @return request builder to further configure (additional headers, cookies, etc.)
+ */
+ public MockHttpServletRequestBuilder deleteRequestBuilder(String urlTemplate, Object... uriVars) {
+ return requestBuilder(Optional.empty(), Optional.empty(), HttpMethod.DELETE, urlTemplate, uriVars);
+ }
+
+ /**
+ * Shortcut to issue a DELETE request (no header)
+ *
+ * @param urlTemplate API end-point
+ * @param uriVars values for end-point URL placeholders
+ * @return API response to be tested
+ */
+ public ResultActions delete(String urlTemplate, Object... uriVars) {
+ return perform(deleteRequestBuilder(urlTemplate, uriVars));
+ }
+
+ /* HEAD */
+ /**
+ * Factory for a HEAD request builder.
+ *
+ * @param urlTemplate API end-point
+ * @param uriVars values for end-point URL placeholders
+ * @return request builder to further configure (additional headers, cookies, etc.)
+ */
+ public MockHttpServletRequestBuilder headRequestBuilder(String urlTemplate, Object... uriVars) {
+ return requestBuilder(Optional.empty(), Optional.empty(), HttpMethod.HEAD, urlTemplate, uriVars);
+ }
+
+ /**
+ * Shortcut to issue a HEAD request (no header)
+ *
+ * @param urlTemplate API end-point
+ * @param uriVars values for end-point URL placeholders
+ * @return API response to be tested
+ */
+ public ResultActions head(String urlTemplate, Object... uriVars) {
+ return perform(headRequestBuilder(urlTemplate, uriVars));
+ }
+
+ /* OPTION */
+ /**
+ * Factory for an OPTION request initialized with an Accept header.
+ *
+ * @param accept response body media-type
+ * @param urlTemplate API end-point
+ * @param uriVars values for end-point URL placeholders
+ * @return request builder to be further configured (additional headers, cookies, etc.)
+ */
+ public MockHttpServletRequestBuilder optionRequestBuilder(MediaType accept, String urlTemplate, Object... uriVars) {
+ return requestBuilder(Optional.of(accept), Optional.empty(), HttpMethod.OPTIONS, urlTemplate, uriVars);
+ }
+
+ /**
+ * Factory for an OPTION request initialized with an Accept header set to default media-type.
+ *
+ * @param urlTemplate API end-point
+ * @param uriVars values for end-point URL placeholders
+ * @return request builder to be further configured (additional headers, cookies, etc.)
+ */
+ public MockHttpServletRequestBuilder optionRequestBuilder(String urlTemplate, Object... uriVars) {
+ return optionRequestBuilder(mediaType, urlTemplate, uriVars);
+ }
+
+ /**
+ * Shortcut to issue an OPTION request with Accept header
+ *
+ * @param accept response body media-type
+ * @param urlTemplate API end-point
+ * @param uriVars values for end-point URL placeholders
+ * @return API response to be further configured
+ */
+ public ResultActions option(MediaType accept, String urlTemplate, Object... uriVars) {
+ return perform(optionRequestBuilder(accept, urlTemplate, uriVars));
+ }
+
+ /**
+ * Shortcut to issue an OPTION request with default media-type as Accept header
+ *
+ * @param urlTemplate API end-point
+ * @param uriVars values for end-point URL placeholders
+ * @return API response to be further configured
+ */
+ public ResultActions option(String urlTemplate, Object... uriVars) {
+ return perform(optionRequestBuilder(urlTemplate, uriVars));
+ }
+
+ /**
+ * Adds serialized payload to request content. Rather low-level, consider using this class
+ * {@link org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder MockHttpServletRequestBuilder} factories instead (getRequestBuilder,
+ * postRequestBuilder, etc.)
+ *
+ * @param request builder you want to set body to
+ * @param payload object to be serialized as body
+ * @param mediaType what format you want payload to be serialized to (corresponding HttpMessageConverter must be registered)
+ * @param charset char-set to be used for payload serialization
+ * @param payload type
+ * @return the request with provided payload as content
+ */
+ public MockHttpServletRequestBuilder feed(MockHttpServletRequestBuilder request, T payload, MediaType mediaType, Charset charset) {
+ if (payload == null) {
+ return request;
+ }
+
+ final var msg = conv.outputMessage(payload, new MediaType(mediaType, charset));
+ return request.headers(msg.headers).content(msg.out.toByteArray());
+ }
+
+ public DispatcherServlet getDispatcherServlet() {
+ return mockMvc.getDispatcherServlet();
+ }
+
+ /**
+ * @param postProcessor request post-processor to be added to the list of those applied before request is performed
+ * @return this {@link MockMvcSupport}
+ */
+ public MockMvcSupport with(RequestPostProcessor postProcessor) {
+ Assert.notNull(postProcessor, "postProcessor is required");
+ this.postProcessors.add(postProcessor);
+ return this;
+ }
}
\ No newline at end of file
diff --git a/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/OidcIdAuthenticationTokenRequestPostProcessor.java b/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/OidcIdAuthenticationTokenRequestPostProcessor.java
index 006be8cc4..f6857150b 100644
--- a/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/OidcIdAuthenticationTokenRequestPostProcessor.java
+++ b/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/OidcIdAuthenticationTokenRequestPostProcessor.java
@@ -1,14 +1,13 @@
/*
* Copyright 2019 Jérôme Wacongne
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may
- * obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the
+ * License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
- * and limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
package com.c4_soft.springaddons.security.oauth2.test.mockmvc;
diff --git a/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/ServletUnitTestingSupport.java b/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/ServletUnitTestingSupport.java
index 33ee02dc6..91f42aa90 100644
--- a/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/ServletUnitTestingSupport.java
+++ b/webmvc/spring-addons-webmvc-test/src/main/java/com/c4_soft/springaddons/security/oauth2/test/mockmvc/ServletUnitTestingSupport.java
@@ -1,14 +1,13 @@
/*
* Copyright 2019 Jérôme Wacongne
*
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the
+ * License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
- * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
package com.c4_soft.springaddons.security.oauth2.test.mockmvc;
@@ -18,10 +17,8 @@
import org.springframework.test.web.servlet.MockMvc;
/**
- * Helper class for servlet {@code @Controller} unit-tests using security flow
- * API (useless if using annotations).
- * Might be used either as a parent class (easier) or collaborator (requires
- * some test configuration).
+ * Helper class for servlet {@code @Controller} unit-tests using security flow API (useless if using annotations).
+ * Might be used either as a parent class (easier) or collaborator (requires some test configuration).
*
* @author Jérôme Wacongne <ch4mp@c4-soft.com>
*/
@@ -32,8 +29,7 @@ public class ServletUnitTestingSupport {
protected BeanFactory beanFactory;
/**
- * @return ready to use {@link MockMvcSupport}, a {@link MockMvc} wrapper
- * providing helpers for common REST requests
+ * @return ready to use {@link MockMvcSupport}, a {@link MockMvc} wrapper providing helpers for common REST requests
*/
public MockMvcSupport mockMvc() {
return beanFactory.getBean(MockMvcSupport.class);