Allow Token Introspection to be customized
Closes gh-493
This commit is contained in:
committed by
Joe Grandja
parent
5ab82f83cb
commit
7160290aaf
@@ -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<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
|
||||
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
|
||||
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
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
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
|
||||
((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
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <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
|
||||
|
||||
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
|
||||
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<HttpServletRequest, Authentication> {
|
||||
implements AuthenticationConverter {
|
||||
|
||||
@Override
|
||||
public Authentication convert(HttpServletRequest request) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user