Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Opaque Token Validation Support for Servlet Web Application Type #185

Merged
merged 105 commits into from
Oct 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
105 commits
Select commit Hold shift + click to select a range
5d86f86
fix: pom.xml to reduce vulnerabilities
snyk-bot Sep 9, 2020
9020e89
add new property to OktaOAuth2Properties
arvindkrishnakumar-okta Sep 10, 2020
e1f1695
updates
arvindkrishnakumar-okta Sep 10, 2020
84c3bf8
added application properties with logging config
arvindkrishnakumar-okta Sep 10, 2020
275dc24
removed unused variable - pmd violation
arvindkrishnakumar-okta Sep 10, 2020
d384df4
added custom opaque token introspector based off on nimbus oaque toke…
arvindkrishnakumar-okta Sep 10, 2020
c5f78ef
updated per review comments
arvindkrishnakumar-okta Sep 10, 2020
3d9ca2b
minor refactor
arvindkrishnakumar-okta Sep 10, 2020
65879e7
minor refactor
arvindkrishnakumar-okta Sep 10, 2020
22121b1
fixed pmd violations
arvindkrishnakumar-okta Sep 10, 2020
bf4a5d6
fixed pmd violations
arvindkrishnakumar-okta Sep 10, 2020
5792bc0
fixed pmd violations
arvindkrishnakumar-okta Sep 10, 2020
65abd6b
updates per comments
arvindkrishnakumar-okta Sep 10, 2020
9707f2a
removed application.properties
arvindkrishnakumar-okta Sep 10, 2020
5d51036
updated javadoc comment for opaque
arvindkrishnakumar-okta Sep 10, 2020
3ee0d7d
updated javadoc comment for opaque
arvindkrishnakumar-okta Sep 10, 2020
44c3597
updates per review comments
arvindkrishnakumar-okta Sep 10, 2020
d2363bf
removed okta opaque token introspection class and refactored around it
arvindkrishnakumar-okta Sep 10, 2020
d936e5f
minor refactoring
arvindkrishnakumar-okta Sep 10, 2020
2ba8a26
updates
arvindkrishnakumar-okta Sep 11, 2020
a05d2a4
updates
arvindkrishnakumar-okta Sep 11, 2020
a5c2fd4
fix tests
arvindkrishnakumar-okta Sep 11, 2020
873c527
fix tests
arvindkrishnakumar-okta Sep 11, 2020
ba13ab6
fix tests
arvindkrishnakumar-okta Sep 11, 2020
95e9781
refactored
arvindkrishnakumar-okta Sep 11, 2020
489f956
fix tests
arvindkrishnakumar-okta Sep 11, 2020
e60bc56
fix tests
arvindkrishnakumar-okta Sep 11, 2020
4b85138
refactor
arvindkrishnakumar-okta Sep 11, 2020
f3fd8e7
refactoring/cleanup
arvindkrishnakumar-okta Sep 11, 2020
daafbef
reverted changes to AutoConfigConditionalTest
arvindkrishnakumar-okta Sep 11, 2020
ccc4b92
Merge https://github.com/okta/okta-spring-boot into add_opaque_token_…
arvindkrishnakumar-okta Sep 11, 2020
a393db4
fix tests
arvindkrishnakumar-okta Sep 11, 2020
c22d216
add custom opaque token conditional
arvindkrishnakumar-okta Sep 11, 2020
5c92761
add licence header to new class
arvindkrishnakumar-okta Sep 12, 2020
15306de
refactoring done
arvindkrishnakumar-okta Sep 12, 2020
b803f01
minor refactor
arvindkrishnakumar-okta Sep 14, 2020
177e4f4
impl isRootOrgIssuer method
arvindkrishnakumar-okta Sep 14, 2020
f0a0d8f
minor refactoring
arvindkrishnakumar-okta Sep 16, 2020
d6546ba
added customizer that can configure jwt or opaque flows conditionally…
arvindkrishnakumar-okta Sep 16, 2020
062f8bf
added code snippet to readme on how to add custom claims to jwt acces…
arvindkrishnakumar-okta Sep 2, 2020
f731d37
update to code snippet
arvindkrishnakumar-okta Sep 2, 2020
8941160
update README
arvindkrishnakumar-okta Sep 2, 2020
ec23e62
update README
arvindkrishnakumar-okta Sep 2, 2020
6151511
update README
arvindkrishnakumar-okta Sep 2, 2020
41d876b
Update README.md
arvindkrishnakumar-okta Sep 18, 2020
548bbd2
Update README.md
arvindkrishnakumar-okta Sep 18, 2020
9a946e2
Update README.md
arvindkrishnakumar-okta Sep 18, 2020
f542d64
Update README.md
arvindkrishnakumar-okta Sep 21, 2020
55675d4
Update README.md
arvindkrishnakumar-okta Sep 21, 2020
45fdede
updates to work with both jwt and opaque cases plus root org detection
arvindkrishnakumar-okta Sep 25, 2020
335e5ec
fix findbug error
arvindkrishnakumar-okta Sep 25, 2020
0fb2a71
minor refactor
arvindkrishnakumar-okta Sep 25, 2020
a699262
bumped spring boot to v2.3.4.RELEASE
arvindkrishnakumar-okta Sep 25, 2020
5664fd3
added opaque token custom group claim converter
arvindkrishnakumar-okta Sep 28, 2020
75f7636
Update README.md
arvindkrishnakumar-okta Sep 23, 2020
a9f3ec2
Update README.md
arvindkrishnakumar-okta Sep 23, 2020
9cc9c32
Update README.md
arvindkrishnakumar-okta Sep 28, 2020
19cbb59
Update README.md
arvindkrishnakumar-okta Sep 28, 2020
0f6217e
Update README.md
arvindkrishnakumar-okta Sep 28, 2020
ba1994a
Update README.md
arvindkrishnakumar-okta Sep 28, 2020
fca8b6e
Update README.md
arvindkrishnakumar-okta Sep 28, 2020
5ad5ac4
Update README.md
arvindkrishnakumar-okta Sep 28, 2020
f0c392d
Update README.md
arvindkrishnakumar-okta Sep 28, 2020
0b19761
Update README.md
arvindkrishnakumar-okta Sep 28, 2020
eac495b
Update README.md
arvindkrishnakumar-okta Sep 23, 2020
4b5c98e
Update README.md
arvindkrishnakumar-okta Sep 23, 2020
4a9dc26
Update README.md
arvindkrishnakumar-okta Sep 23, 2020
7561915
Update README.md
arvindkrishnakumar-okta Sep 23, 2020
15e013e
Update README.md
arvindkrishnakumar-okta Sep 23, 2020
188adfa
Update README.md
arvindkrishnakumar-okta Sep 28, 2020
65cf376
Update README.md
arvindkrishnakumar-okta Sep 28, 2020
2f3c97b
Update README.md
arvindkrishnakumar-okta Sep 28, 2020
74f229e
Update README.md
arvindkrishnakumar-okta Sep 28, 2020
a56578d
Update README.md
arvindkrishnakumar-okta Sep 28, 2020
796ec4a
Update README.md
arvindkrishnakumar-okta Sep 28, 2020
63f64d0
Improve Quickstart steps to be numbers
Sep 28, 2020
7143c71
Add link to Spring Security issue
Sep 28, 2020
7429fbc
fix: readme typo
robertjd Oct 1, 2020
d90629b
Add note in readme about the orgUrl property
bdemers Oct 5, 2020
79c66f9
Fixing formatting issues
bdemers Oct 5, 2020
50654be
fix: pom.xml to reduce vulnerabilities
snyk-bot Sep 19, 2020
08c81a0
Merge https://github.com/okta/okta-spring-boot into add_opaque_token_…
arvindkrishnakumar-okta Oct 5, 2020
a8979e4
fix some review comments
arvindkrishnakumar-okta Oct 5, 2020
f215abf
fixed review comments
arvindkrishnakumar-okta Oct 6, 2020
13f637a
fixed review comments
arvindkrishnakumar-okta Oct 7, 2020
b1b2b5c
added logging
arvindkrishnakumar-okta Oct 7, 2020
de58e94
review comments addressed
arvindkrishnakumar-okta Oct 7, 2020
50cc50b
added test for missing client secret in OktaOAuth2PropertiesMappingEn…
arvindkrishnakumar-okta Oct 7, 2020
07671b8
added UserAgent header to resttemplate used in OpaqueTokenIntrospecti…
arvindkrishnakumar-okta Oct 8, 2020
fed0000
used an alternate constructor for NimbusOpaqueTokenIntrospector
arvindkrishnakumar-okta Oct 8, 2020
83a986b
cleanup
arvindkrishnakumar-okta Oct 8, 2020
0f472c3
cleanup
arvindkrishnakumar-okta Oct 8, 2020
8476284
review comments addressed
arvindkrishnakumar-okta Oct 8, 2020
99ad9d6
added opaque token support for Reactive/Webflux case as well
arvindkrishnakumar-okta Oct 8, 2020
0b7bdbf
minor refactor
arvindkrishnakumar-okta Oct 9, 2020
249e562
reverted unintentional import removal
arvindkrishnakumar-okta Oct 9, 2020
64a14ae
minor refactoring
arvindkrishnakumar-okta Oct 9, 2020
d757e38
reverted changes done for opaque token support to Reactive/webflux ap…
arvindkrishnakumar-okta Oct 12, 2020
5e1481a
Ensuring the Okta Env Post Processor only sets valid properties
bdemers Oct 12, 2020
dc6cd54
addressed review comments
arvindkrishnakumar-okta Oct 12, 2020
8a4293b
Revert change that increases scope of var
bdemers Oct 13, 2020
6f33aa9
Updated tests to reflect previous condition (custom as support only)
bdemers Oct 13, 2020
a3b454c
added more unit tests to cover all possible resource server config co…
arvindkrishnakumar-okta Oct 14, 2020
606e027
Update test method names
bdemers Oct 14, 2020
e77aec3
minor linting fixes
bdemers Oct 14, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2020-Present Okta, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.okta.spring.tests.oauth2.implicit

import com.okta.test.mock.Scenario
import com.okta.test.mock.application.ApplicationTestRunner
import org.hamcrest.Matchers
import org.testng.annotations.Test

import static com.okta.test.mock.scenarios.Scenario.IMPLICIT_FLOW_REMOTE_VALIDATION
import static io.restassured.RestAssured.given

@Scenario(IMPLICIT_FLOW_REMOTE_VALIDATION)
class ImplicitRemoteValidationGroupIT extends ApplicationTestRunner {

private final static String ERROR_401 = "401 Unauthorized"

@Test
void groupAccessTest() {
given()
.header("Authorization", "Bearer ${IMPLICIT_FLOW_REMOTE_VALIDATION.definition.accessTokenJwt}")
.redirects()
.follow(false)
.when()
.get("http://localhost:${applicationPort}/everyone")
.then()
.statusCode(200)
.body(Matchers.equalTo("Everyone has Access: [email protected]"))
bdemers marked this conversation as resolved.
Show resolved Hide resolved
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties;
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
Expand All @@ -33,14 +33,19 @@
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
import org.springframework.web.client.RestTemplate;

import java.lang.reflect.Field;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.Optional;

import static org.springframework.util.StringUtils.isEmpty;

final class OktaOAuth2Configurer extends AbstractHttpConfigurer<OktaOAuth2Configurer, HttpSecurity> {

private static final Logger log = LoggerFactory.getLogger(OktaOAuth2Configurer.class);

@SuppressWarnings("rawtypes")
@Override
public void init(HttpSecurity http) throws Exception {

Expand All @@ -50,6 +55,8 @@ public void init(HttpSecurity http) throws Exception {
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
if (!context.getBeansOfType(OAuth2ClientProperties.class).isEmpty()
&& !isEmpty(oktaOAuth2Properties.getIssuer())
Expand All @@ -61,26 +68,73 @@ public void init(HttpSecurity http) throws Exception {
if (!context.getBeansOfType(OidcClientInitiatedLogoutSuccessHandler.class).isEmpty()) {
http.logout().logoutSuccessHandler(context.getBean(OidcClientInitiatedLogoutSuccessHandler.class));
}

} else {
log.debug("OAuth/OIDC Login not configured due to missing issuer, client-id, or client-secret property");
}

// resource server configuration
if (!context.getBeansOfType(OAuth2ResourceServerProperties.class).isEmpty()) {
OAuth2ResourceServerProperties resourceServerProperties = context.getBean(OAuth2ResourceServerProperties.class);
if (!isEmpty(resourceServerProperties.getJwt().getIssuerUri())) {
// configure Okta specific auth converter (extracts authorities from `groupsClaim`
configureResourceServer(http, oktaOAuth2Properties);
} else {
log.debug("OAuth resource server not configured due to missing issuer property");
}
// Resource Server Config

// if issuer is root org, use opaque token validation
if (TokenUtil.isRootOrgIssuer(oktaOAuth2Properties.getIssuer())) {
log.debug("Opaque Token validation/introspection will be configured.");
configureResourceServerForOpaqueTokenValidation(http, oktaOAuth2Properties);
return;
}

OAuth2ResourceServerConfigurer oAuth2ResourceServerConfigurer = http.getConfigurer(OAuth2ResourceServerConfigurer.class);

if (getJwtConfigurer(oAuth2ResourceServerConfigurer).isPresent()) {
log.debug("JWT configurer is set in OAuth resource server configuration. " +
"JWT validation will be configured.");
configureResourceServerForJwtValidation(http, oktaOAuth2Properties);
} else if (getOpaqueTokenConfigurer(oAuth2ResourceServerConfigurer).isPresent()) {
log.debug("Opaque Token configurer is set in OAuth resource server configuration. " +
"Opaque Token validation/introspection will be configured.");
configureResourceServerForOpaqueTokenValidation(http, oktaOAuth2Properties);
} else {
log.debug("OAuth resource server not configured due to missing OAuth2ResourceServerProperties bean");
log.debug("Defaulting to Okta JWT resource server configuration.");
configureResourceServerForJwtValidation(http, oktaOAuth2Properties);
}
}
}

private Optional<OAuth2ResourceServerConfigurer<?>.JwtConfigurer> getJwtConfigurer(OAuth2ResourceServerConfigurer<?> oAuth2ResourceServerConfigurer) throws IllegalAccessException {
if (oAuth2ResourceServerConfigurer != null) {
return getFieldValue(oAuth2ResourceServerConfigurer, "jwtConfigurer");
}
return Optional.empty();
}

private Optional<OAuth2ResourceServerConfigurer<?>.OpaqueTokenConfigurer> getOpaqueTokenConfigurer(OAuth2ResourceServerConfigurer<?> oAuth2ResourceServerConfigurer) throws IllegalAccessException {
if (oAuth2ResourceServerConfigurer != null) {
return getFieldValue(oAuth2ResourceServerConfigurer, "opaqueTokenConfigurer");
}
return Optional.empty();
}

private <T> Optional<T> getFieldValue(Object source, String fieldName) throws IllegalAccessException {
Field field = AccessController.doPrivileged((PrivilegedAction<Field>) () -> {
Field result = null;
try {
result = OAuth2ResourceServerConfigurer.class.getDeclaredField(fieldName);
result.setAccessible(true);
} catch (NoSuchFieldException e) {
log.warn("Could not get field '" + fieldName + "' of {} via reflection",
OAuth2ResourceServerConfigurer.class.getName(), e);
}
return result;
});

if (field == null) {
String errMsg = "Expected field '" + fieldName + "' was not found in OAuth resource server configuration. " +
"Version incompatibility with Spring Security detected." +
"Check https://github.com/okta/okta-spring-boot for project updates.";
throw new RuntimeException(errMsg);
}

return Optional.ofNullable((T) field.get(source));
}

private void configureLogin(HttpSecurity http, OktaOAuth2Properties oktaOAuth2Properties) throws Exception {

http.oauth2Login()
Expand All @@ -92,10 +146,16 @@ private void configureLogin(HttpSecurity http, OktaOAuth2Properties oktaOAuth2Pr
}
}

private void configureResourceServer(HttpSecurity http, OktaOAuth2Properties oktaOAuth2Properties) throws Exception {

private void configureResourceServerForJwtValidation(HttpSecurity http, OktaOAuth2Properties oktaOAuth2Properties) throws Exception {
http.oauth2ResourceServer()
.jwt().jwtAuthenticationConverter(new OktaJwtAuthenticationConverter(oktaOAuth2Properties.getGroupsClaim()));
.jwt().jwtAuthenticationConverter(new OktaJwtAuthenticationConverter(oktaOAuth2Properties.getGroupsClaim()));
}

private void configureResourceServerForOpaqueTokenValidation(HttpSecurity http, OktaOAuth2Properties oktaOAuth2Properties) throws Exception {

if (!isEmpty(oktaOAuth2Properties.getClientId()) && !isEmpty(oktaOAuth2Properties.getClientSecret())) {
http.oauth2ResourceServer().opaqueToken();
}
}

private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,22 @@
import org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.support.BasicAuthenticationInterceptor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal;
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoderJwkSupport;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.web.client.RestOperations;
import org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
import org.springframework.web.client.RestTemplate;

import java.util.Collection;
import java.util.Collections;

@Configuration
@AutoConfigureBefore(OAuth2ResourceServerAutoConfiguration.class)
@ConditionalOnClass(JwtAuthenticationToken.class)
Expand All @@ -45,15 +54,39 @@ class OktaOAuth2ResourceServerAutoConfig {
JwtDecoder jwtDecoder(OAuth2ResourceServerProperties oAuth2ResourceServerProperties,
OktaOAuth2Properties oktaOAuth2Properties) {

NimbusJwtDecoderJwkSupport decoder = new NimbusJwtDecoderJwkSupport(oAuth2ResourceServerProperties.getJwt().getJwkSetUri());
decoder.setJwtValidator(TokenUtil.jwtValidator(oAuth2ResourceServerProperties.getJwt().getIssuerUri(), oktaOAuth2Properties.getAudience()));
decoder.setRestOperations(restOperations());
return decoder;
NimbusJwtDecoder.JwkSetUriJwtDecoderBuilder builder = NimbusJwtDecoder.withJwkSetUri(oAuth2ResourceServerProperties.getJwt().getJwkSetUri());
builder.restOperations(restTemplate());
NimbusJwtDecoder decoder = builder.build();
decoder.setJwtValidator(TokenUtil.jwtValidator(oktaOAuth2Properties.getIssuer(), oktaOAuth2Properties.getAudience()));
return decoder;
}

private RestOperations restOperations() {
private RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getInterceptors().add(new UserAgentRequestInterceptor());
return restTemplate;
}

@Bean
@Conditional(OktaOpaqueTokenIntrospectConditional.class)
OpaqueTokenIntrospector opaqueTokenIntrospector(OktaOAuth2Properties oktaOAuth2Properties,
OAuth2ResourceServerProperties oAuth2ResourceServerProperties) {

RestTemplate restTemplate = restTemplate();
restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor(
oAuth2ResourceServerProperties.getOpaquetoken().getClientId(),
oAuth2ResourceServerProperties.getOpaquetoken().getClientSecret()));

OpaqueTokenIntrospector delegate = new NimbusOpaqueTokenIntrospector(
oAuth2ResourceServerProperties.getOpaquetoken().getIntrospectionUri(),
restTemplate);

return token -> {
OAuth2AuthenticatedPrincipal principal = delegate.introspect(token);
Collection<GrantedAuthority> mappedAuthorities =
Collections.unmodifiableCollection(TokenUtil.tokenClaimsToAuthorities(principal.getAttributes(), oktaOAuth2Properties.getGroupsClaim()));
return new DefaultOAuth2AuthenticatedPrincipal(
principal.getName(), principal.getAttributes(), mappedAuthorities);
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2020-Present Okta, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.okta.spring.boot.oauth;

import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;

class OktaOpaqueTokenIntrospectConditional extends AllNestedConditions {

OktaOpaqueTokenIntrospectConditional() {
super(ConfigurationPhase.REGISTER_BEAN);
}

@ConditionalOnProperty(name="okta.oauth2.client-id")
static class ClientIdCondition { }

@ConditionalOnProperty(name="okta.oauth2.client-secret")
static class ClientSecretCondition { }

@ConditionalOnProperty(name="okta.oauth2.issuer")
static class IssuerCondition { }
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.okta.spring.boot.oauth.config.OktaOAuth2Properties;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
import org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration;
Expand All @@ -28,6 +29,7 @@
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
import org.springframework.web.reactive.function.client.WebClient;

@Configuration
@AutoConfigureBefore(ReactiveOAuth2ResourceServerAutoConfiguration.class)
Expand All @@ -38,10 +40,18 @@
class ReactiveOktaOAuth2ResourceServerAutoConfig {

@Bean
@ConditionalOnMissingBean
ReactiveJwtDecoder jwtDecoder(OAuth2ResourceServerProperties oAuth2ResourceServerProperties, OktaOAuth2Properties oktaOAuth2Properties) {

NimbusReactiveJwtDecoder jwtDecoder = new NimbusReactiveJwtDecoder(oAuth2ResourceServerProperties.getJwt().getJwkSetUri());
jwtDecoder.setJwtValidator(TokenUtil.jwtValidator(oAuth2ResourceServerProperties.getJwt().getIssuerUri(), oktaOAuth2Properties.getAudience()));
return jwtDecoder;
NimbusReactiveJwtDecoder.JwkSetUriReactiveJwtDecoderBuilder builder =
NimbusReactiveJwtDecoder.withJwkSetUri(oAuth2ResourceServerProperties.getJwt().getJwkSetUri());
builder.webClient(webClient());
NimbusReactiveJwtDecoder decoder = builder.build();
decoder.setJwtValidator(TokenUtil.jwtValidator(oAuth2ResourceServerProperties.getJwt().getIssuerUri(), oktaOAuth2Properties.getAudience()));
return decoder;
}

private WebClient webClient() {
return WebClientUtil.createWebClient();
}
}
34 changes: 34 additions & 0 deletions oauth2/src/main/java/com/okta/spring/boot/oauth/TokenUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.okta.spring.boot.oauth;

import com.okta.commons.lang.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.GrantedAuthority;
Expand All @@ -31,6 +32,8 @@
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
Expand Down Expand Up @@ -90,4 +93,35 @@ static OAuth2TokenValidator<Jwt> jwtValidator(String issuer, String audience ) {
});
return new DelegatingOAuth2TokenValidator<>(validators);
}

/**
* Check if the issuer is root/org URI.
*
* Issuer URL that does not follow the pattern '/oauth2/default' (or) '/oauth2/some_id_string' is
* considered root/org issuer.
*
* e.g. https://sample.okta.com (root/org url)
* https://sample.okta.com/oauth2/default (non-root issuer/org url)
* https://sample.okta.com/oauth2/ausar5cbq5TRRsbcJ0h7 (non-root issuer/org url)
*
* @param issuerUri
* @return true if root/org, false otherwise
*/
static boolean isRootOrgIssuer(String issuerUri) throws MalformedURLException {
String uriPath = new URL(issuerUri).getPath();

if (Strings.hasText(uriPath)) {
String[] tokenizedUri = uriPath.substring(uriPath.indexOf("/")+1).split("/");

if (tokenizedUri.length >= 2 &&
"oauth2".equals(tokenizedUri[0]) &&
Strings.hasText(tokenizedUri[1])) {
log.debug("The issuer URL: '{}' is an Okta custom authorization server", issuerUri);
return false;
}
}

log.debug("The issuer URL: '{}' is an Okta root/org authorization server", issuerUri);
return true;
}
}
Loading