Browse Source
The following commits are merged using the default merge strategy.pull/894/head70d433a45aUpdate ref-doc with OAuth2Authorization.getAuthorizedScopes()0994a1e1e1Allow customizing OIDC Provider Configuration Response8043b8c949Allow customizing Authorization Server Metadata Response4466cbe69dUse configured ID Token signature algorithm502fa24cfbPolish gh-78707d69cbfb4Validate client secret not expired2cc603c7e7Improve configurability for AuthenticationConverter and AuthenticationProvider1db05991afMake OAuth2AuthenticationContext an interfacec326b1a2baRemove OAuth2AuthenticationValidator
38 changed files with 1653 additions and 577 deletions
@ -1,40 +0,0 @@
@@ -1,40 +0,0 @@
|
||||
/* |
||||
* 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.oauth2.server.authorization.authentication; |
||||
|
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException; |
||||
|
||||
/** |
||||
* Implementations of this interface are responsible for validating the attribute(s) |
||||
* of the {@link Authentication} associated to the {@link OAuth2AuthenticationContext}. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 0.2.0 |
||||
* @see OAuth2AuthenticationContext |
||||
*/ |
||||
@FunctionalInterface |
||||
public interface OAuth2AuthenticationValidator { |
||||
|
||||
/** |
||||
* Validate the attribute(s) of the {@link Authentication}. |
||||
* |
||||
* @param authenticationContext the authentication context |
||||
* @throws OAuth2AuthenticationException if the attribute(s) of the {@code Authentication} is invalid |
||||
*/ |
||||
void validate(OAuth2AuthenticationContext authenticationContext) throws OAuth2AuthenticationException; |
||||
|
||||
} |
||||
@ -0,0 +1,107 @@
@@ -0,0 +1,107 @@
|
||||
/* |
||||
* 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.oauth2.server.authorization.authentication; |
||||
|
||||
import java.util.Collections; |
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
import java.util.function.Consumer; |
||||
|
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* An {@link OAuth2AuthenticationContext} that holds an {@link OAuth2AuthorizationCodeRequestAuthenticationToken} and additional information |
||||
* and is used when validating the OAuth 2.0 Authorization Request used in the Authorization Code Grant. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 0.4.0 |
||||
* @see OAuth2AuthenticationContext |
||||
* @see OAuth2AuthorizationCodeRequestAuthenticationToken |
||||
* @see OAuth2AuthorizationCodeRequestAuthenticationProvider#setAuthenticationValidator(Consumer) |
||||
*/ |
||||
public final class OAuth2AuthorizationCodeRequestAuthenticationContext implements OAuth2AuthenticationContext { |
||||
private final Map<Object, Object> context; |
||||
|
||||
private OAuth2AuthorizationCodeRequestAuthenticationContext(Map<Object, Object> context) { |
||||
this.context = Collections.unmodifiableMap(new HashMap<>(context)); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
@Nullable |
||||
@Override |
||||
public <V> V get(Object key) { |
||||
return hasKey(key) ? (V) this.context.get(key) : null; |
||||
} |
||||
|
||||
@Override |
||||
public boolean hasKey(Object key) { |
||||
Assert.notNull(key, "key cannot be null"); |
||||
return this.context.containsKey(key); |
||||
} |
||||
|
||||
/** |
||||
* Returns the {@link RegisteredClient registered client}. |
||||
* |
||||
* @return the {@link RegisteredClient} |
||||
*/ |
||||
public RegisteredClient getRegisteredClient() { |
||||
return get(RegisteredClient.class); |
||||
} |
||||
|
||||
/** |
||||
* Constructs a new {@link Builder} with the provided {@link OAuth2AuthorizationCodeRequestAuthenticationToken}. |
||||
* |
||||
* @param authentication the {@link OAuth2AuthorizationCodeRequestAuthenticationToken} |
||||
* @return the {@link Builder} |
||||
*/ |
||||
public static Builder with(OAuth2AuthorizationCodeRequestAuthenticationToken authentication) { |
||||
return new Builder(authentication); |
||||
} |
||||
|
||||
/** |
||||
* A builder for {@link OAuth2AuthorizationCodeRequestAuthenticationContext}. |
||||
*/ |
||||
public static final class Builder extends AbstractBuilder<OAuth2AuthorizationCodeRequestAuthenticationContext, Builder> { |
||||
|
||||
private Builder(OAuth2AuthorizationCodeRequestAuthenticationToken authentication) { |
||||
super(authentication); |
||||
} |
||||
|
||||
/** |
||||
* Sets the {@link RegisteredClient registered client}. |
||||
* |
||||
* @param registeredClient the {@link RegisteredClient} |
||||
* @return the {@link Builder} for further configuration |
||||
*/ |
||||
public Builder registeredClient(RegisteredClient registeredClient) { |
||||
return put(RegisteredClient.class, registeredClient); |
||||
} |
||||
|
||||
/** |
||||
* Builds a new {@link OAuth2AuthorizationCodeRequestAuthenticationContext}. |
||||
* |
||||
* @return the {@link OAuth2AuthorizationCodeRequestAuthenticationContext} |
||||
*/ |
||||
public OAuth2AuthorizationCodeRequestAuthenticationContext build() { |
||||
Assert.notNull(get(RegisteredClient.class), "registeredClient cannot be null"); |
||||
return new OAuth2AuthorizationCodeRequestAuthenticationContext(getContext()); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,226 @@
@@ -0,0 +1,226 @@
|
||||
/* |
||||
* 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.oauth2.server.authorization.authentication; |
||||
|
||||
import java.util.Set; |
||||
import java.util.function.Consumer; |
||||
|
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.oauth2.core.OAuth2Error; |
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; |
||||
import org.springframework.security.oauth2.core.oidc.OidcScopes; |
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; |
||||
import org.springframework.util.StringUtils; |
||||
import org.springframework.web.util.UriComponents; |
||||
import org.springframework.web.util.UriComponentsBuilder; |
||||
|
||||
/** |
||||
* A {@code Consumer} providing access to the {@link OAuth2AuthorizationCodeRequestAuthenticationContext} |
||||
* containing an {@link OAuth2AuthorizationCodeRequestAuthenticationToken} |
||||
* and is the default {@link OAuth2AuthorizationCodeRequestAuthenticationProvider#setAuthenticationValidator(Consumer) authentication validator} |
||||
* used for validating specific OAuth 2.0 Authorization Request parameters used in the Authorization Code Grant. |
||||
* |
||||
* <p> |
||||
* The default implementation first validates {@link OAuth2AuthorizationCodeRequestAuthenticationToken#getRedirectUri()} |
||||
* and then {@link OAuth2AuthorizationCodeRequestAuthenticationToken#getScopes()}. |
||||
* If validation fails, an {@link OAuth2AuthorizationCodeRequestAuthenticationException} is thrown. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 0.4.0 |
||||
* @see OAuth2AuthorizationCodeRequestAuthenticationContext |
||||
* @see OAuth2AuthorizationCodeRequestAuthenticationToken |
||||
* @see OAuth2AuthorizationCodeRequestAuthenticationProvider#setAuthenticationValidator(Consumer) |
||||
*/ |
||||
public final class OAuth2AuthorizationCodeRequestAuthenticationValidator implements Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> { |
||||
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1"; |
||||
|
||||
/** |
||||
* The default validator for {@link OAuth2AuthorizationCodeRequestAuthenticationToken#getScopes()}. |
||||
*/ |
||||
public static final Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> DEFAULT_SCOPE_VALIDATOR = |
||||
OAuth2AuthorizationCodeRequestAuthenticationValidator::validateScope; |
||||
|
||||
/** |
||||
* The default validator for {@link OAuth2AuthorizationCodeRequestAuthenticationToken#getRedirectUri()}. |
||||
*/ |
||||
public static final Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> DEFAULT_REDIRECT_URI_VALIDATOR = |
||||
OAuth2AuthorizationCodeRequestAuthenticationValidator::validateRedirectUri; |
||||
|
||||
private final Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authenticationValidator = |
||||
DEFAULT_REDIRECT_URI_VALIDATOR.andThen(DEFAULT_SCOPE_VALIDATOR); |
||||
|
||||
@Override |
||||
public void accept(OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext) { |
||||
this.authenticationValidator.accept(authenticationContext); |
||||
} |
||||
|
||||
private static void validateScope(OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext) { |
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = |
||||
authenticationContext.getAuthentication(); |
||||
RegisteredClient registeredClient = authenticationContext.getRegisteredClient(); |
||||
|
||||
Set<String> requestedScopes = authorizationCodeRequestAuthentication.getScopes(); |
||||
Set<String> allowedScopes = registeredClient.getScopes(); |
||||
if (!requestedScopes.isEmpty() && !allowedScopes.containsAll(requestedScopes)) { |
||||
throwError(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE, |
||||
authorizationCodeRequestAuthentication, registeredClient); |
||||
} |
||||
} |
||||
|
||||
private static void validateRedirectUri(OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext) { |
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = |
||||
authenticationContext.getAuthentication(); |
||||
RegisteredClient registeredClient = authenticationContext.getRegisteredClient(); |
||||
|
||||
String requestedRedirectUri = authorizationCodeRequestAuthentication.getRedirectUri(); |
||||
|
||||
if (StringUtils.hasText(requestedRedirectUri)) { |
||||
// ***** redirect_uri is available in authorization request
|
||||
|
||||
UriComponents requestedRedirect = null; |
||||
try { |
||||
requestedRedirect = UriComponentsBuilder.fromUriString(requestedRedirectUri).build(); |
||||
} catch (Exception ex) { } |
||||
if (requestedRedirect == null || requestedRedirect.getFragment() != null) { |
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI, |
||||
authorizationCodeRequestAuthentication, registeredClient); |
||||
} |
||||
|
||||
String requestedRedirectHost = requestedRedirect.getHost(); |
||||
if (requestedRedirectHost == null || requestedRedirectHost.equals("localhost")) { |
||||
// As per https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-01#section-9.7.1
|
||||
// While redirect URIs using localhost (i.e., "http://localhost:{port}/{path}")
|
||||
// function similarly to loopback IP redirects described in Section 10.3.3,
|
||||
// the use of "localhost" is NOT RECOMMENDED.
|
||||
OAuth2Error error = new OAuth2Error( |
||||
OAuth2ErrorCodes.INVALID_REQUEST, |
||||
"localhost is not allowed for the redirect_uri (" + requestedRedirectUri + "). " + |
||||
"Use the IP literal (127.0.0.1) instead.", |
||||
"https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-01#section-9.7.1"); |
||||
throwError(error, OAuth2ParameterNames.REDIRECT_URI, |
||||
authorizationCodeRequestAuthentication, registeredClient); |
||||
} |
||||
|
||||
if (!isLoopbackAddress(requestedRedirectHost)) { |
||||
// As per https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-01#section-9.7
|
||||
// When comparing client redirect URIs against pre-registered URIs,
|
||||
// authorization servers MUST utilize exact string matching.
|
||||
if (!registeredClient.getRedirectUris().contains(requestedRedirectUri)) { |
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI, |
||||
authorizationCodeRequestAuthentication, registeredClient); |
||||
} |
||||
} else { |
||||
// As per https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-01#section-10.3.3
|
||||
// The authorization server MUST allow any port to be specified at the
|
||||
// time of the request for loopback IP redirect URIs, to accommodate
|
||||
// clients that obtain an available ephemeral port from the operating
|
||||
// system at the time of the request.
|
||||
boolean validRedirectUri = false; |
||||
for (String registeredRedirectUri : registeredClient.getRedirectUris()) { |
||||
UriComponentsBuilder registeredRedirect = UriComponentsBuilder.fromUriString(registeredRedirectUri); |
||||
registeredRedirect.port(requestedRedirect.getPort()); |
||||
if (registeredRedirect.build().toString().equals(requestedRedirect.toString())) { |
||||
validRedirectUri = true; |
||||
break; |
||||
} |
||||
} |
||||
if (!validRedirectUri) { |
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI, |
||||
authorizationCodeRequestAuthentication, registeredClient); |
||||
} |
||||
} |
||||
|
||||
} else { |
||||
// ***** redirect_uri is NOT available in authorization request
|
||||
|
||||
if (authorizationCodeRequestAuthentication.getScopes().contains(OidcScopes.OPENID) || |
||||
registeredClient.getRedirectUris().size() != 1) { |
||||
// redirect_uri is REQUIRED for OpenID Connect
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI, |
||||
authorizationCodeRequestAuthentication, registeredClient); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private static boolean isLoopbackAddress(String host) { |
||||
// IPv6 loopback address should either be "0:0:0:0:0:0:0:1" or "::1"
|
||||
if ("[0:0:0:0:0:0:0:1]".equals(host) || "[::1]".equals(host)) { |
||||
return true; |
||||
} |
||||
// IPv4 loopback address ranges from 127.0.0.1 to 127.255.255.255
|
||||
String[] ipv4Octets = host.split("\\."); |
||||
if (ipv4Octets.length != 4) { |
||||
return false; |
||||
} |
||||
try { |
||||
int[] address = new int[ipv4Octets.length]; |
||||
for (int i=0; i < ipv4Octets.length; i++) { |
||||
address[i] = Integer.parseInt(ipv4Octets[i]); |
||||
} |
||||
return address[0] == 127 && address[1] >= 0 && address[1] <= 255 && address[2] >= 0 && |
||||
address[2] <= 255 && address[3] >= 1 && address[3] <= 255; |
||||
} catch (NumberFormatException ex) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
private static void throwError(String errorCode, String parameterName, |
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication, |
||||
RegisteredClient registeredClient) { |
||||
OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, ERROR_URI); |
||||
throwError(error, parameterName, authorizationCodeRequestAuthentication, registeredClient); |
||||
} |
||||
|
||||
private static void throwError(OAuth2Error error, String parameterName, |
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication, |
||||
RegisteredClient registeredClient) { |
||||
|
||||
boolean redirectOnError = true; |
||||
if (error.getErrorCode().equals(OAuth2ErrorCodes.INVALID_REQUEST) && |
||||
parameterName.equals(OAuth2ParameterNames.REDIRECT_URI)) { |
||||
redirectOnError = false; |
||||
} |
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = authorizationCodeRequestAuthentication; |
||||
|
||||
if (redirectOnError && !StringUtils.hasText(authorizationCodeRequestAuthentication.getRedirectUri())) { |
||||
String redirectUri = registeredClient.getRedirectUris().iterator().next(); |
||||
authorizationCodeRequestAuthenticationResult = from(authorizationCodeRequestAuthentication) |
||||
.redirectUri(redirectUri) |
||||
.build(); |
||||
} else if (!redirectOnError && StringUtils.hasText(authorizationCodeRequestAuthentication.getRedirectUri())) { |
||||
authorizationCodeRequestAuthenticationResult = from(authorizationCodeRequestAuthentication) |
||||
.redirectUri(null) // Prevent redirects
|
||||
.build(); |
||||
} |
||||
|
||||
authorizationCodeRequestAuthenticationResult.setAuthenticated(authorizationCodeRequestAuthentication.isAuthenticated()); |
||||
|
||||
throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, authorizationCodeRequestAuthenticationResult); |
||||
} |
||||
|
||||
private static OAuth2AuthorizationCodeRequestAuthenticationToken.Builder from(OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication) { |
||||
return OAuth2AuthorizationCodeRequestAuthenticationToken.with(authorizationCodeRequestAuthentication.getClientId(), (Authentication) authorizationCodeRequestAuthentication.getPrincipal()) |
||||
.authorizationUri(authorizationCodeRequestAuthentication.getAuthorizationUri()) |
||||
.redirectUri(authorizationCodeRequestAuthentication.getRedirectUri()) |
||||
.scopes(authorizationCodeRequestAuthentication.getScopes()) |
||||
.state(authorizationCodeRequestAuthentication.getState()) |
||||
.additionalParameters(authorizationCodeRequestAuthentication.getAdditionalParameters()) |
||||
.authorizationCode(authorizationCodeRequestAuthentication.getAuthorizationCode()); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,108 @@
@@ -0,0 +1,108 @@
|
||||
/* |
||||
* 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.oauth2.server.authorization.config.annotation.web.configurers; |
||||
|
||||
import java.util.function.Consumer; |
||||
|
||||
import org.springframework.http.HttpMethod; |
||||
import org.springframework.security.config.annotation.ObjectPostProcessor; |
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity; |
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadata; |
||||
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerMetadataEndpointFilter; |
||||
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; |
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; |
||||
import org.springframework.security.web.util.matcher.RequestMatcher; |
||||
|
||||
/** |
||||
* Configurer for the OAuth 2.0 Authorization Server Metadata Endpoint. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 0.4.0 |
||||
* @see OAuth2AuthorizationServerConfigurer#authorizationServerMetadataEndpoint |
||||
* @see OAuth2AuthorizationServerMetadataEndpointFilter |
||||
*/ |
||||
public final class OAuth2AuthorizationServerMetadataEndpointConfigurer extends AbstractOAuth2Configurer { |
||||
private RequestMatcher requestMatcher; |
||||
private Consumer<OAuth2AuthorizationServerMetadata.Builder> authorizationServerMetadataCustomizer; |
||||
private Consumer<OAuth2AuthorizationServerMetadata.Builder> defaultAuthorizationServerMetadataCustomizer; |
||||
|
||||
/** |
||||
* Restrict for internal use only. |
||||
*/ |
||||
OAuth2AuthorizationServerMetadataEndpointConfigurer(ObjectPostProcessor<Object> objectPostProcessor) { |
||||
super(objectPostProcessor); |
||||
} |
||||
|
||||
/** |
||||
* Sets the {@code Consumer} providing access to the {@link OAuth2AuthorizationServerMetadata.Builder} |
||||
* allowing the ability to customize the claims of the Authorization Server's configuration. |
||||
* |
||||
* @param authorizationServerMetadataCustomizer the {@code Consumer} providing access to the {@link OAuth2AuthorizationServerMetadata.Builder} |
||||
* @return the {@link OAuth2AuthorizationServerMetadataEndpointConfigurer} for further configuration |
||||
*/ |
||||
public OAuth2AuthorizationServerMetadataEndpointConfigurer authorizationServerMetadataCustomizer( |
||||
Consumer<OAuth2AuthorizationServerMetadata.Builder> authorizationServerMetadataCustomizer) { |
||||
this.authorizationServerMetadataCustomizer = authorizationServerMetadataCustomizer; |
||||
return this; |
||||
} |
||||
|
||||
void addDefaultAuthorizationServerMetadataCustomizer( |
||||
Consumer<OAuth2AuthorizationServerMetadata.Builder> defaultAuthorizationServerMetadataCustomizer) { |
||||
this.defaultAuthorizationServerMetadataCustomizer = |
||||
this.defaultAuthorizationServerMetadataCustomizer == null ? |
||||
defaultAuthorizationServerMetadataCustomizer : |
||||
this.defaultAuthorizationServerMetadataCustomizer.andThen(defaultAuthorizationServerMetadataCustomizer); |
||||
} |
||||
|
||||
@Override |
||||
void init(HttpSecurity httpSecurity) { |
||||
this.requestMatcher = new AntPathRequestMatcher( |
||||
"/.well-known/oauth-authorization-server", HttpMethod.GET.name()); |
||||
} |
||||
|
||||
@Override |
||||
void configure(HttpSecurity httpSecurity) { |
||||
OAuth2AuthorizationServerMetadataEndpointFilter authorizationServerMetadataEndpointFilter = |
||||
new OAuth2AuthorizationServerMetadataEndpointFilter(); |
||||
Consumer<OAuth2AuthorizationServerMetadata.Builder> authorizationServerMetadataCustomizer = getAuthorizationServerMetadataCustomizer(); |
||||
if (authorizationServerMetadataCustomizer != null) { |
||||
authorizationServerMetadataEndpointFilter.setAuthorizationServerMetadataCustomizer(authorizationServerMetadataCustomizer); |
||||
} |
||||
httpSecurity.addFilterBefore(postProcess(authorizationServerMetadataEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class); |
||||
} |
||||
|
||||
private Consumer<OAuth2AuthorizationServerMetadata.Builder> getAuthorizationServerMetadataCustomizer() { |
||||
Consumer<OAuth2AuthorizationServerMetadata.Builder> authorizationServerMetadataCustomizer = null; |
||||
if (this.defaultAuthorizationServerMetadataCustomizer != null || this.authorizationServerMetadataCustomizer != null) { |
||||
if (this.defaultAuthorizationServerMetadataCustomizer != null) { |
||||
authorizationServerMetadataCustomizer = this.defaultAuthorizationServerMetadataCustomizer; |
||||
} |
||||
if (this.authorizationServerMetadataCustomizer != null) { |
||||
authorizationServerMetadataCustomizer = |
||||
authorizationServerMetadataCustomizer == null ? |
||||
this.authorizationServerMetadataCustomizer : |
||||
authorizationServerMetadataCustomizer.andThen(this.authorizationServerMetadataCustomizer); |
||||
} |
||||
} |
||||
return authorizationServerMetadataCustomizer; |
||||
} |
||||
|
||||
@Override |
||||
RequestMatcher getRequestMatcher() { |
||||
return this.requestMatcher; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,108 @@
@@ -0,0 +1,108 @@
|
||||
/* |
||||
* 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.oauth2.server.authorization.config.annotation.web.configurers; |
||||
|
||||
import java.util.function.Consumer; |
||||
|
||||
import org.springframework.http.HttpMethod; |
||||
import org.springframework.security.config.annotation.ObjectPostProcessor; |
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity; |
||||
import org.springframework.security.oauth2.server.authorization.oidc.OidcProviderConfiguration; |
||||
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcProviderConfigurationEndpointFilter; |
||||
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; |
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; |
||||
import org.springframework.security.web.util.matcher.RequestMatcher; |
||||
|
||||
/** |
||||
* Configurer for the OpenID Connect 1.0 Provider Configuration Endpoint. |
||||
* |
||||
* @author Joe Grandja |
||||
* @since 0.4.0 |
||||
* @see OidcConfigurer#providerConfigurationEndpoint |
||||
* @see OidcProviderConfigurationEndpointFilter |
||||
*/ |
||||
public final class OidcProviderConfigurationEndpointConfigurer extends AbstractOAuth2Configurer { |
||||
private RequestMatcher requestMatcher; |
||||
private Consumer<OidcProviderConfiguration.Builder> providerConfigurationCustomizer; |
||||
private Consumer<OidcProviderConfiguration.Builder> defaultProviderConfigurationCustomizer; |
||||
|
||||
/** |
||||
* Restrict for internal use only. |
||||
*/ |
||||
OidcProviderConfigurationEndpointConfigurer(ObjectPostProcessor<Object> objectPostProcessor) { |
||||
super(objectPostProcessor); |
||||
} |
||||
|
||||
/** |
||||
* Sets the {@code Consumer} providing access to the {@link OidcProviderConfiguration.Builder} |
||||
* allowing the ability to customize the claims of the OpenID Provider's configuration. |
||||
* |
||||
* @param providerConfigurationCustomizer the {@code Consumer} providing access to the {@link OidcProviderConfiguration.Builder} |
||||
* @return the {@link OidcProviderConfigurationEndpointConfigurer} for further configuration |
||||
*/ |
||||
public OidcProviderConfigurationEndpointConfigurer providerConfigurationCustomizer( |
||||
Consumer<OidcProviderConfiguration.Builder> providerConfigurationCustomizer) { |
||||
this.providerConfigurationCustomizer = providerConfigurationCustomizer; |
||||
return this; |
||||
} |
||||
|
||||
void addDefaultProviderConfigurationCustomizer( |
||||
Consumer<OidcProviderConfiguration.Builder> defaultProviderConfigurationCustomizer) { |
||||
this.defaultProviderConfigurationCustomizer = |
||||
this.defaultProviderConfigurationCustomizer == null ? |
||||
defaultProviderConfigurationCustomizer : |
||||
this.defaultProviderConfigurationCustomizer.andThen(defaultProviderConfigurationCustomizer); |
||||
} |
||||
|
||||
@Override |
||||
void init(HttpSecurity httpSecurity) { |
||||
this.requestMatcher = new AntPathRequestMatcher( |
||||
"/.well-known/openid-configuration", HttpMethod.GET.name()); |
||||
} |
||||
|
||||
@Override |
||||
void configure(HttpSecurity httpSecurity) { |
||||
OidcProviderConfigurationEndpointFilter oidcProviderConfigurationEndpointFilter = |
||||
new OidcProviderConfigurationEndpointFilter(); |
||||
Consumer<OidcProviderConfiguration.Builder> providerConfigurationCustomizer = getProviderConfigurationCustomizer(); |
||||
if (providerConfigurationCustomizer != null) { |
||||
oidcProviderConfigurationEndpointFilter.setProviderConfigurationCustomizer(providerConfigurationCustomizer); |
||||
} |
||||
httpSecurity.addFilterBefore(postProcess(oidcProviderConfigurationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class); |
||||
} |
||||
|
||||
private Consumer<OidcProviderConfiguration.Builder> getProviderConfigurationCustomizer() { |
||||
Consumer<OidcProviderConfiguration.Builder> providerConfigurationCustomizer = null; |
||||
if (this.defaultProviderConfigurationCustomizer != null || this.providerConfigurationCustomizer != null) { |
||||
if (this.defaultProviderConfigurationCustomizer != null) { |
||||
providerConfigurationCustomizer = this.defaultProviderConfigurationCustomizer; |
||||
} |
||||
if (this.providerConfigurationCustomizer != null) { |
||||
providerConfigurationCustomizer = |
||||
providerConfigurationCustomizer == null ? |
||||
this.providerConfigurationCustomizer : |
||||
providerConfigurationCustomizer.andThen(this.providerConfigurationCustomizer); |
||||
} |
||||
} |
||||
return providerConfigurationCustomizer; |
||||
} |
||||
|
||||
@Override |
||||
RequestMatcher getRequestMatcher() { |
||||
return this.requestMatcher; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,86 @@
@@ -0,0 +1,86 @@
|
||||
/* |
||||
* 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.oauth2.server.authorization.web.authentication; |
||||
|
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
|
||||
import javax.servlet.http.HttpServletRequest; |
||||
|
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.core.context.SecurityContextHolder; |
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException; |
||||
import org.springframework.security.oauth2.core.OAuth2Error; |
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; |
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationToken; |
||||
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenIntrospectionEndpointFilter; |
||||
import org.springframework.security.web.authentication.AuthenticationConverter; |
||||
import org.springframework.util.MultiValueMap; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* Attempts to extract an Introspection Request from {@link HttpServletRequest} |
||||
* and then converts it to an {@link OAuth2TokenIntrospectionAuthenticationToken} used for authenticating the request. |
||||
* |
||||
* @author Gerardo Roza |
||||
* @author Joe Grandja |
||||
* @since 0.4.0 |
||||
* @see AuthenticationConverter |
||||
* @see OAuth2TokenIntrospectionAuthenticationToken |
||||
* @see OAuth2TokenIntrospectionEndpointFilter |
||||
*/ |
||||
public final class OAuth2TokenIntrospectionAuthenticationConverter implements AuthenticationConverter { |
||||
|
||||
@Override |
||||
public Authentication convert(HttpServletRequest request) { |
||||
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); |
||||
|
||||
MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request); |
||||
|
||||
// token (REQUIRED)
|
||||
String token = parameters.getFirst(OAuth2ParameterNames.TOKEN); |
||||
if (!StringUtils.hasText(token) || |
||||
parameters.get(OAuth2ParameterNames.TOKEN).size() != 1) { |
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.TOKEN); |
||||
} |
||||
|
||||
// token_type_hint (OPTIONAL)
|
||||
String tokenTypeHint = parameters.getFirst(OAuth2ParameterNames.TOKEN_TYPE_HINT); |
||||
if (StringUtils.hasText(tokenTypeHint) && |
||||
parameters.get(OAuth2ParameterNames.TOKEN_TYPE_HINT).size() != 1) { |
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.TOKEN_TYPE_HINT); |
||||
} |
||||
|
||||
Map<String, Object> additionalParameters = new HashMap<>(); |
||||
parameters.forEach((key, value) -> { |
||||
if (!key.equals(OAuth2ParameterNames.TOKEN) && |
||||
!key.equals(OAuth2ParameterNames.TOKEN_TYPE_HINT)) { |
||||
additionalParameters.put(key, value.get(0)); |
||||
} |
||||
}); |
||||
|
||||
return new OAuth2TokenIntrospectionAuthenticationToken( |
||||
token, clientPrincipal, tokenTypeHint, additionalParameters); |
||||
} |
||||
|
||||
private static void throwError(String errorCode, String parameterName) { |
||||
OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Token Introspection Parameter: " + parameterName, |
||||
"https://datatracker.ietf.org/doc/html/rfc7662#section-2.1"); |
||||
throw new OAuth2AuthenticationException(error); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,74 @@
@@ -0,0 +1,74 @@
|
||||
/* |
||||
* 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.oauth2.server.authorization.web.authentication; |
||||
|
||||
import javax.servlet.http.HttpServletRequest; |
||||
|
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.core.context.SecurityContextHolder; |
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException; |
||||
import org.springframework.security.oauth2.core.OAuth2Error; |
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; |
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationToken; |
||||
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenRevocationEndpointFilter; |
||||
import org.springframework.security.web.authentication.AuthenticationConverter; |
||||
import org.springframework.util.MultiValueMap; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* Attempts to extract a Revoke Token Request from {@link HttpServletRequest} |
||||
* and then converts it to an {@link OAuth2TokenRevocationAuthenticationToken} used for authenticating the request. |
||||
* |
||||
* @author Vivek Babu |
||||
* @author Joe Grandja |
||||
* @since 0.4.0 |
||||
* @see AuthenticationConverter |
||||
* @see OAuth2TokenRevocationAuthenticationToken |
||||
* @see OAuth2TokenRevocationEndpointFilter |
||||
*/ |
||||
public final class OAuth2TokenRevocationAuthenticationConverter implements AuthenticationConverter { |
||||
|
||||
@Override |
||||
public Authentication convert(HttpServletRequest request) { |
||||
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); |
||||
|
||||
MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request); |
||||
|
||||
// token (REQUIRED)
|
||||
String token = parameters.getFirst(OAuth2ParameterNames.TOKEN); |
||||
if (!StringUtils.hasText(token) || |
||||
parameters.get(OAuth2ParameterNames.TOKEN).size() != 1) { |
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.TOKEN); |
||||
} |
||||
|
||||
// token_type_hint (OPTIONAL)
|
||||
String tokenTypeHint = parameters.getFirst(OAuth2ParameterNames.TOKEN_TYPE_HINT); |
||||
if (StringUtils.hasText(tokenTypeHint) && |
||||
parameters.get(OAuth2ParameterNames.TOKEN_TYPE_HINT).size() != 1) { |
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.TOKEN_TYPE_HINT); |
||||
} |
||||
|
||||
return new OAuth2TokenRevocationAuthenticationToken(token, clientPrincipal, tokenTypeHint); |
||||
} |
||||
|
||||
private static void throwError(String errorCode, String parameterName) { |
||||
OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Token Revocation Parameter: " + parameterName, |
||||
"https://datatracker.ietf.org/doc/html/rfc7009#section-2.1"); |
||||
throw new OAuth2AuthenticationException(error); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue