- * This bean is auto-configured by {@link SpringAddonsOAuth2ClientBeans} as {@link ConditionalOnMissingBean @ConditionalOnMissingBean} of type
+ * This bean is auto-configured by {@link ReactiveSpringAddonsOidcClientBeans} as {@link ConditionalOnMissingBean @ConditionalOnMissingBean} of type
* {@link ServerLogoutSuccessHandler}. Usage:
*
*
@@ -42,11 +42,11 @@
*
* @author Jerome Wacongne ch4mp@c4-soft.com
* @see SpringAddonsOAuth2LogoutRequestUriBuilder
- * @see SpringAddonsOAuth2ClientProperties
+ * @see SpringAddonsOidcClientProperties
*/
@Data
@RequiredArgsConstructor
-public class SpringAddonsOAuth2ServerLogoutSuccessHandler implements ServerLogoutSuccessHandler {
+public class SpringAddonsServerLogoutSuccessHandler implements ServerLogoutSuccessHandler {
private final LogoutRequestUriBuilder uriBuilder;
private final ReactiveClientRegistrationRepository clientRegistrationRepo;
private final ServerRedirectStrategy redirectStrategy = new DefaultServerRedirectStrategy();
diff --git a/webflux/spring-addons-webflux-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/SpringAddonsServerOAuth2AuthorizationRequestResolver.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/client/SpringAddonsServerOAuth2AuthorizationRequestResolver.java
similarity index 90%
rename from webflux/spring-addons-webflux-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/SpringAddonsServerOAuth2AuthorizationRequestResolver.java
rename to spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/client/SpringAddonsServerOAuth2AuthorizationRequestResolver.java
index 6db7f988c..c14cf10ab 100644
--- a/webflux/spring-addons-webflux-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/SpringAddonsServerOAuth2AuthorizationRequestResolver.java
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/client/SpringAddonsServerOAuth2AuthorizationRequestResolver.java
@@ -1,4 +1,4 @@
-package com.c4_soft.springaddons.security.oauth2.config.reactive;
+package com.c4_soft.springaddons.security.oidc.starter.reactive.client;
import java.util.HashMap;
import java.util.Map;
@@ -13,8 +13,8 @@
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.web.server.ServerWebExchange;
-import com.c4_soft.springaddons.security.oauth2.config.SpringAddonsOAuth2ClientProperties;
-import com.c4_soft.springaddons.security.oauth2.config.SpringAddonsOAuth2ClientProperties.RequestParam;
+import com.c4_soft.springaddons.security.oidc.starter.properties.SpringAddonsOidcClientProperties;
+import com.c4_soft.springaddons.security.oidc.starter.properties.SpringAddonsOidcClientProperties.RequestParam;
import reactor.core.publisher.Mono;
@@ -27,7 +27,7 @@ public class SpringAddonsServerOAuth2AuthorizationRequestResolver extends Defaul
public SpringAddonsServerOAuth2AuthorizationRequestResolver(
InMemoryReactiveClientRegistrationRepository clientRegistrationRepository,
- SpringAddonsOAuth2ClientProperties addonsClientProperties) {
+ SpringAddonsOidcClientProperties addonsClientProperties) {
super(clientRegistrationRepository);
clientRegistrationRepository.forEach(reg -> {
final var params = addonsClientProperties.getAuthorizationRequestParams().get(reg.getRegistrationId());
diff --git a/webflux/spring-addons-webflux-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/SpringAddonsServerOAuth2AuthorizedClientRepository.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/client/SpringAddonsServerOAuth2AuthorizedClientRepository.java
similarity index 97%
rename from webflux/spring-addons-webflux-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/SpringAddonsServerOAuth2AuthorizedClientRepository.java
rename to spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/client/SpringAddonsServerOAuth2AuthorizedClientRepository.java
index 3f0e74c18..bc51a2c5e 100644
--- a/webflux/spring-addons-webflux-client/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/SpringAddonsServerOAuth2AuthorizedClientRepository.java
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/client/SpringAddonsServerOAuth2AuthorizedClientRepository.java
@@ -1,4 +1,4 @@
-package com.c4_soft.springaddons.security.oauth2.config.reactive;
+package com.c4_soft.springaddons.security.oidc.starter.reactive.client;
import java.util.Collections;
import java.util.HashSet;
@@ -20,8 +20,8 @@
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebSession;
-import com.c4_soft.springaddons.security.oauth2.config.reactive.SpringAddonsOAuth2ClientBeans.SpringAddonsWebSessionStore;
-import com.c4_soft.springaddons.security.oauth2.config.reactive.SpringAddonsOAuth2ClientBeans.WebSessionListener;
+import com.c4_soft.springaddons.security.oidc.starter.reactive.client.ReactiveSpringAddonsOidcClientBeans.SpringAddonsWebSessionStore;
+import com.c4_soft.springaddons.security.oidc.starter.reactive.client.ReactiveSpringAddonsOidcClientBeans.WebSessionListener;
import com.nimbusds.jwt.JWTClaimNames;
import reactor.core.publisher.Flux;
diff --git a/spring-addons-oauth2/src/main/java/com/c4_soft/springaddons/security/oauth2/config/ReactiveJwtAbstractAuthenticationTokenConverter.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/ReactiveJwtAbstractAuthenticationTokenConverter.java
similarity index 82%
rename from spring-addons-oauth2/src/main/java/com/c4_soft/springaddons/security/oauth2/config/ReactiveJwtAbstractAuthenticationTokenConverter.java
rename to spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/ReactiveJwtAbstractAuthenticationTokenConverter.java
index 85829bf4b..c4c387ac8 100644
--- a/spring-addons-oauth2/src/main/java/com/c4_soft/springaddons/security/oauth2/config/ReactiveJwtAbstractAuthenticationTokenConverter.java
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/ReactiveJwtAbstractAuthenticationTokenConverter.java
@@ -1,4 +1,4 @@
-package com.c4_soft.springaddons.security.oauth2.config;
+package com.c4_soft.springaddons.security.oidc.starter.reactive.resourceserver;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
diff --git a/webflux/spring-addons-webflux-jwt-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/AddonsWebSecurityBeans.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/ReactiveSpringAddonsOidcResourceServerBeans.java
similarity index 66%
rename from webflux/spring-addons-webflux-jwt-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/AddonsWebSecurityBeans.java
rename to spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/ReactiveSpringAddonsOidcResourceServerBeans.java
index 329d8338f..48ff4ea14 100644
--- a/webflux/spring-addons-webflux-jwt-resource-server/src/main/java/com/c4_soft/springaddons/security/oauth2/config/reactive/AddonsWebSecurityBeans.java
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/ReactiveSpringAddonsOidcResourceServerBeans.java
@@ -1,8 +1,7 @@
-package com.c4_soft.springaddons.security.oauth2.config.reactive;
+package com.c4_soft.springaddons.security.oidc.starter.reactive.resourceserver;
import java.net.URI;
import java.nio.charset.Charset;
-import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -10,19 +9,19 @@
import java.util.stream.Stream;
import org.springframework.boot.autoconfigure.AutoConfiguration;
-import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
-import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
+import org.springframework.core.convert.converter.Converter;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
@@ -42,15 +41,18 @@
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
import org.springframework.security.web.server.csrf.CsrfToken;
import org.springframework.util.StringUtils;
-import org.springframework.web.cors.CorsConfiguration;
-import org.springframework.web.cors.reactive.CorsConfigurationSource;
-import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
-import com.c4_soft.springaddons.security.oauth2.config.ReactiveJwtAbstractAuthenticationTokenConverter;
-import com.c4_soft.springaddons.security.oauth2.config.SpringAddonsSecurityProperties;
-import com.c4_soft.springaddons.security.oauth2.config.SpringAddonsSecurityProperties.CorsProperties;
+import com.c4_soft.springaddons.security.oidc.starter.properties.SpringAddonsOidcProperties;
+import com.c4_soft.springaddons.security.oidc.starter.properties.condition.bean.CookieCsrfCondition;
+import com.c4_soft.springaddons.security.oidc.starter.properties.condition.bean.DefaultAuthenticationManagerResolverCondition;
+import com.c4_soft.springaddons.security.oidc.starter.properties.condition.bean.IsIntrospectingResourceServerCondition;
+import com.c4_soft.springaddons.security.oidc.starter.properties.condition.bean.IsJwtDecoderResourceServerCondition;
+import com.c4_soft.springaddons.security.oidc.starter.properties.condition.configuration.IsNotServlet;
+import com.c4_soft.springaddons.security.oidc.starter.properties.condition.configuration.IsOidcResourceServerCondition;
+import com.c4_soft.springaddons.security.oidc.starter.reactive.ReactiveConfigurationSupport;
+import com.c4_soft.springaddons.security.oidc.starter.reactive.ReactiveSpringAddonsOidcBeans;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;
@@ -66,9 +68,9 @@
*
*
* - SecurityWebFilterChain: applies CORS, CSRF, anonymous, sessionCreationPolicy, SSL redirect and 401 instead of redirect to login properties as
- * defined in {@link SpringAddonsSecurityProperties}
+ * defined in {@link SpringAddonsOidcProperties}
* - AuthorizeExchangeSpecPostProcessor. Override if you need fined grained HTTP security (more than authenticated() to all routes but the ones defined
- * as permitAll() in {@link SpringAddonsSecurityProperties}
+ * as permitAll() in {@link SpringAddonsOidcProperties}
* - Jwt2AuthoritiesConverter: responsible for converting the JWT into Collection<? extends GrantedAuthority>
* - ReactiveJwt2OpenidClaimSetConverter<T extends Map<String, Object> & Serializable>: responsible for converting the JWT into a
* claim-set of your choice (OpenID or not)
@@ -80,12 +82,12 @@
*
* @author Jerome Wacongne ch4mp@c4-soft.com
*/
-@ConditionalOnProperty(matchIfMissing = true, prefix = "com.c4-soft.springaddons.security", name = "enabled")
+@Conditional({ IsOidcResourceServerCondition.class, IsNotServlet.class })
@EnableWebFluxSecurity
@AutoConfiguration
+@ImportAutoConfiguration(ReactiveSpringAddonsOidcBeans.class)
@Slf4j
-@Import({ AddonsSecurityBeans.class })
-public class AddonsWebSecurityBeans {
+public class ReactiveSpringAddonsOidcResourceServerBeans {
/**
*
@@ -100,7 +102,7 @@ public class AddonsWebSecurityBeans {
*
* @param http HTTP security to configure
* @param serverProperties Spring "server" configuration properties
- * @param addonsProperties "com.c4-soft.springaddons.security" configuration properties
+ * @param addonsProperties "com.c4-soft.springaddons.oidc" 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}
@@ -108,20 +110,75 @@ public class AddonsWebSecurityBeans {
* @return A default {@link SecurityWebFilterChain} for reactive resource-servers with JWT decoder(matches all unmatched
* routes with lowest precedence)
*/
+ @Conditional(IsJwtDecoderResourceServerCondition.class)
@Order(Ordered.LOWEST_PRECEDENCE)
@Bean
- SecurityWebFilterChain springAddonsResourceServerSecurityFilterChain(
+ SecurityWebFilterChain springAddonsJwtResourceServerSecurityFilterChain(
ServerHttpSecurity http,
ServerProperties serverProperties,
- SpringAddonsSecurityProperties addonsProperties,
+ SpringAddonsOidcProperties addonsProperties,
ResourceServerAuthorizeExchangeSpecPostProcessor authorizePostProcessor,
ResourceServerHttpSecurityPostProcessor httpPostProcessor,
ReactiveAuthenticationManagerResolver authenticationManagerResolver,
ServerAccessDeniedHandler accessDeniedHandler) {
http.oauth2ResourceServer(server -> server.authenticationManagerResolver(authenticationManagerResolver));
- ReactiveConfigurationSupport
- .configureResourceServer(http, serverProperties, addonsProperties, accessDeniedHandler, authorizePostProcessor, httpPostProcessor);
+ ReactiveConfigurationSupport.configureResourceServer(
+ http,
+ serverProperties,
+ addonsProperties.getResourceserver(),
+ accessDeniedHandler,
+ authorizePostProcessor,
+ httpPostProcessor);
+
+ return http.build();
+ }
+
+ /**
+ *
+ * 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 ResourceServerHttpSecurityPostProcessor} 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.oidc" 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}
+ * @param accessDeniedHandler handler for unauthorized requests (missing or invalid access-token)
+ * @return A default {@link SecurityWebFilterChain} for reactive resource-servers with access-token introspection
+ * (matches all unmatched routes with lowest precedence)
+ */
+ @Conditional(IsIntrospectingResourceServerCondition.class)
+ @Order(Ordered.LOWEST_PRECEDENCE)
+ @Bean
+ SecurityWebFilterChain springAddonsIntrospectingResourceServerSecurityFilterChain(
+ ServerHttpSecurity http,
+ ServerProperties serverProperties,
+ SpringAddonsOidcProperties addonsProperties,
+ ResourceServerAuthorizeExchangeSpecPostProcessor authorizePostProcessor,
+ ResourceServerHttpSecurityPostProcessor httpPostProcessor,
+ org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenAuthenticationConverter introspectionAuthenticationConverter,
+ org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector opaqueTokenIntrospector,
+ ServerAccessDeniedHandler accessDeniedHandler) {
+ http.oauth2ResourceServer(server -> server.opaqueToken(ot -> {
+ ot.introspector(opaqueTokenIntrospector);
+ ot.authenticationConverter(introspectionAuthenticationConverter);
+ }));
+
+ ReactiveConfigurationSupport.configureResourceServer(
+ http,
+ serverProperties,
+ addonsProperties.getResourceserver(),
+ accessDeniedHandler,
+ authorizePostProcessor,
+ httpPostProcessor);
return http.build();
}
@@ -149,54 +206,41 @@ ResourceServerHttpSecurityPostProcessor httpPostProcessor() {
return serverHttpSecurity -> serverHttpSecurity;
}
- CorsConfigurationSource corsConfig(CorsProperties[] corsProperties) {
- 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;
- }
-
/**
* Provides with multi-tenancy: builds a ReactiveAuthenticationManagerResolver per provided OIDC issuer URI
*
* @param auth2ResourceServerProperties "spring.security.oauth2.resourceserver" configuration properties
- * @param addonsProperties "com.c4-soft.springaddons.security" configuration properties
+ * @param addonsProperties "com.c4-soft.springaddons.oidc" configuration properties
* @param jwtAuthenticationConverter converts from a {@link Jwt} to an {@link Authentication} implementation
* @return Multi-tenant {@link ReactiveAuthenticationManagerResolver} (one for each configured issuer)
*/
- @ConditionalOnMissingBean
+ @Conditional(DefaultAuthenticationManagerResolverCondition.class)
@Bean
ReactiveAuthenticationManagerResolver authenticationManagerResolver(
OAuth2ResourceServerProperties auth2ResourceServerProperties,
- SpringAddonsSecurityProperties addonsProperties,
- ReactiveJwtAbstractAuthenticationTokenConverter jwtAuthenticationConverter) {
+ SpringAddonsOidcProperties 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 -> {
- log.warn("spring.security.oauth2.resourceserver configuration will be ignored in favor of com.c4-soft.springaddons.security");
+ log.warn("spring.security.oauth2.resourceserver configuration will be ignored in favor of com.c4-soft.springaddons.oidc");
});
// @formatter:on
final Map> jwtManagers =
- Stream.of(addonsProperties.getIssuers()).collect(Collectors.toMap(issuer -> issuer.getLocation().toString(), issuer -> {
+ Stream.of(addonsProperties.getOps()).collect(Collectors.toMap(issuer -> issuer.getIss().toString(), issuer -> {
final var decoder = issuer.getJwkSetUri() != null && StringUtils.hasLength(issuer.getJwkSetUri().toString())
? NimbusReactiveJwtDecoder.withJwkSetUri(issuer.getJwkSetUri().toString()).build()
- : NimbusReactiveJwtDecoder.withIssuerLocation(issuer.getLocation().toString()).build();
+ : NimbusReactiveJwtDecoder.withIssuerLocation(issuer.getIss().toString()).build();
- final OAuth2TokenValidator defaultValidator = Optional.ofNullable(issuer.getLocation()).map(URI::toString)
+ final OAuth2TokenValidator defaultValidator = Optional.ofNullable(issuer.getIss()).map(URI::toString)
.map(JwtValidators::createDefaultWithIssuer).orElse(JwtValidators.createDefault());
// If the spring-addons conf for resource server contains a non empty audience, add an audience validator
// @formatter:off
- final OAuth2TokenValidator jwtValidator = Optional.ofNullable(issuer.getAudience())
+ final OAuth2TokenValidator jwtValidator = Optional.ofNullable(issuer.getAud())
.filter(StringUtils::hasText)
.map(audience -> new JwtClaimValidator>(
JwtClaimNames.AUD,
@@ -214,7 +258,7 @@ ReactiveAuthenticationManagerResolver authenticationManagerRe
log.debug(
"Building default JwtIssuerReactiveAuthenticationManagerResolver with: {} {}",
auth2ResourceServerProperties.getJwt(),
- Stream.of(addonsProperties.getIssuers()).toList());
+ Stream.of(addonsProperties.getOps()).toList());
return new JwtIssuerReactiveAuthenticationManagerResolver(issuerLocation -> jwtManagers.getOrDefault(issuerLocation, Mono.empty()));
}
@@ -240,7 +284,7 @@ ServerAccessDeniedHandler serverAccessDeniedHandler() {
/**
* https://docs.spring.io/spring-security/reference/5.8/migration/reactive.html#_i_am_using_angularjs_or_another_javascript_framework
*/
- @Conditional(CookieCsrf.class)
+ @Conditional(CookieCsrfCondition.class)
@ConditionalOnMissingBean(name = "csrfCookieWebFilter")
@Bean
WebFilter csrfCookieWebFilter() {
@@ -250,22 +294,4 @@ WebFilter csrfCookieWebFilter() {
}).then(chain.filter(exchange));
};
}
-
- static class CookieCsrf extends AnyNestedCondition {
-
- public CookieCsrf() {
- super(ConfigurationPhase.PARSE_CONFIGURATION);
- }
-
- @ConditionalOnProperty(name = "com.c4-soft.springaddons.security.csrf", havingValue = "cookie-accessible-from-js")
- static class Value1Condition {
-
- }
-
- @ConditionalOnProperty(name = "com.c4-soft.springaddons.security.csrf", havingValue = "cookie-http-only")
- static class Value2Condition {
-
- }
-
- }
}
\ No newline at end of file
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/ResourceServerAuthorizeExchangeSpecPostProcessor.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/ResourceServerAuthorizeExchangeSpecPostProcessor.java
new file mode 100644
index 000000000..83e365bf1
--- /dev/null
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/ResourceServerAuthorizeExchangeSpecPostProcessor.java
@@ -0,0 +1,14 @@
+package com.c4_soft.springaddons.security.oidc.starter.reactive.resourceserver;
+
+import com.c4_soft.springaddons.security.oidc.starter.reactive.AuthorizeExchangeSpecPostProcessor;
+
+/**
+ * Customize access-control for routes which where not listed in
+ * {@link com.c4_soft.springaddons.security.oidc.starter.properties.SpringAddonsOidcClientProperties#permitAll SpringAddonsOidcClientProperties::permit-all} or
+ * {@link com.c4_soft.springaddons.security.oidc.starter.properties.SpringAddonsOidcResourceServerProperties#permitAll
+ * SpringAddonsOidcResourceServerProperties::permit-all}
+ *
+ * @author ch4mp
+ */
+public interface ResourceServerAuthorizeExchangeSpecPostProcessor extends AuthorizeExchangeSpecPostProcessor {
+}
\ No newline at end of file
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/ResourceServerHttpSecurityPostProcessor.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/ResourceServerHttpSecurityPostProcessor.java
new file mode 100644
index 000000000..7a167d4dc
--- /dev/null
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/ResourceServerHttpSecurityPostProcessor.java
@@ -0,0 +1,14 @@
+package com.c4_soft.springaddons.security.oidc.starter.reactive.resourceserver;
+
+import org.springframework.security.config.web.server.ServerHttpSecurity;
+
+import com.c4_soft.springaddons.security.oidc.starter.reactive.HttpSecurityPostProcessor;
+
+/**
+ * Process {@link ServerHttpSecurity} 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 HttpSecurityPostProcessor {
+}
diff --git a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ExpressionInterceptUrlRegistryPostProcessor.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/ExpressionInterceptUrlRegistryPostProcessor.java
similarity index 58%
rename from webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ExpressionInterceptUrlRegistryPostProcessor.java
rename to spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/ExpressionInterceptUrlRegistryPostProcessor.java
index 3beea5559..f86d1c7fc 100644
--- a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ExpressionInterceptUrlRegistryPostProcessor.java
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/ExpressionInterceptUrlRegistryPostProcessor.java
@@ -1,13 +1,15 @@
-package com.c4_soft.springaddons.security.oauth2.config.synchronised;
+package com.c4_soft.springaddons.security.oidc.starter.synchronised;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
-import com.c4_soft.springaddons.security.oauth2.config.SpringAddonsSecurityProperties;
+import com.c4_soft.springaddons.security.oidc.starter.properties.SpringAddonsOidcClientProperties;
+import com.c4_soft.springaddons.security.oidc.starter.properties.SpringAddonsOidcResourceServerProperties;
/**
- * Customize access-control for routes which where not listed in {@link SpringAddonsSecurityProperties#permitAll}
- *
+ * Customize access-control for routes which where not listed in {@link SpringAddonsOidcClientProperties#permitAll} or
+ * {@link SpringAddonsOidcResourceServerProperties#permitAll}
+ *
* @author ch4mp
*/
public interface ExpressionInterceptUrlRegistryPostProcessor {
diff --git a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/HttpServletRequestSupport.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/HttpServletRequestSupport.java
similarity index 98%
rename from webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/HttpServletRequestSupport.java
rename to spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/HttpServletRequestSupport.java
index e8ee54062..aaf42bb8c 100644
--- a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/HttpServletRequestSupport.java
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/HttpServletRequestSupport.java
@@ -1,4 +1,4 @@
-package com.c4_soft.springaddons.security.oauth2.config.synchronised;
+package com.c4_soft.springaddons.security.oidc.starter.synchronised;
import java.util.List;
import java.util.Optional;
diff --git a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ServerHttpSecurityPostProcessor.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/ServerHttpSecurityPostProcessor.java
similarity index 74%
rename from webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ServerHttpSecurityPostProcessor.java
rename to spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/ServerHttpSecurityPostProcessor.java
index 9c5530f72..837c05246 100644
--- a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ServerHttpSecurityPostProcessor.java
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/ServerHttpSecurityPostProcessor.java
@@ -1,4 +1,4 @@
-package com.c4_soft.springaddons.security.oauth2.config.synchronised;
+package com.c4_soft.springaddons.security.oidc.starter.synchronised;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
diff --git a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ServletConfigurationSupport.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/ServletConfigurationSupport.java
similarity index 78%
rename from webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ServletConfigurationSupport.java
rename to spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/ServletConfigurationSupport.java
index 0b2eabdb9..87885bfe0 100644
--- a/webmvc/spring-addons-webmvc-core/src/main/java/com/c4_soft/springaddons/security/oauth2/config/synchronised/ServletConfigurationSupport.java
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/ServletConfigurationSupport.java
@@ -1,4 +1,4 @@
-package com.c4_soft.springaddons.security.oauth2.config.synchronised;
+package com.c4_soft.springaddons.security.oidc.starter.synchronised;
import static org.springframework.security.config.Customizer.withDefaults;
@@ -18,9 +18,14 @@
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.OncePerRequestFilter;
-import com.c4_soft.springaddons.security.oauth2.config.SpringAddonsOAuth2ClientProperties;
-import com.c4_soft.springaddons.security.oauth2.config.SpringAddonsSecurityProperties;
-import com.c4_soft.springaddons.security.oauth2.config.SpringAddonsSecurityProperties.CorsProperties;
+import com.c4_soft.springaddons.security.oidc.starter.properties.CorsProperties;
+import com.c4_soft.springaddons.security.oidc.starter.properties.Csrf;
+import com.c4_soft.springaddons.security.oidc.starter.properties.SpringAddonsOidcClientProperties;
+import com.c4_soft.springaddons.security.oidc.starter.properties.SpringAddonsOidcResourceServerProperties;
+import com.c4_soft.springaddons.security.oidc.starter.synchronised.client.ClientExpressionInterceptUrlRegistryPostProcessor;
+import com.c4_soft.springaddons.security.oidc.starter.synchronised.client.ClientHttpSecurityPostProcessor;
+import com.c4_soft.springaddons.security.oidc.starter.synchronised.resourceserver.ResourceServerExpressionInterceptUrlRegistryPostProcessor;
+import com.c4_soft.springaddons.security.oidc.starter.synchronised.resourceserver.ResourceServerHttpSecurityPostProcessor;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
@@ -32,7 +37,7 @@ public class ServletConfigurationSupport {
public static HttpSecurity configureResourceServer(
HttpSecurity http,
ServerProperties serverProperties,
- SpringAddonsSecurityProperties addonsResourceServerProperties,
+ SpringAddonsOidcResourceServerProperties addonsResourceServerProperties,
ResourceServerExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor,
ResourceServerHttpSecurityPostProcessor httpPostProcessor)
throws Exception {
@@ -41,12 +46,10 @@ public static HttpSecurity configureResourceServer(
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());
- }));
- }
+ 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());
@@ -58,7 +61,7 @@ public static HttpSecurity configureResourceServer(
public static HttpSecurity configureClient(
HttpSecurity http,
ServerProperties serverProperties,
- SpringAddonsOAuth2ClientProperties addonsClientProperties,
+ SpringAddonsOidcClientProperties addonsClientProperties,
ClientExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor,
ClientHttpSecurityPostProcessor httpPostProcessor)
throws Exception {
@@ -92,10 +95,12 @@ public static HttpSecurity configureCors(HttpSecurity http, CorsProperties[] cor
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.setAllowCredentials(corsProps.getAllowCredentials());
configuration.setAllowedHeaders(Arrays.asList(corsProps.getAllowedHeaders()));
+ configuration.setAllowedMethods(Arrays.asList(corsProps.getAllowedMethods()));
+ configuration.setAllowedOriginPatterns(Arrays.asList(corsProps.getAllowedOriginPatterns()));
configuration.setExposedHeaders(Arrays.asList(corsProps.getExposedHeaders()));
+ configuration.setMaxAge(corsProps.getMaxAge());
source.registerCorsConfiguration(corsProps.getPath(), configuration);
}
http.cors(cors -> cors.configurationSource(source));
@@ -103,7 +108,7 @@ public static HttpSecurity configureCors(HttpSecurity http, CorsProperties[] cor
return http;
}
- public static HttpSecurity configureState(HttpSecurity http, boolean isStatless, SpringAddonsSecurityProperties.Csrf csrfEnum) throws Exception {
+ public static HttpSecurity configureState(HttpSecurity http, boolean isStatless, Csrf csrfEnum) throws Exception {
if (isStatless) {
http.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/SpringAddonsOidcBeans.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/SpringAddonsOidcBeans.java
new file mode 100644
index 000000000..bef06e916
--- /dev/null
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/SpringAddonsOidcBeans.java
@@ -0,0 +1,144 @@
+package com.c4_soft.springaddons.security.oidc.starter.synchronised;
+
+import java.time.Instant;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
+import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Conditional;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
+import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
+import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames;
+import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
+import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
+import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionAuthenticatedPrincipal;
+import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenAuthenticationConverter;
+
+import com.c4_soft.springaddons.security.oidc.OpenidClaimSet;
+import com.c4_soft.springaddons.security.oidc.starter.ClaimSetAuthoritiesConverter;
+import com.c4_soft.springaddons.security.oidc.starter.ConfigurableClaimSetAuthoritiesConverter;
+import com.c4_soft.springaddons.security.oidc.starter.properties.SpringAddonsOidcProperties;
+import com.c4_soft.springaddons.security.oidc.starter.properties.condition.bean.DefaultGrantedAuthoritiesMapperCondition;
+import com.c4_soft.springaddons.security.oidc.starter.properties.condition.bean.DefaultJwtAbstractAuthenticationTokenConverterCondition;
+import com.c4_soft.springaddons.security.oidc.starter.properties.condition.bean.DefaultOpaqueTokenAuthenticationConverterCondition;
+import com.c4_soft.springaddons.security.oidc.starter.synchronised.resourceserver.JwtAbstractAuthenticationTokenConverter;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author Jerome Wacongne ch4mp@c4-soft.com
+ */
+@ConditionalOnWebApplication(type = Type.SERVLET)
+@AutoConfiguration
+@ImportAutoConfiguration(SpringAddonsOidcProperties.class)
+@Slf4j
+public class SpringAddonsOidcBeans {
+
+ /**
+ * Retrieves granted authorities from a claims-set (decoded from JWT, introspected or obtained from userinfo end-point)
+ *
+ * @param addonsProperties spring-addons configuration properties
+ * @return
+ */
+ @ConditionalOnMissingBean
+ @Bean
+ ClaimSetAuthoritiesConverter authoritiesConverter(SpringAddonsOidcProperties addonsProperties) {
+ log.debug("Building default ConfigurableClaimSetAuthoritiesConverter with: {}", (Object[]) addonsProperties.getOps());
+ return new ConfigurableClaimSetAuthoritiesConverter(addonsProperties);
+ }
+
+ /**
+ * Converter bean from {@link Jwt} to {@link AbstractAuthenticationToken}
+ *
+ * @param authoritiesConverter converts access-token claims into Spring authorities
+ * @param addonsProperties spring-addons configuration properties
+ * @return a converter from {@link Jwt} to {@link AbstractAuthenticationToken}
+ */
+ @Conditional(DefaultJwtAbstractAuthenticationTokenConverterCondition.class)
+ @Bean
+ JwtAbstractAuthenticationTokenConverter jwtAuthenticationConverter(
+ Converter