diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospector.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospector.java index 899b6e948e..4e3756db7b 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospector.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospector.java @@ -121,7 +121,7 @@ public class NimbusReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke MediaType contentType = responseEntity.headers().contentType().orElseThrow(() -> { this.logger.trace("Did not receive Content-Type from introspection endpoint in response"); - throw new OAuth2IntrospectionException( + return new OAuth2IntrospectionException( "Introspection endpoint response was invalid, as no Content-Type header was provided"); }); diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospector.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospector.java index 0e6b19a6d0..187c970260 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospector.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospector.java @@ -35,6 +35,7 @@ import org.springframework.http.MediaType; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; +import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames; import org.springframework.util.Assert; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.ClientResponse; @@ -122,7 +123,7 @@ public class SpringReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke // relying solely on the authorization server to validate this token (not checking // 'exp', for example) return responseEntity.bodyToMono(STRING_OBJECT_MAP) - .filter((body) -> (boolean) body.compute(OAuth2IntrospectionClaimNames.ACTIVE, (k, v) -> { + .filter((body) -> (boolean) body.compute(OAuth2TokenIntrospectionClaimNames.ACTIVE, (k, v) -> { if (v instanceof String) { return Boolean.parseBoolean((String) v); } @@ -134,16 +135,16 @@ public class SpringReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke } private OAuth2AuthenticatedPrincipal convertClaimsSet(Map claims) { - claims.computeIfPresent(OAuth2IntrospectionClaimNames.AUDIENCE, (k, v) -> { + claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.AUD, (k, v) -> { if (v instanceof String) { return Collections.singletonList(v); } return v; }); - claims.computeIfPresent(OAuth2IntrospectionClaimNames.CLIENT_ID, (k, v) -> v.toString()); - claims.computeIfPresent(OAuth2IntrospectionClaimNames.EXPIRES_AT, + claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.CLIENT_ID, (k, v) -> v.toString()); + claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.EXP, (k, v) -> Instant.ofEpochSecond(((Number) v).longValue())); - claims.computeIfPresent(OAuth2IntrospectionClaimNames.ISSUED_AT, + claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.IAT, (k, v) -> Instant.ofEpochSecond(((Number) v).longValue())); // RFC-7662 page 7 directs users to RFC-7519 for defining the values of these // issuer fields. @@ -163,11 +164,11 @@ public class SpringReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke // may be awkward to debug, we do not want to manipulate this value. Previous // versions of Spring Security // would *only* allow valid URLs, which is not what we wish to achieve here. - claims.computeIfPresent(OAuth2IntrospectionClaimNames.ISSUER, (k, v) -> v.toString()); - claims.computeIfPresent(OAuth2IntrospectionClaimNames.NOT_BEFORE, + claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.ISS, (k, v) -> v.toString()); + claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.NBF, (k, v) -> Instant.ofEpochSecond(((Number) v).longValue())); Collection authorities = new ArrayList<>(); - claims.computeIfPresent(OAuth2IntrospectionClaimNames.SCOPE, (k, v) -> { + claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.SCOPE, (k, v) -> { if (v instanceof String) { Collection scopes = Arrays.asList(((String) v).split(" ")); for (String scope : scopes) { diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospectorTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospectorTests.java index ef9dc48980..6de5470b40 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospectorTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/SpringReactiveOpaqueTokenIntrospectorTests.java @@ -37,6 +37,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; +import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames; import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.reactive.function.client.WebClient; @@ -106,18 +107,19 @@ public class SpringReactiveOpaqueTokenIntrospectorTests { SpringReactiveOpaqueTokenIntrospector introspectionClient = new SpringReactiveOpaqueTokenIntrospector( introspectUri, CLIENT_ID, CLIENT_SECRET); OAuth2AuthenticatedPrincipal authority = introspectionClient.introspect("token").block(); + assertThat(authority).isNotNull(); // @formatter:off assertThat(authority.getAttributes()) .isNotNull() - .containsEntry(OAuth2IntrospectionClaimNames.ACTIVE, true) - .containsEntry(OAuth2IntrospectionClaimNames.AUDIENCE, + .containsEntry(OAuth2TokenIntrospectionClaimNames.ACTIVE, true) + .containsEntry(OAuth2TokenIntrospectionClaimNames.AUD, Arrays.asList("https://protected.example.net/resource")) - .containsEntry(OAuth2IntrospectionClaimNames.CLIENT_ID, "l238j323ds-23ij4") - .containsEntry(OAuth2IntrospectionClaimNames.EXPIRES_AT, Instant.ofEpochSecond(1419356238)) - .containsEntry(OAuth2IntrospectionClaimNames.ISSUER, "https://server.example.com/") - .containsEntry(OAuth2IntrospectionClaimNames.SCOPE, Arrays.asList("read", "write", "dolphin")) - .containsEntry(OAuth2IntrospectionClaimNames.SUBJECT, "Z5O3upPC88QrAjx00dis") - .containsEntry(OAuth2IntrospectionClaimNames.USERNAME, "jdoe") + .containsEntry(OAuth2TokenIntrospectionClaimNames.CLIENT_ID, "l238j323ds-23ij4") + .containsEntry(OAuth2TokenIntrospectionClaimNames.EXP, Instant.ofEpochSecond(1419356238)) + .containsEntry(OAuth2TokenIntrospectionClaimNames.ISS, "https://server.example.com/") + .containsEntry(OAuth2TokenIntrospectionClaimNames.SCOPE, Arrays.asList("read", "write", "dolphin")) + .containsEntry(OAuth2TokenIntrospectionClaimNames.SUB, "Z5O3upPC88QrAjx00dis") + .containsEntry(OAuth2TokenIntrospectionClaimNames.USERNAME, "jdoe") .containsEntry("extension_field", "twenty-seven"); // @formatter:on } @@ -149,21 +151,22 @@ public class SpringReactiveOpaqueTokenIntrospectorTests { @Test public void authenticateWhenActiveTokenThenParsesValuesInResponse() { Map introspectedValues = new HashMap<>(); - introspectedValues.put(OAuth2IntrospectionClaimNames.ACTIVE, true); - introspectedValues.put(OAuth2IntrospectionClaimNames.AUDIENCE, Arrays.asList("aud")); - introspectedValues.put(OAuth2IntrospectionClaimNames.NOT_BEFORE, 29348723984L); + introspectedValues.put(OAuth2TokenIntrospectionClaimNames.ACTIVE, true); + introspectedValues.put(OAuth2TokenIntrospectionClaimNames.AUD, Arrays.asList("aud")); + introspectedValues.put(OAuth2TokenIntrospectionClaimNames.NBF, 29348723984L); WebClient webClient = mockResponse(introspectedValues); SpringReactiveOpaqueTokenIntrospector introspectionClient = new SpringReactiveOpaqueTokenIntrospector( INTROSPECTION_URL, webClient); OAuth2AuthenticatedPrincipal authority = introspectionClient.introspect("token").block(); + assertThat(authority).isNotNull(); // @formatter:off assertThat(authority.getAttributes()) .isNotNull() - .containsEntry(OAuth2IntrospectionClaimNames.ACTIVE, true) - .containsEntry(OAuth2IntrospectionClaimNames.AUDIENCE, Arrays.asList("aud")) - .containsEntry(OAuth2IntrospectionClaimNames.NOT_BEFORE, Instant.ofEpochSecond(29348723984L)) - .doesNotContainKey(OAuth2IntrospectionClaimNames.CLIENT_ID) - .doesNotContainKey(OAuth2IntrospectionClaimNames.SCOPE); + .containsEntry(OAuth2TokenIntrospectionClaimNames.ACTIVE, true) + .containsEntry(OAuth2TokenIntrospectionClaimNames.AUD, Arrays.asList("aud")) + .containsEntry(OAuth2TokenIntrospectionClaimNames.NBF, Instant.ofEpochSecond(29348723984L)) + .doesNotContainKey(OAuth2TokenIntrospectionClaimNames.CLIENT_ID) + .doesNotContainKey(OAuth2TokenIntrospectionClaimNames.SCOPE); // @formatter:on } @@ -234,6 +237,7 @@ public class SpringReactiveOpaqueTokenIntrospectorTests { return webClient; } + @SuppressWarnings("unchecked") private Map toMap(String string) { try { return this.mapper.readValue(string, Map.class);