Browse Source

Allow Token Introspection to be customized

Closes gh-493
pull/679/head
Gaurav Tiwari 4 years ago committed by Joe Grandja
parent
commit
7160290aaf
  1. 24
      oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java
  2. 162
      oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2TokenIntrospectionConfigurer.java
  3. 41
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/OAuth2TokenIntrospection.java
  4. 3
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProvider.java
  5. 63
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenIntrospectionEndpointFilter.java
  6. 56
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProviderTests.java

24
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 @@ -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; @@ -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<B extends HttpSecurityBui @@ -100,7 +100,7 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
getRequestMatcher(OAuth2TokenEndpointConfigurer.class).matches(request) ||
getRequestMatcher(OAuth2TokenRevocationEndpointConfigurer.class).matches(request) ||
getRequestMatcher(OidcConfigurer.class).matches(request) ||
this.tokenIntrospectionEndpointMatcher.matches(request) ||
getRequestMatcher(OAuth2TokenIntrospectionConfigurer.class).matches(request) ||
this.jwkSetEndpointMatcher.matches(request) ||
this.authorizationServerMetadataEndpointMatcher.matches(request);
@ -221,6 +221,19 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui @@ -221,6 +221,19 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
return this;
}
/**
* Configures the OAuth 2.0 Token Introspection Endpoint.
*
* @param tokenIntrospectionEndpointCustomizer the {@link Customizer} providing access to the {@link OAuth2TokenIntrospectionConfigurer}
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
* @since 0.2.3
*/
public OAuth2AuthorizationServerConfigurer<B> tokenIntrospectionEndpoint(Customizer<OAuth2TokenIntrospectionConfigurer> 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<B extends HttpSecurityBui @@ -387,12 +400,6 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
ProviderContextFilter providerContextFilter = new ProviderContextFilter(providerSettings);
builder.addFilterAfter(postProcess(providerContextFilter), SecurityContextPersistenceFilter.class);
OAuth2TokenIntrospectionEndpointFilter tokenIntrospectionEndpointFilter =
new OAuth2TokenIntrospectionEndpointFilter(
authenticationManager,
providerSettings.getTokenIntrospectionEndpoint());
builder.addFilterAfter(postProcess(tokenIntrospectionEndpointFilter), FilterSecurityInterceptor.class);
JWKSource<com.nimbusds.jose.proc.SecurityContext> jwkSource = OAuth2ConfigurerUtils.getJwkSource(builder);
if (jwkSource != null) {
NimbusJwkSetEndpointFilter jwkSetEndpointFilter = new NimbusJwkSetEndpointFilter(
@ -412,6 +419,7 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui @@ -412,6 +419,7 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
configurers.put(OAuth2TokenEndpointConfigurer.class, new OAuth2TokenEndpointConfigurer(this::postProcess));
configurers.put(OAuth2TokenRevocationEndpointConfigurer.class, new OAuth2TokenRevocationEndpointConfigurer(this::postProcess));
configurers.put(OidcConfigurer.class, new OidcConfigurer(this::postProcess));
configurers.put(OAuth2TokenIntrospectionConfigurer.class, new OAuth2TokenIntrospectionConfigurer(this::postProcess));
return configurers;
}

162
oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2TokenIntrospectionConfigurer.java

@ -0,0 +1,162 @@ @@ -0,0 +1,162 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://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 org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenIntrospectionEndpointFilter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
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;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
/**
* Configurer for OAuth 2.0 Token Introspection.
*
* @author Gaurav Tiwari
* @since 0.2.3
*/
public class OAuth2TokenIntrospectionConfigurer extends AbstractOAuth2Configurer {
private RequestMatcher requestMatcher;
private AuthenticationConverter accessTokenRequestConverter;
private final List<AuthenticationProvider> authenticationProviders = new LinkedList<>();
private AuthenticationSuccessHandler tokenIntrospectionResponseHandler;
private AuthenticationFailureHandler errorResponseHandler;
/**
* Restrict for internal use only.
*/
OAuth2TokenIntrospectionConfigurer(ObjectPostProcessor<Object> 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
<B extends HttpSecurityBuilder<B>> void init(B builder) {
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
this.requestMatcher = new AntPathRequestMatcher(
providerSettings.getTokenIntrospectionEndpoint(), HttpMethod.POST.name());
List<AuthenticationProvider> authenticationProviders = this.authenticationProviders.isEmpty() ? createDefaultAuthenticationProviders(builder) : this.authenticationProviders;
authenticationProviders.forEach(authenticationProvider -> builder.authenticationProvider(postProcess(authenticationProvider)));
}
@Override
<B extends HttpSecurityBuilder<B>> 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 <B extends HttpSecurityBuilder<B>> List<AuthenticationProvider> createDefaultAuthenticationProviders(B builder) {
List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
OAuth2TokenIntrospectionAuthenticationProvider tokenIntrospectionAuthenticationProvider
= new OAuth2TokenIntrospectionAuthenticationProvider(
OAuth2ConfigurerUtils.getRegisteredClientRepository(builder),
OAuth2ConfigurerUtils.getAuthorizationService(builder)
);
authenticationProviders.add(tokenIntrospectionAuthenticationProvider);
return authenticationProviders;
}
}

41
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/OAuth2TokenIntrospection.java

@ -34,6 +34,7 @@ import org.springframework.util.Assert; @@ -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 <a target="_blank" href="https://tools.ietf.org/html/rfc7662#section-2.2">Section 2.2 Introspection Response</a>
@ -257,6 +258,28 @@ public final class OAuth2TokenIntrospection implements OAuth2TokenIntrospectionC @@ -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<String, Object> 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 @@ -312,15 +335,6 @@ public final class OAuth2TokenIntrospection implements OAuth2TokenIntrospectionC
((List<String>) this.claims.get(name)).add(value);
}
@SuppressWarnings("unchecked")
private void acceptClaimValues(String name, Consumer<List<String>> valuesConsumer) {
Assert.hasText(name, "name cannot be empty");
Assert.notNull(valuesConsumer, "valuesConsumer cannot be null");
this.claims.computeIfAbsent(name, k -> new LinkedList<String>());
List<String> values = (List<String>) 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 @@ -332,5 +346,14 @@ public final class OAuth2TokenIntrospection implements OAuth2TokenIntrospectionC
throw new IllegalArgumentException(errorMessage, ex);
}
}
@SuppressWarnings("unchecked")
private void acceptClaimValues(String name, Consumer<List<String>> valuesConsumer) {
Assert.hasText(name, "name cannot be empty");
Assert.notNull(valuesConsumer, "valuesConsumer cannot be null");
this.claims.computeIfAbsent(name, k -> new LinkedList<String>());
List<String> values = (List<String>) this.claims.get(name);
valuesConsumer.accept(values);
}
}
}

3
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 @@ -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 @@ -143,6 +144,8 @@ public final class OAuth2TokenIntrospectionAuthenticationProvider implements Aut
}
}
tokenClaims.withCustomClaims(authorizedToken.getClaims());
return tokenClaims.build();
}
}

63
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenIntrospectionEndpointFilter.java

@ -1,5 +1,5 @@ @@ -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; @@ -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 @@ -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; @@ -53,6 +56,7 @@ import org.springframework.web.filter.OncePerRequestFilter;
*
* @author Gerardo Roza
* @author Joe Grandja
* @author Gaurav Tiwari
* @see OAuth2TokenIntrospectionAuthenticationProvider
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7662#section-2">Section 2 Introspection Endpoint</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7662#section-2.1">Section 2.1 Introspection Request</a>
@ -66,11 +70,13 @@ public final class OAuth2TokenIntrospectionEndpointFilter extends OncePerRequest @@ -66,11 +70,13 @@ public final class OAuth2TokenIntrospectionEndpointFilter extends OncePerRequest
private final AuthenticationManager authenticationManager;
private final RequestMatcher tokenIntrospectionEndpointMatcher;
private final Converter<HttpServletRequest, Authentication> tokenIntrospectionAuthenticationConverter =
private AuthenticationConverter tokenIntrospectionAuthenticationConverter =
new DefaultTokenIntrospectionAuthenticationConverter();
private final HttpMessageConverter<OAuth2TokenIntrospection> tokenIntrospectionHttpResponseConverter =
new OAuth2TokenIntrospectionHttpMessageConverter();
private final HttpMessageConverter<OAuth2Error> 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 @@ -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 @@ -139,7 +184,7 @@ public final class OAuth2TokenIntrospectionEndpointFilter extends OncePerRequest
}
private static class DefaultTokenIntrospectionAuthenticationConverter
implements Converter<HttpServletRequest, Authentication> {
implements AuthenticationConverter {
@Override
public Authentication convert(HttpServletRequest request) {

56
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; @@ -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 { @@ -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();

Loading…
Cancel
Save