Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@
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;
Expand All @@ -71,6 +70,7 @@
* @author Daniel Garnier-Moiroux
* @author Gerardo Roza
* @author Ovidiu Popa
* @author Gaurav Tiwari
* @since 0.0.1
* @see AbstractHttpConfigurer
* @see OAuth2ClientAuthenticationConfigurer
Expand Down Expand Up @@ -98,7 +98,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);

Expand Down Expand Up @@ -219,6 +219,19 @@ public OAuth2AuthorizationServerConfigurer<B> oidc(Customizer<OidcConfigurer> oi
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.
*
Expand Down Expand Up @@ -363,12 +376,6 @@ public void configure(B builder) {
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(
Expand All @@ -388,6 +395,7 @@ private Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer>
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;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
*
* @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>
Expand Down Expand Up @@ -257,6 +258,28 @@ public Builder claim(String name, Object value) {
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.
Expand Down Expand Up @@ -312,15 +335,6 @@ private void addClaimToClaimList(String name, String value) {
((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;
Expand All @@ -332,5 +346,14 @@ private static void validateURL(Object url, String errorMessage) {
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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
*
* @author Gerardo Roza
* @author Joe Grandja
* @author Gaurav Tiwari
* @since 0.1.1
* @see OAuth2TokenIntrospectionAuthenticationToken
* @see RegisteredClientRepository
Expand Down Expand Up @@ -143,6 +144,8 @@ private static OAuth2TokenIntrospection withActiveTokenClaims(
}
}

tokenClaims.withCustomClaims(authorizedToken.getClaims());

return tokenClaims.build();
}
}
Loading