diff --git a/oauth2/src/main/java/com/okta/spring/boot/oauth/Okta.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/Okta.java index 414acf0e0..9d98c640c 100644 --- a/oauth2/src/main/java/com/okta/spring/boot/oauth/Okta.java +++ b/oauth2/src/main/java/com/okta/spring/boot/oauth/Okta.java @@ -15,7 +15,7 @@ */ package com.okta.spring.boot.oauth; -import org.springframework.beans.factory.annotation.Value; +import com.okta.spring.boot.oauth.config.OktaOAuth2Properties; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -24,6 +24,7 @@ import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; import org.springframework.security.oauth2.client.web.server.DefaultServerOAuth2AuthorizationRequestResolver; import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizationRequestResolver; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; @@ -102,12 +103,6 @@ public static ServerHttpSecurity configureOAuth2WithPkce(ServerHttpSecurity http authorizationRequestResolver.setAuthorizationRequestCustomizer(withPkce()); // enable oauth2 login that uses PKCE http.oauth2Login().authorizationRequestResolver(authorizationRequestResolver); - // enable passing the audience parameter - http.oauth2Login(oauth2 -> oauth2 - .authorizationRequestResolver( - authorizeRequestResolver(clientRegistrationRepository) - ) - ); return http; } @@ -135,6 +130,61 @@ public static HttpSecurity configureOAuth2WithPkce(HttpSecurity http, ClientRegi return http; } + /** + * Configures the {@code http} with an OAuth2 Login, that sends an audience parameter with the authorize request. + * + * @param http the HttpSecurity to configure + * @param clientRegistrationRepository the repository bean, this should be injected into the calling method. + * @param properties the OktaOAuth2Properties bean, this should be injected into the calling method. + * @return the {@code http} to allow method chaining + */ + public static ServerHttpSecurity configureOAuth2WithAudience(ServerHttpSecurity http, + ReactiveClientRegistrationRepository clientRegistrationRepository, + OktaOAuth2Properties properties) throws Exception { + // Don't add audience parameter if empty or default + if (isDefaultAudience(properties)) { + return http; + } + http.oauth2Login(oauth2 -> oauth2 + .authorizationRequestResolver( + reactiveAuthzRequestResolver(clientRegistrationRepository, properties.getAudience()) + ) + ); + + return http; + } + + private static boolean isDefaultAudience(OktaOAuth2Properties properties) { + String audience = properties.getAudience(); + return audience == null || audience.isEmpty() || audience.equals("api://default"); + } + + /** + * Configures the {@code http} with an OAuth2 Login, that sends an audience parameter with the authorize request. + * + * @param http the HttpSecurity to configure + * @param clientRegistrationRepository the repository bean, this should be injected into the calling method. + * @param properties the OktaOAuth2Properties bean, this should be injected into the calling method. + * @return the {@code http} to allow method chaining + */ + public static HttpSecurity configureOAuth2WithAudience(HttpSecurity http, + ClientRegistrationRepository clientRegistrationRepository, + OktaOAuth2Properties properties) throws Exception { + // Don't add audience parameter if empty or default + if (isDefaultAudience(properties)) { + return http; + } + http.oauth2Login(oauth2 -> oauth2 + .authorizationEndpoint(authorization -> authorization + .authorizationRequestResolver( + mvcAuthzRequestResolver(clientRegistrationRepository, properties.getAudience()) + ) + ) + ); + + return http; + } + private static RequestMatcher textRequestMatcher() { return new MediaTypeRequestMatcher(new HeaderContentNegotiationStrategy(), MediaType.TEXT_PLAIN); } @@ -162,22 +212,31 @@ static String statusAsString(HttpStatus status) { return status.value() + " " + status.getReasonPhrase(); } - @Value("${okta.oauth2.audience:}") - private static String audience; - - private static ServerOAuth2AuthorizationRequestResolver authorizeRequestResolver( - ReactiveClientRegistrationRepository clientRegistrationRepository) { + private static ServerOAuth2AuthorizationRequestResolver reactiveAuthzRequestResolver( + ReactiveClientRegistrationRepository clientRegistrationRepository, String audience) { DefaultServerOAuth2AuthorizationRequestResolver authorizationRequestResolver = new DefaultServerOAuth2AuthorizationRequestResolver( clientRegistrationRepository); authorizationRequestResolver.setAuthorizationRequestCustomizer( - authorizeRequestCustomizer()); + authorizationRequestCustomizer(audience)); + + return authorizationRequestResolver; + } + + private static OAuth2AuthorizationRequestResolver mvcAuthzRequestResolver( + ClientRegistrationRepository clientRegistrationRepository, String audience) { + + DefaultOAuth2AuthorizationRequestResolver authorizationRequestResolver = + new DefaultOAuth2AuthorizationRequestResolver( + clientRegistrationRepository, "/oauth2/authorization"); + authorizationRequestResolver.setAuthorizationRequestCustomizer( + authorizationRequestCustomizer(audience)); return authorizationRequestResolver; } - private static Consumer authorizeRequestCustomizer() { + private static Consumer authorizationRequestCustomizer(String audience) { return customizer -> customizer .additionalParameters(params -> params.put("audience", audience)); } diff --git a/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2AutoConfig.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2AutoConfig.java index 49435b0f6..8b83c2b22 100644 --- a/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2AutoConfig.java +++ b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2AutoConfig.java @@ -79,11 +79,12 @@ OAuth2UserService oidcUserService( static class OAuth2SecurityFilterChainConfiguration { @Bean - SecurityFilterChain oauth2SecurityFilterChain(HttpSecurity http, ClientRegistrationRepository clientRegistrationRepository) throws Exception { + SecurityFilterChain oauth2SecurityFilterChain(HttpSecurity http, ClientRegistrationRepository clientRegistrationRepository, OktaOAuth2Properties oktaOAuth2Properties) throws Exception { // as of Spring Security 5.4 the default chain uses oauth2Login OR a JWT resource server (NOT both) // this does the same as both defaults merged together (and provides the previous behavior) http.authorizeRequests((requests) -> requests.anyRequest().authenticated()); Okta.configureOAuth2WithPkce(http, clientRegistrationRepository); + Okta.configureOAuth2WithAudience(http, clientRegistrationRepository, oktaOAuth2Properties); http.oauth2Client(); http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt); return http.build(); diff --git a/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2Configurer.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2Configurer.java index 3c96629d8..597532c88 100644 --- a/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2Configurer.java +++ b/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2Configurer.java @@ -18,7 +18,6 @@ import com.okta.spring.boot.oauth.config.OktaOAuth2Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties; import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties; import org.springframework.context.ApplicationContext; @@ -30,25 +29,17 @@ import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; import org.springframework.security.oauth2.client.oidc.web.logout.OidcClientInitiatedLogoutSuccessHandler; -import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; -import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver; -import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.web.client.RestTemplate; import java.lang.reflect.Field; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Optional; -import java.util.function.Consumer; import static com.okta.commons.lang.Strings.isEmpty; final class OktaOAuth2Configurer extends AbstractHttpConfigurer { - @Value("${okta.oauth2.audience:}") - private String audience; - private static final Logger log = LoggerFactory.getLogger(OktaOAuth2Configurer.class); @SuppressWarnings("rawtypes") @@ -60,6 +51,7 @@ public void init(HttpSecurity http) throws Exception { // make sure OktaOAuth2Properties are available if (!context.getBeansOfType(OktaOAuth2Properties.class).isEmpty()) { OktaOAuth2Properties oktaOAuth2Properties = context.getBean(OktaOAuth2Properties.class); + // Auth Code Flow Config // if OAuth2ClientProperties bean is not available do NOT configure @@ -71,8 +63,7 @@ public void init(HttpSecurity http) throws Exception { && !isEmpty(propertiesProvider.getIssuerUri()) && !isEmpty(propertiesRegistration.getClientId())) { // configure Okta user services - ClientRegistrationRepository clientRegistrationRepository = context.getBean(ClientRegistrationRepository.class); - configureLogin(http, oktaOAuth2Properties, context.getEnvironment(), clientRegistrationRepository); + configureLogin(http, oktaOAuth2Properties, context.getEnvironment()); // check for RP-Initiated logout if (!context.getBeansOfType(OidcClientInitiatedLogoutSuccessHandler.class).isEmpty()) { @@ -171,22 +162,14 @@ private void unsetJwtConfigurer(OAuth2ResourceServerConfigurer oAuth2ResourceSer }); } - private void configureLogin(HttpSecurity http, OktaOAuth2Properties oktaOAuth2Properties, Environment environment, - ClientRegistrationRepository clientRegistrationRepository) throws Exception { + private void configureLogin(HttpSecurity http, OktaOAuth2Properties oktaOAuth2Properties, Environment environment) throws Exception { RestTemplate restTemplate = OktaOAuth2ResourceServerAutoConfig.restTemplate(oktaOAuth2Properties); - http.oauth2Login(oauth2 -> oauth2 - .authorizationEndpoint(authorization -> authorization - .authorizationRequestResolver(authorizationRequestResolver(clientRegistrationRepository))) - .tokenEndpoint(token -> token - .accessTokenResponseClient(accessTokenResponseClient(restTemplate))) - ); + http.oauth2Login() + .tokenEndpoint() + .accessTokenResponseClient(accessTokenResponseClient(restTemplate)); - String audienceProperty = environment.getProperty("okta.oauth2.audience"); - if (audienceProperty != null) { - audience = audienceProperty; - } String redirectUriProperty = environment.getProperty("spring.security.oauth2.client.registration.okta.redirect-uri"); if (redirectUriProperty != null) { // remove `{baseUrl}` pattern, if present, as Spring will solve this on its own @@ -222,19 +205,4 @@ private OAuth2AccessTokenResponseClient acc return accessTokenResponseClient; } - - private OAuth2AuthorizationRequestResolver authorizationRequestResolver(ClientRegistrationRepository clientRegistrationRepository) { - DefaultOAuth2AuthorizationRequestResolver authorizationRequestResolver = - new DefaultOAuth2AuthorizationRequestResolver( - clientRegistrationRepository, "/oauth2/authorization"); - authorizationRequestResolver.setAuthorizationRequestCustomizer( - authorizationRequestCustomizer()); - - return authorizationRequestResolver; - } - - private Consumer authorizationRequestCustomizer() { - return customizer -> customizer - .additionalParameters(params -> params.put("audience", audience)); - } } \ No newline at end of file diff --git a/oauth2/src/main/java/com/okta/spring/boot/oauth/ReactiveOktaOAuth2AutoConfig.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/ReactiveOktaOAuth2AutoConfig.java index a8d30cebd..66ff57cf5 100644 --- a/oauth2/src/main/java/com/okta/spring/boot/oauth/ReactiveOktaOAuth2AutoConfig.java +++ b/oauth2/src/main/java/com/okta/spring/boot/oauth/ReactiveOktaOAuth2AutoConfig.java @@ -17,10 +17,8 @@ import com.okta.spring.boot.oauth.config.OktaOAuth2Properties; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureBefore; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; @@ -52,9 +50,6 @@ @Import(AuthorityProvidersConfig.class) class ReactiveOktaOAuth2AutoConfig { - @Value("${okta.oauth2.audience:}") - private String audience; - @Bean @ConditionalOnMissingBean ReactiveOAuth2UserService oauth2UserService(Collection authoritiesProviders) { @@ -69,13 +64,13 @@ OidcReactiveOAuth2UserService oidcUserService(Collection au } @Bean - @ConditionalOnBean(ReactiveJwtDecoder.class) @ConditionalOnMissingBean(SecurityWebFilterChain.class) - SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, ReactiveJwtDecoder jwtDecoder, ReactiveClientRegistrationRepository clientRegistrationRepository) { + SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, ReactiveJwtDecoder jwtDecoder, ReactiveClientRegistrationRepository clientRegistrationRepository, OktaOAuth2Properties properties) throws Exception { // as of Spring Security 5.4 the default chain uses oauth2Login OR a JWT resource server (NOT both) // this does the same as both defaults merged together (and provides the previous behavior) http.authorizeExchange().anyExchange().authenticated(); Okta.configureOAuth2WithPkce(http, clientRegistrationRepository); + Okta.configureOAuth2WithAudience(http, clientRegistrationRepository, properties); http.oauth2Client(); http.oauth2ResourceServer((server) -> customDecoder(server, jwtDecoder)); return http.build(); diff --git a/oauth2/src/main/java/com/okta/spring/boot/oauth/env/OktaOAuth2PropertiesMappingEnvironmentPostProcessor.java b/oauth2/src/main/java/com/okta/spring/boot/oauth/env/OktaOAuth2PropertiesMappingEnvironmentPostProcessor.java index 119a2b3d0..c939e90b1 100644 --- a/oauth2/src/main/java/com/okta/spring/boot/oauth/env/OktaOAuth2PropertiesMappingEnvironmentPostProcessor.java +++ b/oauth2/src/main/java/com/okta/spring/boot/oauth/env/OktaOAuth2PropertiesMappingEnvironmentPostProcessor.java @@ -142,7 +142,7 @@ public void postProcessEnvironment(ConfigurableEnvironment environment, SpringAp environment.getPropertySources().addLast(oktaOpaqueTokenPropertySource(environment, oidcMetadata)); } environment.getPropertySources().addLast(oktaRedirectUriPropertySource(environment)); - environment.getPropertySources().addLast(otkaForcePkcePropertySource(environment, oidcMetadata)); + environment.getPropertySources().addLast(oktaForcePkcePropertySource(environment, oidcMetadata)); if (application != null) { // This is required as EnvironmentPostProcessors are run before logging system is initialized @@ -150,7 +150,7 @@ public void postProcessEnvironment(ConfigurableEnvironment environment, SpringAp } } - private PropertySource otkaForcePkcePropertySource(ConfigurableEnvironment environment, OIDCMetadata oidcMetadata) { + private PropertySource oktaForcePkcePropertySource(ConfigurableEnvironment environment, OIDCMetadata oidcMetadata) { Map props = new HashMap<>(); props.put("spring.security.oauth2.client.registration.okta.client-authentication-method", oidcMetadata.getClientAuthenticationMethod());