diff --git a/README.MD b/README.MD
index 08f6ebcb4..e6e372f95 100644
--- a/README.MD
+++ b/README.MD
@@ -81,242 +81,15 @@ What are the identified risks of using the resources from such a repo and how ca
- an increasing number of user inspect it and open issues or PRs when detecting a problem (the community is probably much bigger than your team working at detecting Spring Security configuration issues in your own projects)
- having code centralised at one place and reused at many places reduces the risk of a careless mistake in one of your app
-## 1. Spring Boot Starter
+## 1. spring-addons-starter-oidc
**This starter is designed to push auto-configuration to the next level** and does nothing more than helping you to configure Spring Security beans using application properties.
`spring-addons-oidc-starter` does not replace `spring-boot-starter-oauth2-resource-server` and `spring-boot-starter-oauth2-client`, it uses application properties to configure a few beans designed to be picked by Spring Boot official "starters". The aim is to reduce Java code and ease application deployment across environments. In most cases, you should need 0 Java conf. An effort was made to make [tutorials](https://github.com/ch4mpy/spring-addons/tree/master/samples/tutorials), Javadoc as informative as possible. Please refer there for more details.
-If you are curious enough, you might inspect what is auto-configured (and under which conditions) by reading the source code, starting from the [org.springframework.boot.autoconfigure.AutoConfiguration.imports](https://github.com/ch4mpy/spring-addons/blob/master/spring-addons-starter-oidc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports) file, which is the Spring Boot standard entry-point defining what is loaded when a jar is on the classpath.
+Please refer to [the module README](https://github.com/ch4mpy/spring-addons/tree/master/spring-addons-starter-oidc) for features, FAQ and usage
You can refer to [module, dependency, class and other diagrams](https://sourcespy.com/github/ch4mpyspringaddons/) for a general overview of the repository.
-### 1.1. Usage
-**If you are not absolutely sure why you need an OAuth2 client (with `oauth2Login` in Spring, but secured with sessions, not access tokens) or an OAuth2 resource server configuration (secured with access tokens, not sessions, but without `oauth2Login`), please read the [OAuth2 essentials section](https://github.com/ch4mpy/spring-addons/tree/master/samples/tutorials#1-oauth2-essentials) of the tutorials.** This might save you a lot of time and effort.
-
-Add `com.c4-soft.springaddons:spring-addons-starter-oidc` to your dependencies, in addition to `org.springframework.boot:spring-boot-starter-oauth2-client` or `org.springframework.boot:spring-boot-starter-oauth2-resource-server`.
-
-If configuring an OAuth2 client (with `oauth2Login`), define the standard Spring Boot `provider` and `registration` properties for OAuth2 clients.
-
-If configuring an OAuth2 resource server with access token introspection, define the standard Spring Boot `opaquetoken` properties.
-
-Then, define the relevant `com.c4-soft.springaddons.oidc` properties for your use case. There are many complete [samples](https://github.com/ch4mpy/spring-addons/tree/master/samples) and [tutorials](https://github.com/ch4mpy/spring-addons/tree/master/samples/tutorials) you should refer to, but here are a few demos for different use-cases and OpenID Providers:
-
-#### 1.1.1. Resource Server with JWT decoder
-For a REST API secured with JWT access tokens, you need:
-```xml
-
- org.springframework.boot
-
- spring-boot-starter-web
-
-
- org.springframework.boot
- spring-boot-starter-oauth2-resource-server
-
-
-
- com.c4-soft.springaddons
- spring-addons-starter-oidc
-
-```
-And
-```yaml
-com:
- c4-soft:
- springaddons:
- oidc:
- ops:
- - iss: https://oidc.c4-soft.com/auth/realms/master
- username-claim: preferred_username
- authorities:
- - path: $.realm_access.roles
- - path: $.resource_access.*.roles
- resourceserver:
- permit-all:
- - "/greet/public"
- cors:
- - path: /**
- allowed-origin-patterns: http://localhost:4200
-```
-Above configuration will create an application without sessions nor CSRF protection, and 401 will be answered to unauthorized requests to protected resources.
-
-#### 1.1.2. Client
-For an app serving Thymeleaf templates with login and logout:
-```xml
-
- org.springframework.boot
-
- spring-boot-starter-web
-
-
- org.springframework.boot
- spring-boot-starter-thymeleaf
-
-
- org.springframework.boot
- spring-boot-starter-oauth2-client
-
-
-
- com.c4-soft.springaddons
- spring-addons-starter-oidc
-
-```
-And
-```yaml
-cognito-issuer: https://cognito-idp.us-west-2.amazonaws.com/us-west-2_RzhmgLwjl
-cognito-client-id: change-me
-cognito-secret: change-me
-
-spring:
- security:
- oauth2:
- client:
- provider:
- cognito:
- issuer-uri: ${cognito-issuer}
- registration:
- cognito-authorization-code:
- authorization-grant-type: authorization_code
- client-id: ${cognito-client-id}
- client-secret: ${cognito-secret}
- provider: cognito
- scope: openid,profile,email,offline_access
-com:
- c4-soft:
- springaddons:
- oidc:
- ops:
- - iss: ${cognito-issuer}
- username-claim: username
- authorities:
- - path: cognito:groups
- client:
- security-matchers:
- - /**
- permit-all:
- - /login/**
- - /oauth2/**
- - /
- # Auth0 and Cognito do not follow strictly the OpenID RP-Initiated Logout spec and need specific configuration
- oauth2-logout:
- cognito-authorization-code:
- uri: https://spring-addons.auth.us-west-2.amazoncognito.com/logout
- client-id-request-param: client_id
- post-logout-uri-request-param: logout_uri
-```
-Above configuration will create an application secured with sessions (not access tokens), with CSRF protection enabled, and unauthorized requests to protected resources will be redirected to login.
-
-#### 1.1.3. Client and Resource Server
-For an app exposing publicly both
-- Thymeleaf templates secured with session (with login and logout), all templates being served with `/ui` prefix (but index which is at `/`)
-- a REST API secured with access token
-```xml
-
- org.springframework.boot
-
- spring-boot-starter-web
-
-
- org.springframework.boot
-
- spring-boot-starter-webflux
-
-
- org.springframework.boot
- spring-boot-starter-thymeleaf
-
-
- org.springframework.boot
- spring-boot-starter-oauth2-client
-
-
- org.springframework.boot
- spring-boot-starter-oauth2-resource-server
-
-
-
- com.c4-soft.springaddons
- spring-addons-starter-oidc
-
-```
-And
-```yaml
-auth0-issuer: https://oidc.c4-soft.com/auth/realms/master
-auth0-client-id: change-me
-auth0-secret: change-me
-
-spring:
- security:
- oauth2:
- client:
- provider:
- auth0:
- issuer-uri: ${auth0-issuer}
- registration:
- auth0-authorization-code:
- authorization-grant-type: authorization_code
- client-id: ${auth0-client-id}
- client-secret: ${auth0-secret}
- provider: auth0
- scope: openid,profile,email,offline_access
-com:
- c4-soft:
- springaddons:
- oidc:
- ops:
- - iss: ${auth0-issuer}
- username-claim: $['https://c4-soft.com/user']['name']
- authorities:
- - path: $['https://c4-soft.com/user']['roles']
- - path: $.permissions
- client:
- security-matchers:
- - /login/**
- - /oauth2/**
- - /logout
- - /
- - /ui/**
- permit-all:
- - /login/**
- - /oauth2/**
- - /
- # Auth0 and Cognito do not follow strictly the OpenID RP-Initiated Logout spec and need specific configuration
- oauth2-logout:
- auth0-authorization-code:
- uri: ${auth0-issuer}v2/logout
- client-id-request-param: client_id
- post-logout-uri-request-param: returnTo
- # Auth0 requires an "audience" parameter in authorization-code request to deliver JWTs
- authorization-request-params:
- auth0-authorization-code:
- - name: audience
- value: demo.c4-soft.com
- resourceserver:
- permit-all:
- - "/greet/public"
-```
-With the above configuration, two distinct security filter-chains will be defined:
-- a client one with sessions (and CSRF protection enabled), intercepting all requests to UI templates as well as those involved in login and logout, and redirecting to login unauthorized requests to protected templates.
-- a resource server one acting as default (with lowest precedence to process all requests that were not matched with client filter-chain `securityMatchers`), without sessions (requests are secured with JWT access tokens) nor CSRF protections, and returning 401 to unauthorized requests to protected resources.
-
-### 1.2. Customizing Auto-Configuration
-First use your IDE auto-completion to check if there isn't an existing application property covering your needs: a lot is configurable from properties, and all properties are documented.
-
-You can override about any `@Bean` defined by spring-addons (almost all are `@ConditionalOnMissingBean`). Here are a few handy ones:
-- `(Reactive)JwtAbstractAuthenticationTokenConverter`: take control on the `Authentication` instance built after a JWT was successfully decoded and validated
-- `(Reactive)OpaqueTokenAuthenticationConverter`: take control on the `Authentication` instance built after an access token was successfully introspected
-- `ClaimSetAuthoritiesConverter`: opt-out the `ConfigurableClaimSetAuthoritiesConverter`, responsible for authorities mapping
-- `GrantedAuthoritiesMapper`: in OAuth2 clients, opt-out the default `GrantedAuthoritiesMapper` (which delegates authorities mapping to the `ConfigurableClaimSetAuthoritiesConverter` just above)
-- `(Reactive)AuthenticationManagerResolver`: opt-out the authentication manager implementing static multi-tenancy for resource servers with JWT decoders
-- `ResourceServerAuthorizeExchangeSpecPostProcessor`, `ClientAuthorizeExchangeSpecPostProcessor`, `ClientAuthorizeExchangeSpecPostProcessor` or `ResourceServerAuthorizeExchangeSpecPostProcessor`: fine-grained access control from configuration (an alternative is using `@Enable(Reactive)MethodSecurity` and `@PreAuthorize` on controller methods)
-- `ResourceServerHttpSecurityPostProcessor` or `ClientHttpSecurityPostProcessor`: post-process spring-addons auto-configured `SecurityFilterChains` (this enables to change absolutely anything from it).
-
-### 1.3. Disabling `spring-addons-oidc-starter`
-The easiest way is to exclude it from the classpath, but you may also turn the auto-configuration off by:
-- setting `com.c4-soft.springaddons.oidc.resourceserver.enabled` to `false` (this disables the resource server `SecurityFilterChain` bean instantiation, as well as all of its default dependencies)
-- leaving `com.c4-soft.springaddons.oidc.client.securityMatcher` empty (this disables the client `SecurityFilterChain` bean instantiation, as well as all of its default dependencies)
-
## 2. Unit & Integration Testing With Security
Testing method security (`@PreAuthorize`, `@PostFilter`, etc.) requires to configure the security context. `Spring-security-test` provides with `MockMvc` request post-processors and `WebTestClient` mutators to do so, but this requires the context of a request, which limits its usage to testing secured controllers.
@@ -482,7 +255,7 @@ These starters are designed to push auto-configuration one step further. In most
I could forget to update README before releasing, so please refer to [maven central](https://repo1.maven.org/maven2/com/c4-soft/springaddons/spring-addons/) to pick latest available release
```xml
- 7.4.1
+ 7.5.0
@@ -518,6 +291,12 @@ I could forget to update README before releasing, so please refer to [maven cent
### 5.1. `7.x` Branch
+### `7.5.0`
+- Create [spring-addons-starter-oidc README](https://github.com/ch4mpy/spring-addons/tree/master/spring-addons-starter-oidc)
+- Replace `AuthoritiesMappingPropertiesResolver` with `OpenidProviderPropertiesResolver`
+- `OpenidProviderPropertiesResolver` makes multi-tenancy much simpler to implement, including in "dynamic" scenarios (see [spring-addons-starter-oidc README](https://github.com/ch4mpy/spring-addons/tree/master/spring-addons-starter-oidc#1-1-4))
+- Fix names of `(Server)HttpSecurityPostProcessor` (synchronised impl where prefixed with `Server` which it shouldn't and reactive weren't when it should)
+
### `7.4.1`
- [gh-183](https://github.com/ch4mpy/spring-addons/issues/183) Allow anonymous CORS preflight requests (`OPTIONS` requests to a path configured with CORS)
- [gh-184](https://github.com/ch4mpy/spring-addons/issues/184) Configuration properties to add parameters to token requests (necessary for instance to add an `audience` when using client-credentials with Auth0)
diff --git a/samples/tutorials/resource-server_multitenant_dynamic/src/main/java/com/c4soft/springaddons/tutorials/WebSecurityConfig.java b/samples/tutorials/resource-server_multitenant_dynamic/src/main/java/com/c4soft/springaddons/tutorials/WebSecurityConfig.java
index d8edb224f..b4aa0dd84 100644
--- a/samples/tutorials/resource-server_multitenant_dynamic/src/main/java/com/c4soft/springaddons/tutorials/WebSecurityConfig.java
+++ b/samples/tutorials/resource-server_multitenant_dynamic/src/main/java/com/c4soft/springaddons/tutorials/WebSecurityConfig.java
@@ -1,128 +1,44 @@
package com.c4soft.springaddons.tutorials;
import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Collection;
import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.stream.Collectors;
+import java.util.Optional;
-import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Primary;
-import org.springframework.core.convert.converter.Converter;
-import org.springframework.http.HttpStatus;
-import org.springframework.security.authentication.AbstractAuthenticationToken;
-import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.authentication.AuthenticationManagerResolver;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimNames;
-import org.springframework.security.oauth2.jwt.JwtDecoders;
-import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
-import org.springframework.security.oauth2.server.resource.authentication.JwtIssuerAuthenticationManagerResolver;
import org.springframework.stereotype.Component;
-import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.util.StringUtils;
-import com.c4_soft.springaddons.security.oidc.OAuthentication;
-import com.c4_soft.springaddons.security.oidc.OpenidClaimSet;
-import com.c4_soft.springaddons.security.oidc.starter.properties.MissingAuthorizationServerConfigurationException;
+import com.c4_soft.springaddons.security.oidc.starter.OpenidProviderPropertiesResolver;
import com.c4_soft.springaddons.security.oidc.starter.properties.OpenidProviderProperties;
import com.c4_soft.springaddons.security.oidc.starter.properties.SpringAddonsOidcProperties;
-import com.c4_soft.springaddons.security.oidc.starter.synchronised.resourceserver.JwtAbstractAuthenticationTokenConverter;
-
-import jakarta.servlet.http.HttpServletRequest;
@Configuration
@EnableMethodSecurity
public class WebSecurityConfig {
- @Bean
- JwtAbstractAuthenticationTokenConverter authenticationConverter(
- Converter
*
- * It is designed to work with {@link SpringAddonsOidcProperties} which enable to configure:
+ * It relies on {@link OpenidProviderPropertiesResolver} to resolve the configuration properties for the provided claims (and throws if it is not resolved).
+ * This properties enable to configure:
*
*
* - source claims (which claims to pick authorities from, dot.separated.path is supported)
@@ -33,17 +34,13 @@
*/
@RequiredArgsConstructor
public class ConfigurableClaimSetAuthoritiesConverter implements ClaimSetAuthoritiesConverter {
- private final AuthoritiesMappingPropertiesResolver authoritiesMappingPropertiesProvider;
-
- public ConfigurableClaimSetAuthoritiesConverter(SpringAddonsOidcProperties properties) {
- this.authoritiesMappingPropertiesProvider = new ByIssuerAuthoritiesMappingPropertiesResolver(properties);
- }
+ private final OpenidProviderPropertiesResolver opPropertiesResolver;
@Override
public Collection extends GrantedAuthority> convert(Map source) {
- final var authoritiesMappingProperties = authoritiesMappingPropertiesProvider.resolve(source);
+ final var opProperties = opPropertiesResolver.resolve(source).orElseThrow(() -> new NotAConfiguredOpenidProviderException(source));
// @formatter:off
- return authoritiesMappingProperties.stream()
+ return opProperties.getAuthorities().stream()
.flatMap(authoritiesMappingProps -> getAuthorities(source, authoritiesMappingProps))
.map(r -> (GrantedAuthority) new SimpleGrantedAuthority(r)).toList();
// @formatter:on
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/OpenidProviderPropertiesResolver.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/OpenidProviderPropertiesResolver.java
new file mode 100644
index 000000000..b52db57c0
--- /dev/null
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/OpenidProviderPropertiesResolver.java
@@ -0,0 +1,14 @@
+package com.c4_soft.springaddons.security.oidc.starter;
+
+import java.util.Map;
+import java.util.Optional;
+
+import com.c4_soft.springaddons.security.oidc.starter.properties.OpenidProviderProperties;
+
+/**
+ * Resolves OpenID Provider configuration properties from OAuth2 / OpenID claims (decoded from a JWT, introspected from an opaque token or
+ * retrieved from userinfo endpoint)
+ */
+public interface OpenidProviderPropertiesResolver {
+ Optional resolve(Map claimSet);
+}
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/properties/MissingAuthorizationServerConfigurationException.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/properties/MissingAuthorizationServerConfigurationException.java
deleted file mode 100644
index f50f1b30b..000000000
--- a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/properties/MissingAuthorizationServerConfigurationException.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.c4_soft.springaddons.security.oidc.starter.properties;
-
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-@ResponseStatus(HttpStatus.UNAUTHORIZED)
-public class MissingAuthorizationServerConfigurationException extends RuntimeException {
- private static final long serialVersionUID = 5189849969622154264L;
-
- public MissingAuthorizationServerConfigurationException(String jwtIssuer) {
- super("Check application properties: %s is not a trusted issuer".formatted(jwtIssuer));
- }
-
-}
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/properties/NotAConfiguredOpenidProviderException.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/properties/NotAConfiguredOpenidProviderException.java
new file mode 100644
index 000000000..9bff9a525
--- /dev/null
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/properties/NotAConfiguredOpenidProviderException.java
@@ -0,0 +1,18 @@
+package com.c4_soft.springaddons.security.oidc.starter.properties;
+
+import java.util.Map;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ResponseStatus(HttpStatus.UNAUTHORIZED)
+public class NotAConfiguredOpenidProviderException extends RuntimeException {
+ private static final long serialVersionUID = 5189849969622154264L;
+
+ public NotAConfiguredOpenidProviderException(Map claims) {
+ super(
+ "Could not resolve OpenID Provider configuration properties from a JWT with %s as issuer and %s as audience"
+ .formatted(claims.get("iss"), claims.get("aud")));
+ }
+
+}
\ No newline at end of file
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/properties/SpringAddonsOidcProperties.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/properties/SpringAddonsOidcProperties.java
index 3c84b734b..5469585be 100644
--- a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/properties/SpringAddonsOidcProperties.java
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/properties/SpringAddonsOidcProperties.java
@@ -1,9 +1,6 @@
package com.c4_soft.springaddons.security.oidc.starter.properties;
-import java.net.URI;
import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -53,31 +50,4 @@ public class SpringAddonsOidcProperties {
*/
private SpringAddonsOidcResourceServerProperties resourceserver = new SpringAddonsOidcResourceServerProperties();
- /**
- * @param iss the issuer URI string
- * @return configuration properties associated with the provided issuer URI
- * @throws MissingAuthorizationServerConfigurationException if configuration properties don not have an entry for the exact issuer (even trailing slash is
- * important)
- */
- public OpenidProviderProperties getOpProperties(String iss) throws MissingAuthorizationServerConfigurationException {
- return ops
- .stream()
- .filter(issuerProps -> Objects.equals(Optional.ofNullable(issuerProps.getIss()).map(URI::toString).orElse(null), iss))
- .findAny()
- .orElseThrow(() -> new MissingAuthorizationServerConfigurationException(iss));
- }
-
- /**
- * @param iss the issuer URL
- * @return configuration properties associated with the provided issuer URI
- * @throws MissingAuthorizationServerConfigurationException if configuration properties don not have an entry for the exact issuer (even trailing slash is
- * important)
- */
- public OpenidProviderProperties getOpProperties(Object iss) throws MissingAuthorizationServerConfigurationException {
- if (iss == null && ops.size() == 1) {
- return ops.get(0);
- }
- return getOpProperties(Optional.ofNullable(iss).map(Object::toString).orElse(null));
- }
-
}
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/ReactiveConfigurationSupport.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/ReactiveConfigurationSupport.java
index 2ac1b3a77..59dddbebb 100644
--- a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/ReactiveConfigurationSupport.java
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/ReactiveConfigurationSupport.java
@@ -25,9 +25,9 @@
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.reactive.client.ClientAuthorizeExchangeSpecPostProcessor;
-import com.c4_soft.springaddons.security.oidc.starter.reactive.client.ClientHttpSecurityPostProcessor;
+import com.c4_soft.springaddons.security.oidc.starter.reactive.client.ClientServerHttpSecurityPostProcessor;
import com.c4_soft.springaddons.security.oidc.starter.reactive.resourceserver.ResourceServerAuthorizeExchangeSpecPostProcessor;
-import com.c4_soft.springaddons.security.oidc.starter.reactive.resourceserver.ResourceServerHttpSecurityPostProcessor;
+import com.c4_soft.springaddons.security.oidc.starter.reactive.resourceserver.ResourceServerServerHttpSecurityPostProcessor;
import reactor.core.publisher.Mono;
@@ -40,7 +40,7 @@ public static ServerHttpSecurity configureResourceServer(
ServerAuthenticationEntryPoint authenticationEntryPoint,
Optional accessDeniedHandler,
ResourceServerAuthorizeExchangeSpecPostProcessor authorizePostProcessor,
- ResourceServerHttpSecurityPostProcessor httpPostProcessor) {
+ ResourceServerServerHttpSecurityPostProcessor httpPostProcessor) {
ReactiveConfigurationSupport.configureCors(http, addonsResourceServerProperties.getCors());
ReactiveConfigurationSupport.configureState(http, addonsResourceServerProperties.isStatlessSessions(), addonsResourceServerProperties.getCsrf());
@@ -66,7 +66,7 @@ public static ServerHttpSecurity configureClient(
ServerProperties serverProperties,
SpringAddonsOidcClientProperties addonsClientProperties,
ClientAuthorizeExchangeSpecPostProcessor authorizePostProcessor,
- ClientHttpSecurityPostProcessor httpPostProcessor) {
+ ClientServerHttpSecurityPostProcessor httpPostProcessor) {
ReactiveConfigurationSupport.configureCors(http, addonsClientProperties.getCors());
ReactiveConfigurationSupport.configureState(http, false, addonsClientProperties.getCsrf());
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/ReactiveSpringAddonsOidcBeans.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/ReactiveSpringAddonsOidcBeans.java
index bd7c95397..1b8152f96 100644
--- a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/ReactiveSpringAddonsOidcBeans.java
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/ReactiveSpringAddonsOidcBeans.java
@@ -29,8 +29,11 @@
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenAuthenticationConverter;
import com.c4_soft.springaddons.security.oidc.OpenidClaimSet;
+import com.c4_soft.springaddons.security.oidc.starter.ByIssuerOpenidProviderPropertiesResolver;
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.OpenidProviderPropertiesResolver;
+import com.c4_soft.springaddons.security.oidc.starter.properties.NotAConfiguredOpenidProviderException;
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;
@@ -50,6 +53,12 @@
@Slf4j
public class ReactiveSpringAddonsOidcBeans {
+ @ConditionalOnMissingBean
+ OpenidProviderPropertiesResolver authoritiesMappingPropertiesProvider(SpringAddonsOidcProperties addonsProperties) {
+ log.debug("Building default AuthoritiesMappingPropertiesResolver with: {}", addonsProperties.getOps());
+ return new ByIssuerOpenidProviderPropertiesResolver(addonsProperties);
+ }
+
/**
* Retrieves granted authorities from the Jwt (from its private claims or with the help of an external service)
*
@@ -58,9 +67,8 @@ public class ReactiveSpringAddonsOidcBeans {
*/
@ConditionalOnMissingBean
@Bean
- ClaimSetAuthoritiesConverter authoritiesConverter(SpringAddonsOidcProperties addonsProperties) {
- log.debug("Building default CorsConfigurationSource with: {}", addonsProperties);
- return new ConfigurableClaimSetAuthoritiesConverter(addonsProperties);
+ ClaimSetAuthoritiesConverter authoritiesConverter(OpenidProviderPropertiesResolver authoritiesMappingPropertiesProvider) {
+ return new ConfigurableClaimSetAuthoritiesConverter(authoritiesMappingPropertiesProvider);
}
/**
@@ -74,13 +82,18 @@ ClaimSetAuthoritiesConverter authoritiesConverter(SpringAddonsOidcProperties add
@Bean
ReactiveJwtAbstractAuthenticationTokenConverter jwtAuthenticationConverter(
Converter, Collection extends GrantedAuthority>> authoritiesConverter,
- SpringAddonsOidcProperties addonsProperties) {
+ OpenidProviderPropertiesResolver opPropertiesResolver) {
return jwt -> Mono
.just(
new JwtAuthenticationToken(
jwt,
authoritiesConverter.convert(jwt.getClaims()),
- new OpenidClaimSet(jwt.getClaims(), addonsProperties.getOpProperties(jwt.getIssuer()).getUsernameClaim()).getName()));
+ new OpenidClaimSet(
+ jwt.getClaims(),
+ opPropertiesResolver
+ .resolve(jwt.getClaims())
+ .orElseThrow(() -> new NotAConfiguredOpenidProviderException(jwt.getClaims()))
+ .getUsernameClaim()).getName()));
}
/**
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/HttpSecurityPostProcessor.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/ServerHttpSecurityPostProcessor.java
similarity index 89%
rename from spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/HttpSecurityPostProcessor.java
rename to spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/ServerHttpSecurityPostProcessor.java
index f25485c1c..2e6ab6a47 100644
--- a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/HttpSecurityPostProcessor.java
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/ServerHttpSecurityPostProcessor.java
@@ -8,6 +8,6 @@
*
* @author ch4mp
*/
-public interface HttpSecurityPostProcessor {
+public interface ServerHttpSecurityPostProcessor {
ServerHttpSecurity process(ServerHttpSecurity serverHttpSecurity);
}
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/client/ClientHttpSecurityPostProcessor.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/client/ClientHttpSecurityPostProcessor.java
deleted file mode 100644
index 682ce5ca6..000000000
--- a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/client/ClientHttpSecurityPostProcessor.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package com.c4_soft.springaddons.security.oidc.starter.reactive.client;
-
-import com.c4_soft.springaddons.security.oidc.starter.reactive.HttpSecurityPostProcessor;
-
-public interface ClientHttpSecurityPostProcessor extends HttpSecurityPostProcessor {
-}
\ No newline at end of file
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/client/ClientServerHttpSecurityPostProcessor.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/client/ClientServerHttpSecurityPostProcessor.java
new file mode 100644
index 000000000..310a4e05b
--- /dev/null
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/client/ClientServerHttpSecurityPostProcessor.java
@@ -0,0 +1,6 @@
+package com.c4_soft.springaddons.security.oidc.starter.reactive.client;
+
+import com.c4_soft.springaddons.security.oidc.starter.reactive.ServerHttpSecurityPostProcessor;
+
+public interface ClientServerHttpSecurityPostProcessor extends ServerHttpSecurityPostProcessor {
+}
\ No newline at end of file
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/client/ReactiveSpringAddonsOidcClientWithLoginBeans.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/client/ReactiveSpringAddonsOidcClientWithLoginBeans.java
index 3899788f7..7726b5862 100644
--- a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/client/ReactiveSpringAddonsOidcClientWithLoginBeans.java
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/client/ReactiveSpringAddonsOidcClientWithLoginBeans.java
@@ -62,7 +62,7 @@
* - clientAuthorizePostProcessor: a {@link ClientAuthorizeExchangeSpecPostProcessor} 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
+ *
- clientHttpPostProcessor: a {@link ClientServerHttpSecurityPostProcessor} to override anything from above auto-configuration. It is called
* just before the security filter-chain is returned. Default is a no-op.
* - authorizationRequestResolver: a {@link ServerOAuth2AuthorizationRequestResolver} to add custom parameters (from application
* properties) to authorization code request
@@ -91,7 +91,7 @@ public class ReactiveSpringAddonsOidcClientWithLoginBeans {
* - 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 ClientAuthorizeExchangeSpecPostProcessor} for access control configuration from Java conf and
- * {@link ClientHttpSecurityPostProcessor} to override anything from the auto-configuration listed above
+ * {@link ClientServerHttpSecurityPostProcessor} to override anything from the auto-configuration listed above
*
*
* @param http the security filter-chain builder to configure
@@ -128,7 +128,7 @@ SecurityWebFilterChain clientFilterChain(
Optional authenticationFailureHandler,
ServerLogoutSuccessHandler logoutSuccessHandler,
ClientAuthorizeExchangeSpecPostProcessor authorizePostProcessor,
- ClientHttpSecurityPostProcessor httpPostProcessor,
+ ClientServerHttpSecurityPostProcessor httpPostProcessor,
Optional logoutHandler,
Customizer oidcLogoutCustomizer)
throws Exception {
@@ -226,7 +226,7 @@ ClientAuthorizeExchangeSpecPostProcessor clientAuthorizePostProcessor() {
*/
@ConditionalOnMissingBean
@Bean
- ClientHttpSecurityPostProcessor clientHttpPostProcessor() {
+ ClientServerHttpSecurityPostProcessor clientHttpPostProcessor() {
return serverHttpSecurity -> serverHttpSecurity;
}
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/DefaultSpringAddonsReactiveJwtDecoderFactory.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/DefaultSpringAddonsReactiveJwtDecoderFactory.java
new file mode 100644
index 000000000..4b37332e2
--- /dev/null
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/DefaultSpringAddonsReactiveJwtDecoderFactory.java
@@ -0,0 +1,72 @@
+package com.c4_soft.springaddons.security.oidc.starter.reactive.resourceserver;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Optional;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
+import org.springframework.security.oauth2.core.OAuth2TokenValidator;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.JwtClaimNames;
+import org.springframework.security.oauth2.jwt.JwtClaimValidator;
+import org.springframework.security.oauth2.jwt.JwtValidators;
+import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
+import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+import com.c4_soft.springaddons.security.oidc.starter.OpenidProviderPropertiesResolver;
+
+import lombok.RequiredArgsConstructor;
+
+/**
+ *
+ * Provides with a JwtDecoder (configured with the required validators). Both JWK-set and issuer URIs are optional, but at least one must be provided.
+ *
+ *
+ * Uses {@link OpenidProviderPropertiesResolver} to resolve the matching OpenID Provider configuration properties and throws an exception if none are found (the
+ * token issuer is not trusted).
+ *
+ */
+@RequiredArgsConstructor
+public class DefaultSpringAddonsReactiveJwtDecoderFactory implements SpringAddonsReactiveJwtDecoderFactory {
+
+ @Override
+ public ReactiveJwtDecoder create(Optional jwkSetUri, Optional issuer, Optional audience) {
+
+ final var decoder = jwkSetUri.isPresent()
+ ? NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri.get().toString()).build()
+ : NimbusReactiveJwtDecoder
+ .withIssuerLocation(issuer.orElseThrow(() -> new InvalidReactiveJwtDecoderCreationParametersException()).toString())
+ .build();
+
+ final OAuth2TokenValidator defaultValidator = issuer
+ .map(URI::toString)
+ .map(JwtValidators::createDefaultWithIssuer)
+ .orElse(JwtValidators.createDefault());
+
+ // @formatter:off
+ final OAuth2TokenValidator jwtValidator = audience
+ .filter(StringUtils::hasText)
+ .map(opAudience -> new JwtClaimValidator>(
+ JwtClaimNames.AUD,
+ (aud) -> aud != null && aud.contains(opAudience)))
+ .map(audValidator -> (OAuth2TokenValidator) new DelegatingOAuth2TokenValidator<>(List.of(defaultValidator, audValidator)))
+ .orElse(defaultValidator);
+ // @formatter:on
+
+ decoder.setJwtValidator(jwtValidator);
+
+ return decoder;
+ }
+
+ @ResponseStatus(HttpStatus.UNAUTHORIZED)
+ static class InvalidReactiveJwtDecoderCreationParametersException extends RuntimeException {
+ private static final long serialVersionUID = 3575615882241560832L;
+
+ public InvalidReactiveJwtDecoderCreationParametersException() {
+ super("At least one of jwkSetUri or issuer must be provided");
+ }
+ }
+}
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/ReactiveIssuerStartsWithAuthenticationManagerResolver.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/ReactiveIssuerStartsWithAuthenticationManagerResolver.java
deleted file mode 100644
index 72c28fc28..000000000
--- a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/ReactiveIssuerStartsWithAuthenticationManagerResolver.java
+++ /dev/null
@@ -1,64 +0,0 @@
-package com.c4_soft.springaddons.security.oidc.starter.reactive.resourceserver;
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.springframework.core.convert.converter.Converter;
-import org.springframework.http.HttpStatus;
-import org.springframework.security.authentication.AbstractAuthenticationToken;
-import org.springframework.security.authentication.ReactiveAuthenticationManager;
-import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
-import org.springframework.security.oauth2.jwt.Jwt;
-import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
-import org.springframework.security.oauth2.server.resource.authentication.JwtReactiveAuthenticationManager;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-import reactor.core.publisher.Mono;
-
-/**
- * Dynamic multi-tenancy based on issuer prefix (for instance, trust all reams from a given Keycloak Server)
- *
- * @author Jérôme Wacongne <ch4mp#64;c4-soft.com>
- */
-public class ReactiveIssuerStartsWithAuthenticationManagerResolver implements ReactiveAuthenticationManagerResolver {
-
- private final String issuerPrefix;
- private final Converter> authenticationConverter;
- private final Map jwtManagers = new ConcurrentHashMap<>();
-
- /**
- * @param issuerPrefix what access tokens iss claim must start with
- * @param authenticationConverter converter from a valid {@link Jwt} to an {@link AbstractAuthenticationToken} instance
- */
- public ReactiveIssuerStartsWithAuthenticationManagerResolver(
- String issuerPrefix,
- Converter> authenticationConverter) {
- super();
- this.issuerPrefix = issuerPrefix.toString();
- this.authenticationConverter = authenticationConverter;
- }
-
- @Override
- public Mono resolve(String issuer) {
- if (!jwtManagers.containsKey(issuer)) {
- if (!issuer.startsWith(issuerPrefix)) {
- throw new UnknownIssuerException(issuer);
- }
- final var decoder = NimbusReactiveJwtDecoder.withIssuerLocation(issuer).build();
- var provider = new JwtReactiveAuthenticationManager(decoder);
- provider.setJwtAuthenticationConverter(authenticationConverter);
- jwtManagers.put(issuer, provider::authenticate);
- }
- return Mono.just(jwtManagers.get(issuer));
-
- }
-
- @ResponseStatus(HttpStatus.UNAUTHORIZED)
- static class UnknownIssuerException extends RuntimeException {
- private static final long serialVersionUID = 4177339081914400888L;
-
- public UnknownIssuerException(String issuer) {
- super("Unknown issuer: %s".formatted(issuer));
- }
- }
-}
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/ReactiveJWTClaimsSetAuthenticationManager.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/ReactiveJWTClaimsSetAuthenticationManager.java
new file mode 100644
index 000000000..fa131027e
--- /dev/null
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/ReactiveJWTClaimsSetAuthenticationManager.java
@@ -0,0 +1,116 @@
+package com.c4_soft.springaddons.security.oidc.starter.reactive.resourceserver;
+
+import java.net.URI;
+import java.text.ParseException;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.ReactiveAuthenticationManager;
+import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
+import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken;
+import org.springframework.security.oauth2.server.resource.authentication.JwtReactiveAuthenticationManager;
+import org.springframework.util.Assert;
+
+import com.c4_soft.springaddons.security.oidc.starter.OpenidProviderPropertiesResolver;
+import com.c4_soft.springaddons.security.oidc.starter.properties.NotAConfiguredOpenidProviderException;
+import com.c4_soft.springaddons.security.oidc.starter.synchronised.resourceserver.JWTClaimsSetAuthenticationManager.JWTClaimsSetAuthenticationManagerResolver;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.JWTParser;
+
+import lombok.RequiredArgsConstructor;
+import reactor.core.publisher.Mono;
+
+/**
+ *
+ * An {@link AuthenticationManager} relying on {@link JWTClaimsSetAuthenticationManagerResolver}, itself using {@link SpringAddonsReactiveJwtDecoderFactory} and
+ * a {@link Converter Converter<Jwt, Mono<? extends AbstractAuthenticationToken>>}.
+ *
+ *
+ * {@link DefaultSpringAddonsReactiveJwtDecoderFactory}, the default {@link SpringAddonsReactiveJwtDecoderFactory} throws an exception if the OpenID Provider
+ * configuration properties could not be resolved from the JWT claims.
+ *
+ *
+ * @author Jérôme Wacongne <ch4mp#64;c4-soft.com>
+ */
+public class ReactiveJWTClaimsSetAuthenticationManager implements ReactiveAuthenticationManager {
+
+ private final ReactiveJWTClaimsSetAuthenticationManagerResolver jwtAuthenticationManagerResolver;
+
+ public ReactiveJWTClaimsSetAuthenticationManager(
+ OpenidProviderPropertiesResolver opPropertiesResolver,
+ SpringAddonsReactiveJwtDecoderFactory jwtDecoderFactory,
+ Converter> jwtAuthenticationConverter) {
+ this.jwtAuthenticationManagerResolver = new ReactiveJWTClaimsSetAuthenticationManagerResolver(
+ opPropertiesResolver,
+ jwtDecoderFactory,
+ jwtAuthenticationConverter);
+ }
+
+ @Override
+ public Mono authenticate(Authentication authentication) throws AuthenticationException {
+ Assert.isTrue(authentication instanceof BearerTokenAuthenticationToken, "Authentication must be of type BearerTokenAuthenticationToken");
+ JWTClaimsSet jwtClaimSet;
+ try {
+ jwtClaimSet = JWTParser.parse(((BearerTokenAuthenticationToken) authentication).getToken()).getJWTClaimsSet();
+ } catch (ParseException e) {
+ throw new InvalidBearerTokenException("Could not retrieve JWT claim-set");
+ }
+ return this.jwtAuthenticationManagerResolver.resolve(jwtClaimSet).flatMap(authenticationManager -> {
+ if (authenticationManager == null) {
+ throw new InvalidBearerTokenException("Could not resolve the Authentication manager for the provided JWT");
+ }
+ return authenticationManager.authenticate(authentication);
+ });
+ }
+
+ /**
+ *
+ * An {@link ReactiveAuthenticationManagerResolver} for resource servers using JWT decoder(s). It relies on a {@link SpringAddonsReactiveJwtDecoderFactory}
+ * and a {@link Converter Converter<Jwt, Mono<? extends AbstractAuthenticationToken>>}
+ *
+ *
+ * {@link DefaultSpringAddonsReactiveJwtDecoderFactory}, the default {@link SpringAddonsReactiveJwtDecoderFactory} throws an exception if the OpenID
+ * Provider configuration properties could not be resolved from the JWT claims.
+ *
+ *
+ * @author Jérôme Wacongne <ch4mp#64;c4-soft.com>
+ */
+ @RequiredArgsConstructor
+ public static class ReactiveJWTClaimsSetAuthenticationManagerResolver implements ReactiveAuthenticationManagerResolver {
+
+ private final OpenidProviderPropertiesResolver opPropertiesResolver;
+ private final SpringAddonsReactiveJwtDecoderFactory jwtDecoderFactory;
+ private final Converter> jwtAuthenticationConverter;
+ private final Map jwtManagers = new ConcurrentHashMap<>();
+
+ @Override
+ public Mono resolve(JWTClaimsSet jwt) {
+ final var issuer = jwt.getIssuer();
+ if (!jwtManagers.containsKey(issuer)) {
+ final var opProperties = opPropertiesResolver
+ .resolve(jwt.getClaims())
+ .orElseThrow(() -> new NotAConfiguredOpenidProviderException(jwt.getClaims()));
+
+ final var decoder = jwtDecoderFactory
+ .create(
+ Optional.ofNullable(opProperties.getJwkSetUri()),
+ Optional.of(URI.create(jwt.getIssuer().toString())),
+ Optional.of(opProperties.getAud()));
+
+ var provider = new JwtReactiveAuthenticationManager(decoder);
+ provider.setJwtAuthenticationConverter(jwtAuthenticationConverter);
+ jwtManagers.put(issuer, provider::authenticate);
+ }
+ return Mono.just(jwtManagers.get(issuer));
+ }
+ }
+
+}
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/ReactiveSpringAddonsOidcResourceServerBeans.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/ReactiveSpringAddonsOidcResourceServerBeans.java
index 94b60f438..77861db40 100644
--- a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/ReactiveSpringAddonsOidcResourceServerBeans.java
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/ReactiveSpringAddonsOidcResourceServerBeans.java
@@ -1,17 +1,11 @@
package com.c4_soft.springaddons.security.oidc.starter.reactive.resourceserver;
-import java.net.URI;
import java.nio.charset.Charset;
-import java.util.List;
-import java.util.Map;
import java.util.Optional;
-import java.util.stream.Collectors;
-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.security.oauth2.resource.OAuth2ResourceServerProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
@@ -22,21 +16,12 @@
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AbstractAuthenticationToken;
-import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
-import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
-import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.jwt.Jwt;
-import org.springframework.security.oauth2.jwt.JwtClaimNames;
-import org.springframework.security.oauth2.jwt.JwtClaimValidator;
-import org.springframework.security.oauth2.jwt.JwtValidators;
-import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
-import org.springframework.security.oauth2.server.resource.authentication.JwtIssuerReactiveAuthenticationManagerResolver;
-import org.springframework.security.oauth2.server.resource.authentication.JwtReactiveAuthenticationManager;
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector;
import org.springframework.security.web.AuthenticationEntryPoint;
@@ -45,10 +30,10 @@
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
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.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
+import com.c4_soft.springaddons.security.oidc.starter.OpenidProviderPropertiesResolver;
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;
@@ -59,7 +44,6 @@
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;
/**
@@ -91,7 +75,6 @@
@EnableWebFluxSecurity
@AutoConfiguration
@ImportAutoConfiguration(ReactiveSpringAddonsOidcBeans.class)
-@Slf4j
public class ReactiveSpringAddonsOidcResourceServerBeans {
/**
@@ -101,7 +84,7 @@ public class ReactiveSpringAddonsOidcResourceServerBeans {
*
*
* 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
+ * {@link ResourceServerServerHttpSecurityPostProcessor} bean to override anything from this bean
*
* .
*
@@ -123,7 +106,7 @@ SecurityWebFilterChain springAddonsJwtResourceServerSecurityFilterChain(
ServerProperties serverProperties,
SpringAddonsOidcProperties addonsProperties,
ResourceServerAuthorizeExchangeSpecPostProcessor authorizePostProcessor,
- ResourceServerHttpSecurityPostProcessor httpPostProcessor,
+ ResourceServerServerHttpSecurityPostProcessor httpPostProcessor,
ReactiveAuthenticationManagerResolver authenticationManagerResolver,
ServerAuthenticationEntryPoint authenticationEntryPoint,
Optional accessDeniedHandler) {
@@ -149,7 +132,7 @@ SecurityWebFilterChain springAddonsJwtResourceServerSecurityFilterChain(
*
*
* 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
+ * {@link ResourceServerServerHttpSecurityPostProcessor} bean to override anything from this bean
*
* .
*
@@ -172,7 +155,7 @@ SecurityWebFilterChain springAddonsIntrospectingResourceServerSecurityFilterChai
ServerProperties serverProperties,
SpringAddonsOidcProperties addonsProperties,
ResourceServerAuthorizeExchangeSpecPostProcessor authorizePostProcessor,
- ResourceServerHttpSecurityPostProcessor httpPostProcessor,
+ ResourceServerServerHttpSecurityPostProcessor httpPostProcessor,
ReactiveOpaqueTokenAuthenticationConverter introspectionAuthenticationConverter,
ReactiveOpaqueTokenIntrospector opaqueTokenIntrospector,
ServerAuthenticationEntryPoint authenticationEntryPoint,
@@ -214,7 +197,7 @@ ResourceServerAuthorizeExchangeSpecPostProcessor authorizePostProcessor() {
*/
@ConditionalOnMissingBean
@Bean
- ResourceServerHttpSecurityPostProcessor httpPostProcessor() {
+ ResourceServerServerHttpSecurityPostProcessor httpPostProcessor() {
return serverHttpSecurity -> serverHttpSecurity;
}
@@ -229,55 +212,10 @@ ResourceServerHttpSecurityPostProcessor httpPostProcessor() {
@Conditional(DefaultAuthenticationManagerResolverCondition.class)
@Bean
ReactiveAuthenticationManagerResolver authenticationManagerResolver(
- OAuth2ResourceServerProperties auth2ResourceServerProperties,
- SpringAddonsOidcProperties addonsProperties,
+ OpenidProviderPropertiesResolver opPropertiesResolver,
+ SpringAddonsReactiveJwtDecoderFactory jwtDecoderFactory,
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).orElse(null)))
- .filter(StringUtils::hasLength)
- .ifPresent(jwtConf -> {
- log.warn("spring.security.oauth2.resourceserver configuration will be ignored in favor of com.c4-soft.springaddons.oidc");
- });
- // @formatter:on
-
- final Map> jwtManagers = addonsProperties
- .getOps()
- .stream()
- .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.getIss().toString()).build();
-
- 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.getAud())
- .filter(StringUtils::hasText)
- .map(audience -> new JwtClaimValidator>(
- JwtClaimNames.AUD,
- (aud) -> aud != null && aud.contains(audience)))
- .map(audValidator -> (OAuth2TokenValidator) new DelegatingOAuth2TokenValidator<>(List.of(defaultValidator, audValidator)))
- .orElse(defaultValidator);
- // @formatter:on
-
- decoder.setJwtValidator(jwtValidator);
- var provider = new JwtReactiveAuthenticationManager(decoder);
- provider.setJwtAuthenticationConverter(jwtAuthenticationConverter);
- return Mono.just(provider);
- }));
-
- log
- .debug(
- "Building default JwtIssuerReactiveAuthenticationManagerResolver with: {} {}",
- auth2ResourceServerProperties.getJwt(),
- Stream.of(addonsProperties.getOps()).toList());
- return new JwtIssuerReactiveAuthenticationManagerResolver(issuerLocation -> jwtManagers.getOrDefault(issuerLocation, Mono.empty()));
+ return new SpringAddonsReactiveJwtAuthenticationManagerResolver(opPropertiesResolver, jwtDecoderFactory, jwtAuthenticationConverter);
}
/**
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/ResourceServerServerHttpSecurityPostProcessor.java
similarity index 65%
rename from spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/ResourceServerHttpSecurityPostProcessor.java
rename to spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/ResourceServerServerHttpSecurityPostProcessor.java
index 7a167d4dc..d5f48b452 100644
--- 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/ResourceServerServerHttpSecurityPostProcessor.java
@@ -2,7 +2,7 @@
import org.springframework.security.config.web.server.ServerHttpSecurity;
-import com.c4_soft.springaddons.security.oidc.starter.reactive.HttpSecurityPostProcessor;
+import com.c4_soft.springaddons.security.oidc.starter.reactive.ServerHttpSecurityPostProcessor;
/**
* Process {@link ServerHttpSecurity} of default security filter-chain after it was processed by spring-addons. This enables to override anything that was
@@ -10,5 +10,5 @@
*
* @author ch4mp
*/
-public interface ResourceServerHttpSecurityPostProcessor extends HttpSecurityPostProcessor {
+public interface ResourceServerServerHttpSecurityPostProcessor extends ServerHttpSecurityPostProcessor {
}
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/SpringAddonsReactiveJwtAuthenticationManagerResolver.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/SpringAddonsReactiveJwtAuthenticationManagerResolver.java
new file mode 100644
index 000000000..2e531e8e7
--- /dev/null
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/SpringAddonsReactiveJwtAuthenticationManagerResolver.java
@@ -0,0 +1,43 @@
+package com.c4_soft.springaddons.security.oidc.starter.reactive.resourceserver;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.authentication.ReactiveAuthenticationManager;
+import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.web.server.ServerWebExchange;
+
+import com.c4_soft.springaddons.security.oidc.starter.OpenidProviderPropertiesResolver;
+import com.c4_soft.springaddons.security.oidc.starter.synchronised.resourceserver.JWTClaimsSetAuthenticationManager.JWTClaimsSetAuthenticationManagerResolver;
+import com.c4_soft.springaddons.security.oidc.starter.synchronised.resourceserver.SpringAddonsJwtDecoderFactory;
+
+import reactor.core.publisher.Mono;
+
+/**
+ *
+ * An {@link ReactiveAuthenticationManagerResolver} always resolving the same {@link ReactiveJWTClaimsSetAuthenticationManager} which relies on
+ * {@link JWTClaimsSetAuthenticationManagerResolver}, itself using {@link SpringAddonsJwtDecoderFactory} and a {@link Converter Converter@lt;Jwt,
+ * AbstractAuthenticationToken>}.
+ *
+ *
+ * {@link DefaultSpringAddonsReactiveJwtDecoderFactory}, the default {@link SpringAddonsReactiveJwtDecoderFactory} throws an exception if the OpenID Provider
+ * configuration properties could not be resolved from the JWT claims.
+ *
+ *
+ * @author Jérôme Wacongne <ch4mp#64;c4-soft.com>
+ */
+public class SpringAddonsReactiveJwtAuthenticationManagerResolver implements ReactiveAuthenticationManagerResolver {
+ private final ReactiveJWTClaimsSetAuthenticationManager authenticationManager;
+
+ public SpringAddonsReactiveJwtAuthenticationManagerResolver(
+ OpenidProviderPropertiesResolver opPropertiesResolver,
+ SpringAddonsReactiveJwtDecoderFactory jwtDecoderFactory,
+ Converter> jwtAuthenticationConverter) {
+ authenticationManager = new ReactiveJWTClaimsSetAuthenticationManager(opPropertiesResolver, jwtDecoderFactory, jwtAuthenticationConverter);
+ }
+
+ @Override
+ public Mono resolve(ServerWebExchange context) {
+ return Mono.just(authenticationManager);
+ }
+}
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/SpringAddonsReactiveJwtDecoderFactory.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/SpringAddonsReactiveJwtDecoderFactory.java
new file mode 100644
index 000000000..b89cc4847
--- /dev/null
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/reactive/resourceserver/SpringAddonsReactiveJwtDecoderFactory.java
@@ -0,0 +1,21 @@
+package com.c4_soft.springaddons.security.oidc.starter.reactive.resourceserver;
+
+import java.net.URI;
+import java.util.Optional;
+
+import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
+
+import com.c4_soft.springaddons.security.oidc.starter.OpenidProviderPropertiesResolver;
+
+/**
+ *
+ * Provides with a JwtDecoder (configured with the required validators). Both JWK-set and issuer URIs are optional, but at least one should be provided.
+ *
+ *
+ * {@link DefaultSpringAddonsReactiveJwtDecoderFactory}, the default implementation uses {@link OpenidProviderPropertiesResolver} to resolve the matching OpenID Provider
+ * configuration properties and throws an exception if none are found (the token issuer is not trusted).
+ *
+ */
+public interface SpringAddonsReactiveJwtDecoderFactory {
+ ReactiveJwtDecoder create(Optional jwkSetUri, Optional issuer, Optional audience);
+}
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/ServerHttpSecurityPostProcessor.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/HttpSecurityPostProcessor.java
similarity index 81%
rename from spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/ServerHttpSecurityPostProcessor.java
rename to spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/HttpSecurityPostProcessor.java
index 837c05246..9c6a85d03 100644
--- a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/ServerHttpSecurityPostProcessor.java
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/HttpSecurityPostProcessor.java
@@ -2,6 +2,6 @@
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-public interface ServerHttpSecurityPostProcessor {
+public interface HttpSecurityPostProcessor {
HttpSecurity process(HttpSecurity httpSecurity) throws Exception;
}
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
index 9dd5d39ed..deecffac0 100644
--- 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
@@ -32,8 +32,11 @@
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.OpenidProviderPropertiesResolver;
+import com.c4_soft.springaddons.security.oidc.starter.ByIssuerOpenidProviderPropertiesResolver;
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.NotAConfiguredOpenidProviderException;
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;
@@ -51,6 +54,12 @@
@Slf4j
public class SpringAddonsOidcBeans {
+ @ConditionalOnMissingBean
+ OpenidProviderPropertiesResolver authoritiesMappingPropertiesProvider(SpringAddonsOidcProperties addonsProperties) {
+ log.debug("Building default AuthoritiesMappingPropertiesResolver with: {}", addonsProperties.getOps());
+ return new ByIssuerOpenidProviderPropertiesResolver(addonsProperties);
+ }
+
/**
* Retrieves granted authorities from a claims-set (decoded from JWT, introspected or obtained from userinfo end-point)
*
@@ -59,9 +68,8 @@ public class SpringAddonsOidcBeans {
*/
@ConditionalOnMissingBean
@Bean
- ClaimSetAuthoritiesConverter authoritiesConverter(SpringAddonsOidcProperties addonsProperties) {
- log.debug("Building default ConfigurableClaimSetAuthoritiesConverter with: {}", addonsProperties.getOps());
- return new ConfigurableClaimSetAuthoritiesConverter(addonsProperties);
+ ClaimSetAuthoritiesConverter authoritiesConverter(OpenidProviderPropertiesResolver authoritiesMappingPropertiesProvider) {
+ return new ConfigurableClaimSetAuthoritiesConverter(authoritiesMappingPropertiesProvider);
}
/**
@@ -75,11 +83,14 @@ ClaimSetAuthoritiesConverter authoritiesConverter(SpringAddonsOidcProperties add
@Bean
JwtAbstractAuthenticationTokenConverter jwtAuthenticationConverter(
Converter, Collection extends GrantedAuthority>> authoritiesConverter,
- SpringAddonsOidcProperties addonsProperties) {
+ OpenidProviderPropertiesResolver opPropertiesResolver) {
return jwt -> new JwtAuthenticationToken(
jwt,
authoritiesConverter.convert(jwt.getClaims()),
- new OpenidClaimSet(jwt.getClaims(), addonsProperties.getOpProperties(jwt.getIssuer()).getUsernameClaim()).getName());
+ new OpenidClaimSet(
+ jwt.getClaims(),
+ opPropertiesResolver.resolve(jwt.getClaims()).orElseThrow(() -> new NotAConfiguredOpenidProviderException(jwt.getClaims())).getUsernameClaim())
+ .getName());
}
/**
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/client/ClientHttpSecurityPostProcessor.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/client/ClientHttpSecurityPostProcessor.java
index 0aa4ee38d..f3b6d40b3 100644
--- a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/client/ClientHttpSecurityPostProcessor.java
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/client/ClientHttpSecurityPostProcessor.java
@@ -1,11 +1,11 @@
package com.c4_soft.springaddons.security.oidc.starter.synchronised.client;
-import com.c4_soft.springaddons.security.oidc.starter.synchronised.ServerHttpSecurityPostProcessor;
+import com.c4_soft.springaddons.security.oidc.starter.synchronised.HttpSecurityPostProcessor;
/**
* 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 {
+public interface ClientHttpSecurityPostProcessor extends HttpSecurityPostProcessor {
}
\ No newline at end of file
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/resourceserver/DefaultSpringAddonsJwtDecoderFactory.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/resourceserver/DefaultSpringAddonsJwtDecoderFactory.java
new file mode 100644
index 000000000..1d838298c
--- /dev/null
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/resourceserver/DefaultSpringAddonsJwtDecoderFactory.java
@@ -0,0 +1,70 @@
+package com.c4_soft.springaddons.security.oidc.starter.synchronised.resourceserver;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Optional;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
+import org.springframework.security.oauth2.core.OAuth2TokenValidator;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.JwtClaimNames;
+import org.springframework.security.oauth2.jwt.JwtClaimValidator;
+import org.springframework.security.oauth2.jwt.JwtDecoder;
+import org.springframework.security.oauth2.jwt.JwtValidators;
+import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+import com.c4_soft.springaddons.security.oidc.starter.OpenidProviderPropertiesResolver;
+
+import lombok.RequiredArgsConstructor;
+
+/**
+ *
+ * Provides with a JwtDecoder (configured with the required validators). Both JWK-set and issuer URIs are optional, but at least one must be provided.
+ *
+ *
+ * Uses {@link OpenidProviderPropertiesResolver} to resolve the matching OpenID Provider configuration properties and throws an exception if none are found
+ * (the token issuer is not trusted).
+ *
+ */
+@RequiredArgsConstructor
+public class DefaultSpringAddonsJwtDecoderFactory implements SpringAddonsJwtDecoderFactory {
+
+ @Override
+ public JwtDecoder create(Optional jwkSetUri, Optional issuer, Optional audience) {
+
+ final var decoder = jwkSetUri.isPresent()
+ ? NimbusJwtDecoder.withJwkSetUri(jwkSetUri.get().toString()).build()
+ : NimbusJwtDecoder.withIssuerLocation(issuer.orElseThrow(() -> new InvalidJwtDecoderCreationParametersException()).toString()).build();
+
+ final OAuth2TokenValidator defaultValidator = issuer
+ .map(URI::toString)
+ .map(JwtValidators::createDefaultWithIssuer)
+ .orElse(JwtValidators.createDefault());
+
+ // @formatter:off
+ final OAuth2TokenValidator jwtValidator = audience
+ .filter(StringUtils::hasText)
+ .map(opAudience -> new JwtClaimValidator>(
+ JwtClaimNames.AUD,
+ (aud) -> aud != null && aud.contains(opAudience)))
+ .map(audValidator -> (OAuth2TokenValidator) new DelegatingOAuth2TokenValidator<>(List.of(defaultValidator, audValidator)))
+ .orElse(defaultValidator);
+ // @formatter:on
+
+ decoder.setJwtValidator(jwtValidator);
+
+ return decoder;
+ }
+
+ @ResponseStatus(HttpStatus.UNAUTHORIZED)
+ static class InvalidJwtDecoderCreationParametersException extends RuntimeException {
+ private static final long serialVersionUID = 3575615882241560832L;
+
+ public InvalidJwtDecoderCreationParametersException() {
+ super("At least one of jwkSetUri or issuer must be provided");
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/resourceserver/IssuerStartsWithAuthenticationManagerResolver.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/resourceserver/IssuerStartsWithAuthenticationManagerResolver.java
deleted file mode 100644
index b71d0fb22..000000000
--- a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/resourceserver/IssuerStartsWithAuthenticationManagerResolver.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package com.c4_soft.springaddons.security.oidc.starter.synchronised.resourceserver;
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.springframework.core.convert.converter.Converter;
-import org.springframework.http.HttpStatus;
-import org.springframework.security.authentication.AbstractAuthenticationToken;
-import org.springframework.security.authentication.AuthenticationManager;
-import org.springframework.security.authentication.AuthenticationManagerResolver;
-import org.springframework.security.oauth2.jwt.Jwt;
-import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
-import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
-import org.springframework.web.bind.annotation.ResponseStatus;
-
-/**
- * Dynamic multi-tenancy based on issuer prefix (for instance, trust all reams from a given Keycloak Server)
- *
- * @author Jérôme Wacongne <ch4mp#64;c4-soft.com>
- */
-public class IssuerStartsWithAuthenticationManagerResolver implements AuthenticationManagerResolver {
-
- private final String issuerPrefix;
- private final Converter authenticationConverter;
- private final Map jwtManagers = new ConcurrentHashMap<>();
-
- /**
- * @param issuerPrefix what access tokens iss claim must start with
- * @param authenticationConverter converter from a valid {@link Jwt} to an {@link AbstractAuthenticationToken} instance
- */
- public IssuerStartsWithAuthenticationManagerResolver(String issuerPrefix, Converter authenticationConverter) {
- super();
- this.issuerPrefix = issuerPrefix.toString();
- this.authenticationConverter = authenticationConverter;
- }
-
- @Override
- public AuthenticationManager resolve(String issuer) {
- if (!jwtManagers.containsKey(issuer)) {
- if (!issuer.startsWith(issuerPrefix)) {
- throw new UnknownIssuerException(issuer);
- }
- final var decoder = NimbusJwtDecoder.withIssuerLocation(issuer).build();
- var provider = new JwtAuthenticationProvider(decoder);
- provider.setJwtAuthenticationConverter(authenticationConverter);
- jwtManagers.put(issuer, provider::authenticate);
- }
- return jwtManagers.get(issuer);
-
- }
-
- @ResponseStatus(HttpStatus.UNAUTHORIZED)
- static class UnknownIssuerException extends RuntimeException {
- private static final long serialVersionUID = -7140122776788781704L;
-
- public UnknownIssuerException(String issuer) {
- super("Unknown issuer: %s".formatted(issuer));
- }
- }
-}
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/resourceserver/JWTClaimsSetAuthenticationManager.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/resourceserver/JWTClaimsSetAuthenticationManager.java
new file mode 100644
index 000000000..5c129aac3
--- /dev/null
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/resourceserver/JWTClaimsSetAuthenticationManager.java
@@ -0,0 +1,109 @@
+package com.c4_soft.springaddons.security.oidc.starter.synchronised.resourceserver;
+
+import java.net.URI;
+import java.text.ParseException;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.AuthenticationManagerResolver;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
+import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
+import org.springframework.util.Assert;
+
+import com.c4_soft.springaddons.security.oidc.starter.OpenidProviderPropertiesResolver;
+import com.c4_soft.springaddons.security.oidc.starter.properties.NotAConfiguredOpenidProviderException;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.JWTParser;
+
+import lombok.RequiredArgsConstructor;
+
+/**
+ *
+ * An {@link AuthenticationManager} relying on {@link JWTClaimsSetAuthenticationManagerResolver}, itself using {@link SpringAddonsJwtDecoderFactory} and a
+ * {@link Converter Converter<Jwt, AbstractAuthenticationToken>}.
+ *
+ *
+ * {@link DefaultSpringAddonsJwtDecoderFactory}, the default {@link SpringAddonsJwtDecoderFactory} throws an exception if the OpenID Provider configuration
+ * properties could not be resolved from the JWT claims.
+ *
+ *
+ * @author Jérôme Wacongne <ch4mp#64;c4-soft.com>
+ */
+public class JWTClaimsSetAuthenticationManager implements AuthenticationManager {
+
+ private final JWTClaimsSetAuthenticationManagerResolver jwtAuthenticationManagerResolver;
+
+ public JWTClaimsSetAuthenticationManager(
+ OpenidProviderPropertiesResolver opPropertiesResolver,
+ SpringAddonsJwtDecoderFactory jwtDecoderFactory,
+ Converter jwtAuthenticationConverter) {
+ this.jwtAuthenticationManagerResolver = new JWTClaimsSetAuthenticationManagerResolver(
+ opPropertiesResolver,
+ jwtDecoderFactory,
+ jwtAuthenticationConverter);
+ }
+
+ @Override
+ public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+ Assert.isTrue(authentication instanceof BearerTokenAuthenticationToken, "Authentication must be of type BearerTokenAuthenticationToken");
+ JWTClaimsSet jwtClaimSet;
+ try {
+ jwtClaimSet = JWTParser.parse(((BearerTokenAuthenticationToken) authentication).getToken()).getJWTClaimsSet();
+ } catch (ParseException e) {
+ throw new InvalidBearerTokenException("Could not retrieve JWT claim-set");
+ }
+ AuthenticationManager authenticationManager = this.jwtAuthenticationManagerResolver.resolve(jwtClaimSet);
+ if (authenticationManager == null) {
+ throw new InvalidBearerTokenException("Could not resolve the authentication manager for the provided JWT");
+ }
+ return authenticationManager.authenticate(authentication);
+ }
+
+ /**
+ *
+ * An {@link AuthenticationManagerResolver} for resource servers using JWT decoder(s). It relies on a {@link SpringAddonsJwtDecoderFactory} and a
+ * {@link Converter Converter<Jwt, AbstractAuthenticationToken>}
+ *
+ *
+ * {@link DefaultSpringAddonsJwtDecoderFactory}, the default {@link SpringAddonsJwtDecoderFactory} throws an exception if the OpenID Provider configuration
+ * properties could not be resolved from the JWT claims.
+ *
+ *
+ * @author Jérôme Wacongne <ch4mp#64;c4-soft.com>
+ */
+ @RequiredArgsConstructor
+ public static class JWTClaimsSetAuthenticationManagerResolver implements AuthenticationManagerResolver {
+
+ private final OpenidProviderPropertiesResolver opPropertiesResolver;
+ private final SpringAddonsJwtDecoderFactory jwtDecoderFactory;
+ private final Converter jwtAuthenticationConverter;
+ private final Map jwtManagers = new ConcurrentHashMap<>();
+
+ @Override
+ public AuthenticationManager resolve(JWTClaimsSet jwt) {
+ final var issuer = jwt.getIssuer();
+ if (!jwtManagers.containsKey(issuer)) {
+ final var opProperties = opPropertiesResolver
+ .resolve(jwt.getClaims())
+ .orElseThrow(() -> new NotAConfiguredOpenidProviderException(jwt.getClaims()));
+
+ final var decoder = jwtDecoderFactory
+ .create(Optional.ofNullable(opProperties.getJwkSetUri()), Optional.of(URI.create(jwt.getIssuer())), Optional.of(opProperties.getAud()));
+
+ var provider = new JwtAuthenticationProvider(decoder);
+ provider.setJwtAuthenticationConverter(jwtAuthenticationConverter);
+ jwtManagers.put(issuer, provider::authenticate);
+ }
+ return jwtManagers.get(issuer);
+ }
+ }
+
+}
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/resourceserver/ResourceServerHttpSecurityPostProcessor.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/resourceserver/ResourceServerHttpSecurityPostProcessor.java
index 129533ab3..4afb859bb 100644
--- a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/resourceserver/ResourceServerHttpSecurityPostProcessor.java
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/resourceserver/ResourceServerHttpSecurityPostProcessor.java
@@ -2,7 +2,7 @@
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import com.c4_soft.springaddons.security.oidc.starter.synchronised.ServerHttpSecurityPostProcessor;
+import com.c4_soft.springaddons.security.oidc.starter.synchronised.HttpSecurityPostProcessor;
/**
* Process {@link HttpSecurity} of default security filter-chain after it was processed by spring-addons. This enables to override anything that was
@@ -10,5 +10,5 @@
*
* @author ch4mp
*/
-public interface ResourceServerHttpSecurityPostProcessor extends ServerHttpSecurityPostProcessor {
+public interface ResourceServerHttpSecurityPostProcessor extends HttpSecurityPostProcessor {
}
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/resourceserver/SpringAddonsJwtAuthenticationManagerResolver.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/resourceserver/SpringAddonsJwtAuthenticationManagerResolver.java
new file mode 100644
index 000000000..f088a5b5d
--- /dev/null
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/resourceserver/SpringAddonsJwtAuthenticationManagerResolver.java
@@ -0,0 +1,41 @@
+package com.c4_soft.springaddons.security.oidc.starter.synchronised.resourceserver;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.AuthenticationManagerResolver;
+import org.springframework.security.oauth2.jwt.Jwt;
+
+import com.c4_soft.springaddons.security.oidc.starter.OpenidProviderPropertiesResolver;
+import com.c4_soft.springaddons.security.oidc.starter.synchronised.resourceserver.JWTClaimsSetAuthenticationManager.JWTClaimsSetAuthenticationManagerResolver;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+/**
+ *
+ * An {@link AuthenticationManagerResolver} always resolving the same {@link JWTClaimsSetAuthenticationManager} which relies on
+ * {@link JWTClaimsSetAuthenticationManagerResolver}, itself using {@link SpringAddonsJwtDecoderFactory} and a {@link Converter Converter@lt;Jwt,
+ * AbstractAuthenticationToken>}.
+ *
+ *
+ * {@link DefaultSpringAddonsJwtDecoderFactory}, the default {@link SpringAddonsJwtDecoderFactory} throws an exception if the OpenID Provider configuration
+ * properties could not be resolved from the JWT claims.
+ *
+ *
+ * @author Jérôme Wacongne <ch4mp#64;c4-soft.com>
+ */
+public class SpringAddonsJwtAuthenticationManagerResolver implements AuthenticationManagerResolver {
+ private final JWTClaimsSetAuthenticationManager authenticationManager;
+
+ public SpringAddonsJwtAuthenticationManagerResolver(
+ OpenidProviderPropertiesResolver opPropertiesResolver,
+ SpringAddonsJwtDecoderFactory jwtDecoderFactory,
+ Converter jwtAuthenticationConverter) {
+ this.authenticationManager = new JWTClaimsSetAuthenticationManager(opPropertiesResolver, jwtDecoderFactory, jwtAuthenticationConverter);
+ }
+
+ @Override
+ public AuthenticationManager resolve(HttpServletRequest context) {
+ return authenticationManager;
+ }
+}
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/resourceserver/SpringAddonsJwtDecoderFactory.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/resourceserver/SpringAddonsJwtDecoderFactory.java
new file mode 100644
index 000000000..ea8a7603a
--- /dev/null
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/resourceserver/SpringAddonsJwtDecoderFactory.java
@@ -0,0 +1,21 @@
+package com.c4_soft.springaddons.security.oidc.starter.synchronised.resourceserver;
+
+import java.net.URI;
+import java.util.Optional;
+
+import org.springframework.security.oauth2.jwt.JwtDecoder;
+
+import com.c4_soft.springaddons.security.oidc.starter.OpenidProviderPropertiesResolver;
+
+/**
+ *
+ * Provides with a JwtDecoder (configured with the required validators). Both JWK-set and issuer URIs are optional, but at least one should be provided.
+ *
+ *
+ * {@link DefaultSpringAddonsJwtDecoderFactory}, the default implementation uses {@link OpenidProviderPropertiesResolver} to resolve the matching OpenID Provider
+ * configuration properties and throws an exception if none are found (the token issuer is not trusted).
+ *
+ */
+public interface SpringAddonsJwtDecoderFactory {
+ JwtDecoder create(Optional jwkSetUri, Optional issuer, Optional audience);
+}
\ No newline at end of file
diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/resourceserver/SpringAddonsOidcResourceServerBeans.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/resourceserver/SpringAddonsOidcResourceServerBeans.java
index e9850ca5f..c744fb46d 100644
--- a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/resourceserver/SpringAddonsOidcResourceServerBeans.java
+++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/resourceserver/SpringAddonsOidcResourceServerBeans.java
@@ -1,18 +1,12 @@
package com.c4_soft.springaddons.security.oidc.starter.synchronised.resourceserver;
-import java.net.URI;
-import java.util.List;
-import java.util.Map;
import java.util.Optional;
-import java.util.stream.Collectors;
-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.boot.autoconfigure.web.ServerProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
@@ -22,30 +16,22 @@
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AbstractAuthenticationToken;
-import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationManagerResolver;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.Authentication;
-import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
-import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.jwt.Jwt;
-import org.springframework.security.oauth2.jwt.JwtClaimNames;
-import org.springframework.security.oauth2.jwt.JwtClaimValidator;
import org.springframework.security.oauth2.jwt.JwtDecoder;
-import org.springframework.security.oauth2.jwt.JwtValidators;
-import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
-import org.springframework.security.oauth2.server.resource.authentication.JwtIssuerAuthenticationManagerResolver;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.server.SecurityWebFilterChain;
-import org.springframework.util.StringUtils;
+import com.c4_soft.springaddons.security.oidc.starter.OpenidProviderPropertiesResolver;
import com.c4_soft.springaddons.security.oidc.starter.properties.SpringAddonsOidcProperties;
import com.c4_soft.springaddons.security.oidc.starter.properties.condition.bean.DefaultAuthenticationManagerResolverCondition;
import com.c4_soft.springaddons.security.oidc.starter.properties.condition.bean.IsIntrospectingResourceServerCondition;
@@ -55,7 +41,6 @@
import com.c4_soft.springaddons.security.oidc.starter.synchronised.SpringAddonsOidcBeans;
import jakarta.servlet.http.HttpServletRequest;
-import lombok.extern.slf4j.Slf4j;
/**
*
@@ -91,7 +76,6 @@
@EnableWebSecurity
@AutoConfiguration
@ImportAutoConfiguration(SpringAddonsOidcBeans.class)
-@Slf4j
public class SpringAddonsOidcResourceServerBeans {
/**
*
@@ -202,63 +186,18 @@ ResourceServerHttpSecurityPostProcessor httpPostProcessor() {
/**
* 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.oidc" configuration properties
+ * @param opPropertiesResolver a resolver for OpenID Provider configuration properties
+ * @param jwtDecoderFactory something to build a JWT decoder from OpenID Provider configuration properties
* @param jwtAuthenticationConverter converts from a {@link Jwt} to an {@link Authentication} implementation
* @return Multi-tenant {@link AuthenticationManagerResolver} (one for each configured issuer)
*/
@Conditional(DefaultAuthenticationManagerResolverCondition.class)
@Bean
AuthenticationManagerResolver authenticationManagerResolver(
- OAuth2ResourceServerProperties auth2ResourceServerProperties,
- SpringAddonsOidcProperties addonsProperties,
+ OpenidProviderPropertiesResolver opPropertiesResolver,
+ SpringAddonsJwtDecoderFactory jwtDecoderFactory,
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.oidc");
- });
- // @formatter:on
-
- final Map jwtManagers = addonsProperties
- .getOps()
- .stream()
- .collect(Collectors.toMap(issuer -> issuer.getIss().toString(), issuer -> {
- final var decoder = issuer.getJwkSetUri() != null && StringUtils.hasLength(issuer.getJwkSetUri().toString())
- ? NimbusJwtDecoder.withJwkSetUri(issuer.getJwkSetUri().toString()).build()
- : NimbusJwtDecoder.withIssuerLocation(issuer.getIss().toString()).build();
-
- final OAuth2TokenValidator defaultValidator = Optional
- .ofNullable(issuer.getIss())
- .map(URI::toString)
- .map(JwtValidators::createDefaultWithIssuer)
- .orElse(JwtValidators.createDefault());
-
- // @formatter:off
- final OAuth2TokenValidator jwtValidator = Optional.ofNullable(issuer.getAud())
- .filter(StringUtils::hasText)
- .map(audience -> new JwtClaimValidator>(
- JwtClaimNames.AUD,
- (aud) -> aud != null && aud.contains(audience)))
- .map(audValidator -> (OAuth2TokenValidator) new DelegatingOAuth2TokenValidator<>(List.of(defaultValidator, audValidator)))
- .orElse(defaultValidator);
- // @formatter:on
-
- decoder.setJwtValidator(jwtValidator);
- var provider = new JwtAuthenticationProvider(decoder);
- provider.setJwtAuthenticationConverter(jwtAuthenticationConverter);
- return provider::authenticate;
- }));
-
- log
- .debug(
- "Building default JwtIssuerAuthenticationManagerResolver with: ",
- auth2ResourceServerProperties.getJwt(),
- Stream.of(addonsProperties.getOps()).toList());
-
- return new JwtIssuerAuthenticationManagerResolver((AuthenticationManagerResolver) jwtManagers::get);
+ return new SpringAddonsJwtAuthenticationManagerResolver(opPropertiesResolver, jwtDecoderFactory, jwtAuthenticationConverter);
}
@ConditionalOnMissingBean
diff --git a/spring-addons-starter-oidc/src/test/java/com/c4_soft/springaddons/security/oidc/starter/ConfigurableJwtGrantedAuthoritiesConverterTest.java b/spring-addons-starter-oidc/src/test/java/com/c4_soft/springaddons/security/oidc/starter/ConfigurableJwtGrantedAuthoritiesConverterTest.java
index 0e8faeb46..69b4c7072 100644
--- a/spring-addons-starter-oidc/src/test/java/com/c4_soft/springaddons/security/oidc/starter/ConfigurableJwtGrantedAuthoritiesConverterTest.java
+++ b/spring-addons-starter-oidc/src/test/java/com/c4_soft/springaddons/security/oidc/starter/ConfigurableJwtGrantedAuthoritiesConverterTest.java
@@ -53,7 +53,7 @@ public void test() throws URISyntaxException {
final var properties = new SpringAddonsOidcProperties();
properties.setOps(List.of(issuerProperties));
- final var converter = new ConfigurableClaimSetAuthoritiesConverter(properties);
+ final var converter = new ConfigurableClaimSetAuthoritiesConverter(new ByIssuerOpenidProviderPropertiesResolver(properties));
final var claimSet = new OpenidClaimSet(jwt.getClaims());
// Assert mapping with default properties