From 7160290aafec5e7d19d910fd5a1a57795733da59 Mon Sep 17 00:00:00 2001 From: Gaurav Tiwari Date: Sat, 19 Feb 2022 14:27:30 +0530 Subject: [PATCH] Allow Token Introspection to be customized Closes gh-493 --- .../OAuth2AuthorizationServerConfigurer.java | 24 ++- .../OAuth2TokenIntrospectionConfigurer.java | 162 ++++++++++++++++++ .../oauth2/core/OAuth2TokenIntrospection.java | 41 ++++- ...enIntrospectionAuthenticationProvider.java | 3 + ...Auth2TokenIntrospectionEndpointFilter.java | 63 ++++++- ...rospectionAuthenticationProviderTests.java | 56 ++++++ 6 files changed, 323 insertions(+), 26 deletions(-) create mode 100644 oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2TokenIntrospectionConfigurer.java diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java index 5cda883d..ed49c861 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java @@ -53,7 +53,6 @@ import org.springframework.security.oauth2.server.authorization.web.OAuth2Author import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenIntrospectionEndpointFilter; import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenRevocationEndpointFilter; import org.springframework.security.oauth2.server.authorization.web.ProviderContextFilter; -import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; import org.springframework.security.web.authentication.HttpStatusEntryPoint; import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; import org.springframework.security.web.context.HttpRequestResponseHolder; @@ -73,6 +72,7 @@ import org.springframework.util.Assert; * @author Daniel Garnier-Moiroux * @author Gerardo Roza * @author Ovidiu Popa + * @author Gaurav Tiwari * @since 0.0.1 * @see AbstractHttpConfigurer * @see OAuth2ClientAuthenticationConfigurer @@ -100,7 +100,7 @@ public final class OAuth2AuthorizationServerConfigurer tokenIntrospectionEndpoint(Customizer tokenIntrospectionEndpointCustomizer) { + tokenIntrospectionEndpointCustomizer.customize(getConfigurer(OAuth2TokenIntrospectionConfigurer.class)); + return this; + } + + /** * Returns a {@link RequestMatcher} for the authorization server endpoints. * @@ -387,12 +400,6 @@ public final class OAuth2AuthorizationServerConfigurer jwkSource = OAuth2ConfigurerUtils.getJwkSource(builder); if (jwkSource != null) { NimbusJwkSetEndpointFilter jwkSetEndpointFilter = new NimbusJwkSetEndpointFilter( @@ -412,6 +419,7 @@ public final class OAuth2AuthorizationServerConfigurer authenticationProviders = new LinkedList<>(); + private AuthenticationSuccessHandler tokenIntrospectionResponseHandler; + private AuthenticationFailureHandler errorResponseHandler; + + /** + * Restrict for internal use only. + */ + OAuth2TokenIntrospectionConfigurer(ObjectPostProcessor objectPostProcessor) { + super(objectPostProcessor); + } + + /** + * Sets the {@link AuthenticationConverter} used when attempting to extract an Access Token Request from {@link HttpServletRequest} + * to an instance of {@link OAuth2AuthorizationGrantAuthenticationToken} used for authenticating the authorization grant. + * + * @param accessTokenRequestConverter the {@link AuthenticationConverter} used when attempting to extract an Access Token Request from {@link HttpServletRequest} + * @return the {@link OAuth2TokenIntrospectionConfigurer} for further configuration + */ + public OAuth2TokenIntrospectionConfigurer accessTokenRequestConverter(AuthenticationConverter accessTokenRequestConverter) { + this.accessTokenRequestConverter = accessTokenRequestConverter; + return this; + } + + /** + * Adds an {@link AuthenticationProvider} used for authenticating a type of {@link OAuth2TokenIntrospectionAuthenticationToken}. + * + * @param authenticationProvider an {@link AuthenticationProvider} used for authenticating a type of {@link OAuth2TokenIntrospectionAuthenticationToken} + * @return the {@link OAuth2TokenIntrospectionConfigurer} for further configuration + */ + public OAuth2TokenIntrospectionConfigurer authenticationProvider(AuthenticationProvider authenticationProvider) { + Assert.notNull(authenticationProvider, "authenticationProvider cannot be null"); + this.authenticationProviders.add(authenticationProvider); + return this; + } + + /** + * Sets the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2TokenIntrospectionAuthenticationToken}. + * + * @param tokenIntrospectionResponseHandler the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2TokenIntrospectionAuthenticationToken} + * @return the {@link OAuth2TokenEndpointConfigurer} for further configuration + */ + public OAuth2TokenIntrospectionConfigurer accessTokenResponseHandler(AuthenticationSuccessHandler tokenIntrospectionResponseHandler) { + this.tokenIntrospectionResponseHandler = tokenIntrospectionResponseHandler; + return this; + } + + /** + * Sets the {@link AuthenticationFailureHandler} used for handling an {@link org.springframework.security.oauth2.core.OAuth2AuthenticationException} + * and returning the {@link OAuth2Error Error Response}. + * + * @param errorResponseHandler the {@link AuthenticationFailureHandler} used for handling an {@link org.springframework.security.oauth2.core.OAuth2AuthenticationException} + * @return the {@link OAuth2TokenIntrospectionConfigurer} for further configuration + */ + public OAuth2TokenIntrospectionConfigurer errorResponseHandler(AuthenticationFailureHandler errorResponseHandler) { + this.errorResponseHandler = errorResponseHandler; + return this; + } + + @Override + > void init(B builder) { + ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder); + this.requestMatcher = new AntPathRequestMatcher( + providerSettings.getTokenIntrospectionEndpoint(), HttpMethod.POST.name()); + + List authenticationProviders = this.authenticationProviders.isEmpty() ? createDefaultAuthenticationProviders(builder) : this.authenticationProviders; + authenticationProviders.forEach(authenticationProvider -> builder.authenticationProvider(postProcess(authenticationProvider))); + } + + @Override + > void configure(B builder) { + AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class); + ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder); + + OAuth2TokenIntrospectionEndpointFilter introspectionEndpointFilter = + new OAuth2TokenIntrospectionEndpointFilter(authenticationManager, providerSettings.getTokenIntrospectionEndpoint()); + + if (accessTokenRequestConverter != null) { + introspectionEndpointFilter.setAuthenticationConverter(this.accessTokenRequestConverter); + } + + if (this.tokenIntrospectionResponseHandler != null) { + introspectionEndpointFilter.setAuthenticationSuccessHandler(this.tokenIntrospectionResponseHandler); + } + + if (this.errorResponseHandler != null) { + introspectionEndpointFilter.setAuthenticationFailureHandler(this.errorResponseHandler); + } + + builder.addFilterAfter(postProcess(introspectionEndpointFilter), FilterSecurityInterceptor.class); + } + + @Override + public RequestMatcher getRequestMatcher() { + return this.requestMatcher; + } + + private > List createDefaultAuthenticationProviders(B builder) { + List authenticationProviders = new ArrayList<>(); + + OAuth2TokenIntrospectionAuthenticationProvider tokenIntrospectionAuthenticationProvider + = new OAuth2TokenIntrospectionAuthenticationProvider( + OAuth2ConfigurerUtils.getRegisteredClientRepository(builder), + OAuth2ConfigurerUtils.getAuthorizationService(builder) + ); + + authenticationProviders.add(tokenIntrospectionAuthenticationProvider); + + return authenticationProviders; + + } +} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/OAuth2TokenIntrospection.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/OAuth2TokenIntrospection.java index 4ad72f06..c236179b 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/OAuth2TokenIntrospection.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/OAuth2TokenIntrospection.java @@ -34,6 +34,7 @@ import org.springframework.util.Assert; * * @author Gerardo Roza * @author Joe Grandja + * @author Gaurav Tiwari * @since 0.1.1 * @see OAuth2TokenIntrospectionClaimAccessor * @see Section 2.2 Introspection Response @@ -257,6 +258,28 @@ public final class OAuth2TokenIntrospection implements OAuth2TokenIntrospectionC return this; } + /** + * Adds custom claims if corresponding keys don't exist in present set of claims. + * + * @since 0.2.3 + * @param presentedClaims map of all claims + * @return the {@link Builder} for further configuration + */ + public Builder withCustomClaims(Map presentedClaims) { + + if (presentedClaims != null && !presentedClaims.isEmpty()) { + + presentedClaims.keySet().forEach(key -> { + if (!this.claims.containsKey(key)) { + this.claim(key, presentedClaims.get(key)); + } + }); + + } + + return this; + } + /** * Provides access to every {@link #claim(String, Object)} declared so far with * the possibility to add, replace, or remove. @@ -312,15 +335,6 @@ public final class OAuth2TokenIntrospection implements OAuth2TokenIntrospectionC ((List) this.claims.get(name)).add(value); } - @SuppressWarnings("unchecked") - private void acceptClaimValues(String name, Consumer> valuesConsumer) { - Assert.hasText(name, "name cannot be empty"); - Assert.notNull(valuesConsumer, "valuesConsumer cannot be null"); - this.claims.computeIfAbsent(name, k -> new LinkedList()); - List values = (List) this.claims.get(name); - valuesConsumer.accept(values); - } - private static void validateURL(Object url, String errorMessage) { if (URL.class.isAssignableFrom(url.getClass())) { return; @@ -332,5 +346,14 @@ public final class OAuth2TokenIntrospection implements OAuth2TokenIntrospectionC throw new IllegalArgumentException(errorMessage, ex); } } + + @SuppressWarnings("unchecked") + private void acceptClaimValues(String name, Consumer> valuesConsumer) { + Assert.hasText(name, "name cannot be empty"); + Assert.notNull(valuesConsumer, "valuesConsumer cannot be null"); + this.claims.computeIfAbsent(name, k -> new LinkedList()); + List values = (List) this.claims.get(name); + valuesConsumer.accept(values); + } } } diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProvider.java index a7393d7f..bc7d651b 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProvider.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProvider.java @@ -41,6 +41,7 @@ import static org.springframework.security.oauth2.server.authorization.authentic * * @author Gerardo Roza * @author Joe Grandja + * @author Gaurav Tiwari * @since 0.1.1 * @see OAuth2TokenIntrospectionAuthenticationToken * @see RegisteredClientRepository @@ -143,6 +144,8 @@ public final class OAuth2TokenIntrospectionAuthenticationProvider implements Aut } } + tokenClaims.withCustomClaims(authorizedToken.getClaims()); + return tokenClaims.build(); } } diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenIntrospectionEndpointFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenIntrospectionEndpointFilter.java index 935608cf..59d55960 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenIntrospectionEndpointFilter.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenIntrospectionEndpointFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 the original author or authors. + * Copyright 2020-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,13 +24,13 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServletServerHttpResponse; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2Error; @@ -41,6 +41,9 @@ import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMe import org.springframework.security.oauth2.core.http.converter.OAuth2TokenIntrospectionHttpMessageConverter; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationToken; +import org.springframework.security.web.authentication.AuthenticationConverter; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; @@ -53,6 +56,7 @@ import org.springframework.web.filter.OncePerRequestFilter; * * @author Gerardo Roza * @author Joe Grandja + * @author Gaurav Tiwari * @see OAuth2TokenIntrospectionAuthenticationProvider * @see Section 2 Introspection Endpoint * @see Section 2.1 Introspection Request @@ -66,11 +70,13 @@ public final class OAuth2TokenIntrospectionEndpointFilter extends OncePerRequest private final AuthenticationManager authenticationManager; private final RequestMatcher tokenIntrospectionEndpointMatcher; - private final Converter tokenIntrospectionAuthenticationConverter = + private AuthenticationConverter tokenIntrospectionAuthenticationConverter = new DefaultTokenIntrospectionAuthenticationConverter(); private final HttpMessageConverter tokenIntrospectionHttpResponseConverter = new OAuth2TokenIntrospectionHttpMessageConverter(); private final HttpMessageConverter errorHttpResponseConverter = new OAuth2ErrorHttpMessageConverter(); + private AuthenticationSuccessHandler authenticationSuccessHandler = this::sendTokenIntrospectionResponse;; + private AuthenticationFailureHandler authenticationFailureHandler = this::sendErrorResponse; /** * Constructs an {@code OAuth2TokenIntrospectionEndpointFilter} using the provided parameters. @@ -112,21 +118,60 @@ public final class OAuth2TokenIntrospectionEndpointFilter extends OncePerRequest OAuth2TokenIntrospectionAuthenticationToken tokenIntrospectionAuthenticationResult = (OAuth2TokenIntrospectionAuthenticationToken) this.authenticationManager.authenticate(tokenIntrospectionAuthentication); - OAuth2TokenIntrospection tokenClaims = tokenIntrospectionAuthenticationResult.getTokenClaims(); - sendTokenIntrospectionResponse(response, tokenClaims); + this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, tokenIntrospectionAuthenticationResult); } catch (OAuth2AuthenticationException ex) { SecurityContextHolder.clearContext(); - sendErrorResponse(response, ex.getError()); + this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex); } } - private void sendTokenIntrospectionResponse(HttpServletResponse response, OAuth2TokenIntrospection tokenClaims) throws IOException { + /** + * Sets the {@link AuthenticationConverter} used when attempting to extract a Token Introspection Request from + * {@link HttpServletRequest} to an instance of {@link OAuth2TokenIntrospectionAuthenticationToken} used for authenticating the request. + * + * @param authenticationConverter the {@link AuthenticationConverter} used when attempting to extract a Token Introspection Request from {@link HttpServletRequest} + * @since 0.2.3 + */ + public void setAuthenticationConverter(AuthenticationConverter authenticationConverter) { + Assert.notNull(authenticationConverter, "authenticationConverter cannot be null."); + this.tokenIntrospectionAuthenticationConverter = authenticationConverter; + } + + /** + * Sets the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2TokenIntrospectionAuthenticationToken} + * + * @param authenticationSuccessHandler the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2TokenIntrospectionAuthenticationToken} + * @since 0.2.3 + */ + public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler authenticationSuccessHandler) { + Assert.notNull(authenticationSuccessHandler, "authenticationSuccessHandler cannot be null."); + this.authenticationSuccessHandler = authenticationSuccessHandler; + } + + /** + * Sets the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException} and + * returning {@link OAuth2Error Error Resonse}. + * + * @param authenticationFailureHandler the {@link .AuthenticationFailureHandler} used for handling {@link OAuth2AuthenticationException} + * @since 0.2.3 + */ + public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) { + Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null."); + this.authenticationFailureHandler = authenticationFailureHandler; + } + + private void sendTokenIntrospectionResponse(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { + + OAuth2TokenIntrospectionAuthenticationToken tokenIntrospectionAuthenticationResult = (OAuth2TokenIntrospectionAuthenticationToken) authentication; + OAuth2TokenIntrospection tokenClaims = tokenIntrospectionAuthenticationResult.getTokenClaims(); + ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); this.tokenIntrospectionHttpResponseConverter.write(tokenClaims, null, httpResponse); } - private void sendErrorResponse(HttpServletResponse response, OAuth2Error error) throws IOException { + private void sendErrorResponse(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException { + OAuth2Error error = ((OAuth2AuthenticationException) exception).getError(); ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); httpResponse.setStatusCode(HttpStatus.BAD_REQUEST); this.errorHttpResponseConverter.write(error, null, httpResponse); @@ -139,7 +184,7 @@ public final class OAuth2TokenIntrospectionEndpointFilter extends OncePerRequest } private static class DefaultTokenIntrospectionAuthenticationConverter - implements Converter { + implements AuthenticationConverter { @Override public Authentication convert(HttpServletRequest request) { diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProviderTests.java index aaebdd2f..aa44cf83 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProviderTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProviderTests.java @@ -54,6 +54,7 @@ import static org.mockito.Mockito.when; * * @author Gerardo Roza * @author Joe Grandja + * @author Gaurav Tiwari */ public class OAuth2TokenIntrospectionAuthenticationProviderTests { private RegisteredClientRepository registeredClientRepository; @@ -261,6 +262,61 @@ public class OAuth2TokenIntrospectionAuthenticationProviderTests { assertThat(tokenClaims.getId()).isEqualTo(claimsSet.getId()); } + @Test + public void authenticateWhenValidAccessTokenAndCustomClaimThenActiveAndCustomClaimInResponse() { + RegisteredClient authorizedClient = TestRegisteredClients.registeredClient().build(); + Instant issuedAt = Instant.now(); + Instant expiresAt = issuedAt.plus(Duration.ofHours(1)); + OAuth2AccessToken accessToken = new OAuth2AccessToken( + OAuth2AccessToken.TokenType.BEARER, "access-token", issuedAt, expiresAt, + new HashSet<>(Arrays.asList("scope1", "scope2"))); + + // @formatter:off + OAuth2TokenClaimsSet claimsSet = OAuth2TokenClaimsSet.builder() + .issuer("https://provider.com") + .subject("subject") + .audience(Collections.singletonList(authorizedClient.getClientId())) + .issuedAt(issuedAt) + .notBefore(issuedAt) + .expiresAt(expiresAt) + .claim("custom-claim", "custom-claim-value") + .id("id") + .build(); + // @formatter:on + + OAuth2Authorization authorization = TestOAuth2Authorizations + .authorization(authorizedClient, accessToken, claimsSet.getClaims()) + .build(); + when(this.authorizationService.findByToken(eq(accessToken.getTokenValue()), isNull())) + .thenReturn(authorization); + when(this.registeredClientRepository.findById(eq(authorizedClient.getId()))).thenReturn(authorizedClient); + RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); + OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken( + registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); + + OAuth2TokenIntrospectionAuthenticationToken authentication = new OAuth2TokenIntrospectionAuthenticationToken( + accessToken.getTokenValue(), clientPrincipal, null, null); + OAuth2TokenIntrospectionAuthenticationToken authenticationResult = + (OAuth2TokenIntrospectionAuthenticationToken) this.authenticationProvider.authenticate(authentication); + + verify(this.authorizationService).findByToken(eq(authentication.getToken()), isNull()); + verify(this.registeredClientRepository).findById(eq(authorizedClient.getId())); + assertThat(authenticationResult.isAuthenticated()).isTrue(); + OAuth2TokenIntrospection tokenClaims = authenticationResult.getTokenClaims(); + assertThat(tokenClaims.isActive()).isTrue(); + assertThat(tokenClaims.getClientId()).isEqualTo(authorizedClient.getClientId()); + assertThat(tokenClaims.getIssuedAt()).isEqualTo(accessToken.getIssuedAt()); + assertThat(tokenClaims.getExpiresAt()).isEqualTo(accessToken.getExpiresAt()); + assertThat(tokenClaims.getScopes()).containsExactlyInAnyOrderElementsOf(accessToken.getScopes()); + assertThat(tokenClaims.getTokenType()).isEqualTo(accessToken.getTokenType().getValue()); + assertThat(tokenClaims.getNotBefore()).isEqualTo(claimsSet.getNotBefore()); + assertThat(tokenClaims.getSubject()).isEqualTo(claimsSet.getSubject()); + assertThat(tokenClaims.getAudience()).containsExactlyInAnyOrderElementsOf(claimsSet.getAudience()); + assertThat(tokenClaims.getIssuer()).isEqualTo(claimsSet.getIssuer()); + assertThat(tokenClaims.getId()).isEqualTo(claimsSet.getId()); + assertThat((String) tokenClaims.getClaim("custom-claim")).isEqualTo("custom-claim-value"); + } + @Test public void authenticateWhenValidRefreshTokenThenActive() { RegisteredClient authorizedClient = TestRegisteredClients.registeredClient().build();