Polish gh-167
This commit is contained in:
@@ -49,7 +49,7 @@ import org.springframework.security.oauth2.server.authorization.config.ProviderS
|
||||
import org.springframework.security.oauth2.server.authorization.oidc.web.OidcProviderConfigurationEndpointFilter;
|
||||
import org.springframework.security.oauth2.server.authorization.web.NimbusJwkSetEndpointFilter;
|
||||
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
|
||||
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerConfigurationEndpointFilter;
|
||||
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerMetadataEndpointFilter;
|
||||
import org.springframework.security.oauth2.server.authorization.web.OAuth2ClientAuthenticationFilter;
|
||||
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter;
|
||||
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenIntrospectionEndpointFilter;
|
||||
@@ -79,6 +79,7 @@ import org.springframework.util.StringUtils;
|
||||
* @see OAuth2TokenRevocationEndpointFilter
|
||||
* @see NimbusJwkSetEndpointFilter
|
||||
* @see OidcProviderConfigurationEndpointFilter
|
||||
* @see OAuth2AuthorizationServerMetadataEndpointFilter
|
||||
* @see OAuth2ClientAuthenticationFilter
|
||||
*/
|
||||
public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBuilder<B>>
|
||||
@@ -90,7 +91,7 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
|
||||
private RequestMatcher tokenRevocationEndpointMatcher;
|
||||
private RequestMatcher jwkSetEndpointMatcher;
|
||||
private RequestMatcher oidcProviderConfigurationEndpointMatcher;
|
||||
private RequestMatcher oauth2ServerConfigurationEndpointMatcher;
|
||||
private RequestMatcher authorizationServerMetadataEndpointMatcher;
|
||||
private final RequestMatcher endpointsMatcher = (request) ->
|
||||
this.authorizationEndpointMatcher.matches(request) ||
|
||||
this.tokenEndpointMatcher.matches(request) ||
|
||||
@@ -98,7 +99,7 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
|
||||
this.tokenRevocationEndpointMatcher.matches(request) ||
|
||||
this.jwkSetEndpointMatcher.matches(request) ||
|
||||
this.oidcProviderConfigurationEndpointMatcher.matches(request) ||
|
||||
this.oauth2ServerConfigurationEndpointMatcher.matches(request);
|
||||
this.authorizationServerMetadataEndpointMatcher.matches(request);
|
||||
|
||||
/**
|
||||
* Sets the repository of registered clients.
|
||||
@@ -218,9 +219,9 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
|
||||
new OidcProviderConfigurationEndpointFilter(providerSettings);
|
||||
builder.addFilterBefore(postProcess(oidcProviderConfigurationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
|
||||
|
||||
OAuth2AuthorizationServerConfigurationEndpointFilter authorizationServerConfigurationFilter
|
||||
= new OAuth2AuthorizationServerConfigurationEndpointFilter(providerSettings);
|
||||
builder.addFilterBefore(postProcess(authorizationServerConfigurationFilter), AbstractPreAuthenticatedProcessingFilter.class);
|
||||
OAuth2AuthorizationServerMetadataEndpointFilter authorizationServerMetadataEndpointFilter =
|
||||
new OAuth2AuthorizationServerMetadataEndpointFilter(providerSettings);
|
||||
builder.addFilterBefore(postProcess(authorizationServerMetadataEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
|
||||
}
|
||||
|
||||
JWKSource<SecurityContext> jwkSource = getJwkSource(builder);
|
||||
@@ -284,8 +285,8 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
|
||||
providerSettings.jwkSetEndpoint(), HttpMethod.GET.name());
|
||||
this.oidcProviderConfigurationEndpointMatcher = new AntPathRequestMatcher(
|
||||
OidcProviderConfigurationEndpointFilter.DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI, HttpMethod.GET.name());
|
||||
this.oauth2ServerConfigurationEndpointMatcher = new AntPathRequestMatcher(
|
||||
OAuth2AuthorizationServerConfigurationEndpointFilter.DEFAULT_OAUTH2_AUTHORIZATION_SERVER_CONFIGURATION_ENDPOINT_URI, HttpMethod.GET.name());
|
||||
this.authorizationServerMetadataEndpointMatcher = new AntPathRequestMatcher(
|
||||
OAuth2AuthorizationServerMetadataEndpointFilter.DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI, HttpMethod.GET.name());
|
||||
}
|
||||
|
||||
private static void validateProviderSettings(ProviderSettings providerSettings) {
|
||||
|
||||
@@ -1,370 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 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.core;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
/**
|
||||
* A base representation of a Provider Configuration response, returned by an endpoint defined
|
||||
* either in OpenID Connect Discovery 1.0 or OAuth 2.0 Authorization Server Metadata.
|
||||
* It contains a set of claims about the Provider's configuration.
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
* @see OAuth2AuthorizationServerMetadataClaimAccessor
|
||||
* @since 0.1.1
|
||||
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse">4.2. OpenID Provider Configuration Response</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc8414#section-3.2">3.2. Authorization Server Metadata Response</a>
|
||||
*/
|
||||
public abstract class AbstractOAuth2AuthorizationServerConfiguration implements OAuth2AuthorizationServerMetadataClaimAccessor, Serializable {
|
||||
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||
|
||||
protected final Map<String, Object> claims;
|
||||
|
||||
protected AbstractOAuth2AuthorizationServerConfiguration(Map<String, Object> claims) {
|
||||
Assert.notEmpty(claims, "claims cannot be empty");
|
||||
this.claims = Collections.unmodifiableMap(new LinkedHashMap<>(claims));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Authorization Server metadata.
|
||||
*
|
||||
* @return a {@code Map} of the metadata values
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> getClaims() {
|
||||
return this.claims;
|
||||
}
|
||||
|
||||
/**
|
||||
* An abstract builder for subclasses of {@link AbstractOAuth2AuthorizationServerConfiguration}.
|
||||
*/
|
||||
protected static abstract class AbstractBuilder<T extends AbstractOAuth2AuthorizationServerConfiguration, B extends AbstractBuilder<T, B>> {
|
||||
|
||||
protected final Map<String, Object> claims = new LinkedHashMap<>();
|
||||
|
||||
protected AbstractBuilder() {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected B getThis() { return (B) this; }; // avoid unchecked casts in subclasses by using "getThis()" instead of "(B) this"
|
||||
|
||||
/**
|
||||
* Use this {@code issuer} in the resulting {@link AbstractOAuth2AuthorizationServerConfiguration}, REQUIRED.
|
||||
*
|
||||
* @param issuer the {@code URL} of the Authorization Server's Issuer Identifier
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B issuer(String issuer) {
|
||||
return claim(OAuth2AuthorizationServerMetadataClaimNames.ISSUER, issuer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@code authorization_endpoint} in the resulting {@link AbstractOAuth2AuthorizationServerConfiguration}, REQUIRED.
|
||||
*
|
||||
* @param authorizationEndpoint the {@code URL} of the Authorization Server's OAuth 2.0 Authorization Endpoint
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B authorizationEndpoint(String authorizationEndpoint) {
|
||||
return claim(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT, authorizationEndpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@code token_endpoint} in the resulting {@link AbstractOAuth2AuthorizationServerConfiguration}, REQUIRED.
|
||||
*
|
||||
* @param tokenEndpoint the {@code URL} of the Authorization Server's OAuth 2.0 Token Endpoint
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B tokenEndpoint(String tokenEndpoint) {
|
||||
return claim(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT, tokenEndpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this Authentication Method to the collection of {@code token_endpoint_auth_methods_supported}
|
||||
* in the resulting {@link AbstractOAuth2AuthorizationServerConfiguration}, OPTIONAL.
|
||||
*
|
||||
* @param authenticationMethod the OAuth 2.0 Authentication Method supported by the Token Endpoint
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B tokenEndpointAuthenticationMethod(String authenticationMethod) {
|
||||
addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, authenticationMethod);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} of the Token Endpoint Authentication Method(s) allowing the ability to add, replace, or remove.
|
||||
*
|
||||
* @param authenticationMethodsConsumer a {@code Consumer} of the Token Endpoint Authentication Method(s)
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B tokenEndpointAuthenticationMethods(Consumer<List<String>> authenticationMethodsConsumer) {
|
||||
acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, authenticationMethodsConsumer);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@code jwks_uri} in the resulting {@link AbstractOAuth2AuthorizationServerConfiguration}, REQUIRED.
|
||||
*
|
||||
* @param jwkSetUri the {@code URL} of the Authorization Server's JSON Web Key Set document
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B jwkSetUri(String jwkSetUri) {
|
||||
return claim(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI, jwkSetUri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this Response Type to the collection of {@code response_types_supported} in the resulting
|
||||
* {@link AbstractOAuth2AuthorizationServerConfiguration}.
|
||||
*
|
||||
* @param responseType the OAuth 2.0 {@code response_type} value that the Authorization Server supports
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B responseType(String responseType) {
|
||||
addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, responseType);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} of the Response Type(s) allowing the ability to add, replace, or remove.
|
||||
*
|
||||
* @param responseTypesConsumer a {@code Consumer} of the Response Type(s)
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B responseTypes(Consumer<List<String>> responseTypesConsumer) {
|
||||
acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, responseTypesConsumer);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this Grant Type to the collection of {@code grant_types_supported} in the resulting
|
||||
* {@link AbstractOAuth2AuthorizationServerConfiguration}, OPTIONAL.
|
||||
*
|
||||
* @param grantType the OAuth 2.0 {@code grant_type} value that the Authorization Server supports
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B grantType(String grantType) {
|
||||
addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED, grantType);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} of the Grant Type(s) allowing the ability to add, replace, or remove.
|
||||
*
|
||||
* @param grantTypesConsumer a {@code Consumer} of the Grant Type(s)
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B grantTypes(Consumer<List<String>> grantTypesConsumer) {
|
||||
acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED, grantTypesConsumer);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this Scope to the collection of {@code scopes_supported} in the resulting
|
||||
* {@link AbstractOAuth2AuthorizationServerConfiguration}, RECOMMENDED.
|
||||
*
|
||||
* @param scope the OAuth 2.0 {@code scope} value that the Authorization Server supports
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B scope(String scope) {
|
||||
addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, scope);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} of the Scopes(s) allowing the ability to add, replace, or remove.
|
||||
*
|
||||
* @param scopesConsumer a {@code Consumer} of the Scopes(s)
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B scopes(Consumer<List<String>> scopesConsumer) {
|
||||
acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, scopesConsumer);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this claim in the resulting {@link AbstractOAuth2AuthorizationServerConfiguration}
|
||||
*
|
||||
* @param name the claim name
|
||||
* @param value the claim value
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B claim(String name, Object value) {
|
||||
Assert.hasText(name, "name cannot be empty");
|
||||
Assert.notNull(value, "value cannot be null");
|
||||
this.claims.put(name, value);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to every {@link #claim(String, Object)} declared so far with
|
||||
* the possibility to add, replace, or remove.
|
||||
*
|
||||
* @param claimsConsumer a {@code Consumer} of the claims
|
||||
* @return the {@link AbstractBuilder} for further configurations
|
||||
*/
|
||||
public B claims(Consumer<Map<String, Object>> claimsConsumer) {
|
||||
claimsConsumer.accept(this.claims);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@code revocation_endpoint} in the resulting {@link AbstractOAuth2AuthorizationServerConfiguration}, OPTIONAL.
|
||||
*
|
||||
* @param tokenRevocationEndpoint the {@code URL} of the OAuth 2.0 Authorization Server's Token Revocation Endpoint
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B tokenRevocationEndpoint(String tokenRevocationEndpoint) {
|
||||
return claim(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT, tokenRevocationEndpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this Authentication Method to the collection of {@code revocation_endpoint_auth_methods_supported}
|
||||
* in the resulting {@link AbstractOAuth2AuthorizationServerConfiguration}, OPTIONAL.
|
||||
*
|
||||
* @param authenticationMethod the OAuth 2.0 Authentication Method supported by the Revocation Endpoint
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B tokenRevocationEndpointAuthenticationMethod(String authenticationMethod) {
|
||||
addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED, authenticationMethod);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} of the Token Revocation Endpoint Authentication Method(s) allowing the ability to add,
|
||||
* replace, or remove.
|
||||
*
|
||||
* @param authenticationMethodsConsumer a {@code Consumer} of the OAuth 2.0 Token Revocation Endpoint Authentication Method(s)
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B tokenRevocationEndpointAuthenticationMethods(Consumer<List<String>> authenticationMethodsConsumer) {
|
||||
acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED, authenticationMethodsConsumer);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this Proof Key for Code Exchange (PKCE) Code Challenge Method to the collection of
|
||||
* {@code code_challenge_methods_supported} in the resulting {@link AbstractOAuth2AuthorizationServerConfiguration}, OPTIONAL.
|
||||
*
|
||||
* @param codeChallengeMethod the Proof Key for Code Exchange (PKCE) Code Challenge Method
|
||||
* supported by the Authorization Server
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B codeChallengeMethod(String codeChallengeMethod) {
|
||||
addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED, codeChallengeMethod);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} of the Proof Key for Code Exchange (PKCE) Code Challenge Method(s) allowing
|
||||
* the ability to add, replace, or remove.
|
||||
*
|
||||
* @param codeChallengeMethodsConsumer a {@code Consumer} of the Proof Key for Code Exchange (PKCE)
|
||||
* Code Challenge Method(s)
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B codeChallengeMethods(Consumer<List<String>> codeChallengeMethodsConsumer) {
|
||||
acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED, codeChallengeMethodsConsumer);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the {@link AbstractOAuth2AuthorizationServerConfiguration}.
|
||||
*
|
||||
* @return the {@link AbstractOAuth2AuthorizationServerConfiguration}
|
||||
*/
|
||||
public abstract T build();
|
||||
|
||||
protected void validateCommonClaims() {
|
||||
Assert.notNull(this.claims.get(OAuth2AuthorizationServerMetadataClaimNames.ISSUER), "issuer cannot be null");
|
||||
validateURL(this.claims.get(OAuth2AuthorizationServerMetadataClaimNames.ISSUER), "issuer must be a valid URL");
|
||||
Assert.notNull(this.claims.get(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT), "authorizationEndpoint cannot be null");
|
||||
validateURL(this.claims.get(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT), "authorizationEndpoint must be a valid URL");
|
||||
Assert.notNull(this.claims.get(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT), "tokenEndpoint cannot be null");
|
||||
validateURL(this.claims.get(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT), "tokenEndpoint must be a valid URL");
|
||||
Assert.notNull(this.claims.get(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI), "jwksUri cannot be null");
|
||||
validateURL(this.claims.get(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI), "jwksUri must be a valid URL");
|
||||
Assert.notNull(this.claims.get(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED), "responseTypes cannot be null");
|
||||
Assert.isInstanceOf(List.class, this.claims.get(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED), "responseTypes must be of type List");
|
||||
Assert.notEmpty((List<?>) this.claims.get(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED), "responseTypes cannot be empty");
|
||||
if (this.claims.get(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT) != null) {
|
||||
validateURL(this.claims.get(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT), "tokenRevocationEndpoint must be a valid URL");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove claims of type Collection that have a size of zero.
|
||||
* <p>
|
||||
* Both <a target="_blank" href="https://tools.ietf.org/html/rfc8414#section-3.2">3.2. Authorization Server Metadata Response</a>
|
||||
* and <a target="_blank" href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse">4.2. OpenID Provider Configuration Response</a>
|
||||
* state "Claims with zero elements MUST be omitted from the response."
|
||||
*/
|
||||
protected void removeEmptyClaims() {
|
||||
Set<String> claimsToRemove = this.claims.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> entry.getValue() != null)
|
||||
.filter(entry -> Collection.class.isAssignableFrom(entry.getValue().getClass()))
|
||||
.filter(entry -> ((Collection<?>) entry.getValue()).size() == 0)
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
for (String claimToRemove : claimsToRemove) {
|
||||
this.claims.remove(claimToRemove);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void validateURL(Object url, String errorMessage) {
|
||||
if (URL.class.isAssignableFrom(url.getClass())) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
new URI(url.toString()).toURL();
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalArgumentException(errorMessage, ex);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void addClaimToClaimList(String name, String value) {
|
||||
Assert.hasText(name, "name cannot be empty");
|
||||
Assert.notNull(value, "value cannot be null");
|
||||
this.claims.computeIfAbsent(name, k -> new LinkedList<String>());
|
||||
((List<String>) this.claims.get(name)).add(value);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
/*
|
||||
* Copyright 2020-2021 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.core;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A base representation of OAuth 2.0 Authorization Server metadata,
|
||||
* returned by an endpoint defined in OAuth 2.0 Authorization Server Metadata and OpenID Connect Discovery 1.0.
|
||||
* The metadata endpoint returns a set of claims an Authorization Server describes about its configuration.
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
* @see OAuth2AuthorizationServerMetadataClaimAccessor
|
||||
* @since 0.1.1
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc8414#section-3.2">3.2. Authorization Server Metadata Response</a>
|
||||
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse">4.2. OpenID Provider Configuration Response</a>
|
||||
*/
|
||||
public abstract class AbstractOAuth2AuthorizationServerMetadata implements OAuth2AuthorizationServerMetadataClaimAccessor, Serializable {
|
||||
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||
private final Map<String, Object> claims;
|
||||
|
||||
protected AbstractOAuth2AuthorizationServerMetadata(Map<String, Object> claims) {
|
||||
Assert.notEmpty(claims, "claims cannot be empty");
|
||||
this.claims = Collections.unmodifiableMap(new LinkedHashMap<>(claims));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the metadata as claims.
|
||||
*
|
||||
* @return a {@code Map} of the metadata as claims
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> getClaims() {
|
||||
return this.claims;
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for subclasses of {@link AbstractOAuth2AuthorizationServerMetadata}.
|
||||
*/
|
||||
protected static abstract class AbstractBuilder<T extends AbstractOAuth2AuthorizationServerMetadata, B extends AbstractBuilder<T, B>> {
|
||||
private final Map<String, Object> claims = new LinkedHashMap<>();
|
||||
|
||||
protected AbstractBuilder() {
|
||||
}
|
||||
|
||||
protected Map<String, Object> getClaims() {
|
||||
return this.claims;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected final B getThis() {
|
||||
return (B) this; // avoid unchecked casts in subclasses by using "getThis()" instead of "(B) this"
|
||||
};
|
||||
|
||||
/**
|
||||
* Use this {@code issuer} in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, REQUIRED.
|
||||
*
|
||||
* @param issuer the {@code URL} of the Authorization Server's Issuer Identifier
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B issuer(String issuer) {
|
||||
return claim(OAuth2AuthorizationServerMetadataClaimNames.ISSUER, issuer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@code authorization_endpoint} in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, REQUIRED.
|
||||
*
|
||||
* @param authorizationEndpoint the {@code URL} of the OAuth 2.0 Authorization Endpoint
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B authorizationEndpoint(String authorizationEndpoint) {
|
||||
return claim(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT, authorizationEndpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@code token_endpoint} in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, REQUIRED.
|
||||
*
|
||||
* @param tokenEndpoint the {@code URL} of the OAuth 2.0 Token Endpoint
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B tokenEndpoint(String tokenEndpoint) {
|
||||
return claim(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT, tokenEndpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this client authentication method to the collection of {@code token_endpoint_auth_methods_supported}
|
||||
* in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL.
|
||||
*
|
||||
* @param authenticationMethod the client authentication method supported by the OAuth 2.0 Token Endpoint
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B tokenEndpointAuthenticationMethod(String authenticationMethod) {
|
||||
addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, authenticationMethod);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} of the client authentication method(s) allowing the ability to add, replace, or remove.
|
||||
*
|
||||
* @param authenticationMethodsConsumer a {@code Consumer} of the client authentication method(s) supported by the OAuth 2.0 Token Endpoint
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B tokenEndpointAuthenticationMethods(Consumer<List<String>> authenticationMethodsConsumer) {
|
||||
acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, authenticationMethodsConsumer);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@code jwks_uri} in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL.
|
||||
*
|
||||
* @param jwkSetUrl the {@code URL} of the JSON Web Key Set
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B jwkSetUrl(String jwkSetUrl) {
|
||||
return claim(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI, jwkSetUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this OAuth 2.0 {@code scope} to the collection of {@code scopes_supported}
|
||||
* in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, RECOMMENDED.
|
||||
*
|
||||
* @param scope the OAuth 2.0 {@code scope} value supported
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B scope(String scope) {
|
||||
addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, scope);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} of the OAuth 2.0 {@code scope} values supported allowing the ability to add, replace, or remove.
|
||||
*
|
||||
* @param scopesConsumer a {@code Consumer} of the OAuth 2.0 {@code scope} values supported
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B scopes(Consumer<List<String>> scopesConsumer) {
|
||||
acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, scopesConsumer);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this OAuth 2.0 {@code response_type} to the collection of {@code response_types_supported}
|
||||
* in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, REQUIRED.
|
||||
*
|
||||
* @param responseType the OAuth 2.0 {@code response_type} value supported
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B responseType(String responseType) {
|
||||
addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, responseType);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} of the OAuth 2.0 {@code response_type} values supported allowing the ability to add, replace, or remove.
|
||||
*
|
||||
* @param responseTypesConsumer a {@code Consumer} of the OAuth 2.0 {@code response_type} values supported
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B responseTypes(Consumer<List<String>> responseTypesConsumer) {
|
||||
acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, responseTypesConsumer);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this OAuth 2.0 {@code grant_type} to the collection of {@code grant_types_supported}
|
||||
* in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL.
|
||||
*
|
||||
* @param grantType the OAuth 2.0 {@code grant_type} value supported
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B grantType(String grantType) {
|
||||
addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED, grantType);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} of the OAuth 2.0 {@code grant_type} values supported allowing the ability to add, replace, or remove.
|
||||
*
|
||||
* @param grantTypesConsumer a {@code Consumer} of the OAuth 2.0 {@code grant_type} values supported
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B grantTypes(Consumer<List<String>> grantTypesConsumer) {
|
||||
acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED, grantTypesConsumer);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@code revocation_endpoint} in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL.
|
||||
*
|
||||
* @param tokenRevocationEndpoint the {@code URL} of the OAuth 2.0 Token Revocation Endpoint
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B tokenRevocationEndpoint(String tokenRevocationEndpoint) {
|
||||
return claim(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT, tokenRevocationEndpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this client authentication method to the collection of {@code revocation_endpoint_auth_methods_supported}
|
||||
* in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL.
|
||||
*
|
||||
* @param authenticationMethod the client authentication method supported by the OAuth 2.0 Token Revocation Endpoint
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B tokenRevocationEndpointAuthenticationMethod(String authenticationMethod) {
|
||||
addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED, authenticationMethod);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} of the client authentication method(s) allowing the ability to add, replace, or remove.
|
||||
*
|
||||
* @param authenticationMethodsConsumer a {@code Consumer} of the client authentication method(s) supported by the OAuth 2.0 Token Revocation Endpoint
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B tokenRevocationEndpointAuthenticationMethods(Consumer<List<String>> authenticationMethodsConsumer) {
|
||||
acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED, authenticationMethodsConsumer);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@code introspection_endpoint} in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL.
|
||||
*
|
||||
* @param tokenIntrospectionEndpoint the {@code URL} of the OAuth 2.0 Token Introspection Endpoint
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B tokenIntrospectionEndpoint(String tokenIntrospectionEndpoint) {
|
||||
return claim(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT, tokenIntrospectionEndpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this client authentication method to the collection of {@code introspection_endpoint_auth_methods_supported}
|
||||
* in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL.
|
||||
*
|
||||
* @param authenticationMethod the client authentication method supported by the OAuth 2.0 Token Introspection Endpoint
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B tokenIntrospectionEndpointAuthenticationMethod(String authenticationMethod) {
|
||||
addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED, authenticationMethod);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} of the client authentication method(s) allowing the ability to add, replace, or remove.
|
||||
*
|
||||
* @param authenticationMethodsConsumer a {@code Consumer} of the client authentication method(s) supported by the OAuth 2.0 Token Introspection Endpoint
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B tokenIntrospectionEndpointAuthenticationMethods(Consumer<List<String>> authenticationMethodsConsumer) {
|
||||
acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED, authenticationMethodsConsumer);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add this Proof Key for Code Exchange (PKCE) {@code code_challenge_method} to the collection of {@code code_challenge_methods_supported}
|
||||
* in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL.
|
||||
*
|
||||
* @param codeChallengeMethod the {@code code_challenge_method} value supported
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B codeChallengeMethod(String codeChallengeMethod) {
|
||||
addClaimToClaimList(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED, codeChallengeMethod);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Consumer} of the Proof Key for Code Exchange (PKCE) {@code code_challenge_method} values supported allowing the ability to add, replace, or remove.
|
||||
*
|
||||
* @param codeChallengeMethodsConsumer a {@code Consumer} of the {@code code_challenge_method} values supported
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B codeChallengeMethods(Consumer<List<String>> codeChallengeMethodsConsumer) {
|
||||
acceptClaimValues(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED, codeChallengeMethodsConsumer);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this claim in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}.
|
||||
*
|
||||
* @param name the claim name
|
||||
* @param value the claim value
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
*/
|
||||
public B claim(String name, Object value) {
|
||||
Assert.hasText(name, "name cannot be empty");
|
||||
Assert.notNull(value, "value cannot be null");
|
||||
this.claims.put(name, value);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to every {@link #claim(String, Object)} declared so far with
|
||||
* the possibility to add, replace, or remove.
|
||||
*
|
||||
* @param claimsConsumer a {@code Consumer} of the claims
|
||||
* @return the {@link AbstractBuilder} for further configurations
|
||||
*/
|
||||
public B claims(Consumer<Map<String, Object>> claimsConsumer) {
|
||||
claimsConsumer.accept(this.claims);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the {@link AbstractOAuth2AuthorizationServerMetadata}.
|
||||
*
|
||||
* @return the {@link AbstractOAuth2AuthorizationServerMetadata}
|
||||
*/
|
||||
public abstract T build();
|
||||
|
||||
protected void validate() {
|
||||
Assert.notNull(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.ISSUER), "issuer cannot be null");
|
||||
validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.ISSUER), "issuer must be a valid URL");
|
||||
Assert.notNull(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT), "authorizationEndpoint cannot be null");
|
||||
validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT), "authorizationEndpoint must be a valid URL");
|
||||
Assert.notNull(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT), "tokenEndpoint cannot be null");
|
||||
validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT), "tokenEndpoint must be a valid URL");
|
||||
if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED) != null) {
|
||||
Assert.isInstanceOf(List.class, getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED), "tokenEndpointAuthenticationMethods must be of type List");
|
||||
Assert.notEmpty((List<?>) getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED), "tokenEndpointAuthenticationMethods cannot be empty");
|
||||
}
|
||||
if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI) != null) {
|
||||
validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI), "jwksUri must be a valid URL");
|
||||
}
|
||||
if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED) != null) {
|
||||
Assert.isInstanceOf(List.class, getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED), "scopes must be of type List");
|
||||
Assert.notEmpty((List<?>) getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED), "scopes cannot be empty");
|
||||
}
|
||||
Assert.notNull(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED), "responseTypes cannot be null");
|
||||
Assert.isInstanceOf(List.class, getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED), "responseTypes must be of type List");
|
||||
Assert.notEmpty((List<?>) getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED), "responseTypes cannot be empty");
|
||||
if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED) != null) {
|
||||
Assert.isInstanceOf(List.class, getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED), "grantTypes must be of type List");
|
||||
Assert.notEmpty((List<?>) getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED), "grantTypes cannot be empty");
|
||||
}
|
||||
if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT) != null) {
|
||||
validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT), "tokenRevocationEndpoint must be a valid URL");
|
||||
}
|
||||
if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED) != null) {
|
||||
Assert.isInstanceOf(List.class, getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED), "tokenRevocationEndpointAuthenticationMethods must be of type List");
|
||||
Assert.notEmpty((List<?>) getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED), "tokenRevocationEndpointAuthenticationMethods cannot be empty");
|
||||
}
|
||||
if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT) != null) {
|
||||
validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT), "tokenIntrospectionEndpoint must be a valid URL");
|
||||
}
|
||||
if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED) != null) {
|
||||
Assert.isInstanceOf(List.class, getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED), "tokenIntrospectionEndpointAuthenticationMethods must be of type List");
|
||||
Assert.notEmpty((List<?>) getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED), "tokenIntrospectionEndpointAuthenticationMethods cannot be empty");
|
||||
}
|
||||
if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED) != null) {
|
||||
Assert.isInstanceOf(List.class, getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED), "codeChallengeMethods must be of type List");
|
||||
Assert.notEmpty((List<?>) getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED), "codeChallengeMethods cannot be empty");
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void addClaimToClaimList(String name, String value) {
|
||||
Assert.hasText(name, "name cannot be empty");
|
||||
Assert.notNull(value, "value cannot be null");
|
||||
getClaims().computeIfAbsent(name, k -> new LinkedList<String>());
|
||||
((List<String>) getClaims().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");
|
||||
getClaims().computeIfAbsent(name, k -> new LinkedList<String>());
|
||||
List<String> values = (List<String>) getClaims().get(name);
|
||||
valuesConsumer.accept(values);
|
||||
}
|
||||
|
||||
private static void validateURL(Object url, String errorMessage) {
|
||||
if (URL.class.isAssignableFrom(url.getClass())) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
new URI(url.toString()).toURL();
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalArgumentException(errorMessage, ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* Copyright 2020-2021 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.
|
||||
@@ -13,34 +13,27 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.core.endpoint;
|
||||
package org.springframework.security.oauth2.core;
|
||||
|
||||
import org.springframework.security.oauth2.core.AbstractOAuth2AuthorizationServerConfiguration;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationServerMetadataClaimAccessor;
|
||||
import org.springframework.security.oauth2.core.Version;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A representation of an OAuth 2.0 Authorization Server Configuration response,
|
||||
* which is returned form an OAuth 2.0 Authorization Server's Configuration Endpoint,
|
||||
* A representation of an OAuth 2.0 Authorization Server Metadata response,
|
||||
* which is returned from an OAuth 2.0 Authorization Server's Metadata Endpoint,
|
||||
* and contains a set of claims about the Authorization Server's configuration.
|
||||
* The claims are defined by the OAuth 2.0 Authorization Server Metadata
|
||||
* specification (RFC 8414).
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
* @since 0.1.1
|
||||
* @see AbstractOAuth2AuthorizationServerConfiguration
|
||||
* @see OAuth2AuthorizationServerMetadataClaimAccessor
|
||||
* @see AbstractOAuth2AuthorizationServerMetadata
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc8414#section-3.2">3.2. Authorization Server Metadata Response</a>
|
||||
*/
|
||||
public final class OAuth2AuthorizationServerConfiguration extends AbstractOAuth2AuthorizationServerConfiguration
|
||||
implements OAuth2AuthorizationServerMetadataClaimAccessor, Serializable {
|
||||
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||
public final class OAuth2AuthorizationServerMetadata extends AbstractOAuth2AuthorizationServerMetadata {
|
||||
|
||||
private OAuth2AuthorizationServerConfiguration(Map<String, Object> claims) {
|
||||
private OAuth2AuthorizationServerMetadata(Map<String, Object> claims) {
|
||||
super(claims);
|
||||
}
|
||||
|
||||
@@ -66,27 +59,28 @@ public final class OAuth2AuthorizationServerConfiguration extends AbstractOAuth2
|
||||
}
|
||||
|
||||
/**
|
||||
* Helps configure an {@link OAuth2AuthorizationServerConfiguration}.
|
||||
* Helps configure an {@link OAuth2AuthorizationServerMetadata}.
|
||||
*/
|
||||
public static class Builder
|
||||
extends AbstractOAuth2AuthorizationServerConfiguration.AbstractBuilder<OAuth2AuthorizationServerConfiguration, Builder> {
|
||||
public static class Builder extends AbstractBuilder<OAuth2AuthorizationServerMetadata, Builder> {
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the claims and build the {@link OAuth2AuthorizationServerConfiguration}.
|
||||
* Validate the claims and build the {@link OAuth2AuthorizationServerMetadata}.
|
||||
* <p>
|
||||
* The following claims are REQUIRED:
|
||||
* {@code issuer}, {@code authorization_endpoint}, {@code token_endpoint},
|
||||
* {@code jwks_uri} and {@code response_types_supported}.
|
||||
* {@code issuer}, {@code authorization_endpoint}, {@code token_endpoint}
|
||||
* and {@code response_types_supported}.
|
||||
*
|
||||
* @return the {@link OAuth2AuthorizationServerConfiguration}
|
||||
* @return the {@link OAuth2AuthorizationServerMetadata}
|
||||
*/
|
||||
public OAuth2AuthorizationServerConfiguration build() {
|
||||
validateCommonClaims();
|
||||
removeEmptyClaims();
|
||||
return new OAuth2AuthorizationServerConfiguration(this.claims);
|
||||
@Override
|
||||
public OAuth2AuthorizationServerMetadata build() {
|
||||
validate();
|
||||
return new OAuth2AuthorizationServerMetadata(getClaims());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* Copyright 2020-2021 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.
|
||||
@@ -15,18 +15,17 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.core;
|
||||
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A base {@link ClaimAccessor} for the "claims" the Authorization Server can make about
|
||||
* its configuration, used either in OpenID Connect Discovery 1.0 or OAuth 2.0 Authorization
|
||||
* Server Metadata.
|
||||
* A {@link ClaimAccessor} for the "claims" an Authorization Server describes about its configuration,
|
||||
* used in OAuth 2.0 Authorization Server Metadata and OpenID Connect Discovery 1.0.
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
* @since 0.1.1
|
||||
* @see ClaimAccessor
|
||||
* @see OAuth2AuthorizationServerMetadataClaimNames
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc8414#section-2">2. Authorization Server Metadata</a>
|
||||
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata">3. OpenID Provider Metadata</a>
|
||||
*/
|
||||
@@ -73,10 +72,19 @@ public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAcc
|
||||
*
|
||||
* @return the {@code URL} of the JSON Web Key Set
|
||||
*/
|
||||
default URL getJwkSetUri() {
|
||||
default URL getJwkSetUrl() {
|
||||
return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the OAuth 2.0 {@code scope} values supported {@code (scopes_supported)}.
|
||||
*
|
||||
* @return the OAuth 2.0 {@code scope} values supported
|
||||
*/
|
||||
default List<String> getScopes() {
|
||||
return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the OAuth 2.0 {@code response_type} values supported {@code (response_types_supported)}.
|
||||
*
|
||||
@@ -95,22 +103,13 @@ public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAcc
|
||||
return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the OAuth 2.0 {@code scope} values supported {@code (scopes_supported)}.
|
||||
*
|
||||
* @return the OAuth 2.0 {@code scope} values supported
|
||||
*/
|
||||
default List<String> getScopes() {
|
||||
return this.getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code URL} of the OAuth 2.0 Token Revocation Endpoint {@code (revocation_endpoint)}.
|
||||
*
|
||||
* @return the {@code URL} of the OAuth 2.0 Token Revocation Endpoint
|
||||
*/
|
||||
default URL getTokenRevocationEndpoint() {
|
||||
return this.getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT);
|
||||
return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,16 +118,34 @@ public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAcc
|
||||
* @return the client authentication methods supported by the OAuth 2.0 Token Revocation Endpoint
|
||||
*/
|
||||
default List<String> getTokenRevocationEndpointAuthenticationMethods() {
|
||||
return this.getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED);
|
||||
return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Proof Key for Code Exchange (PKCE) code challenge methods supported by the
|
||||
* OAuth 2.0 Authorization Server {@code (code_challenge_methods_supported)}.
|
||||
* Returns the {@code URL} of the OAuth 2.0 Token Introspection Endpoint {@code (introspection_endpoint)}.
|
||||
*
|
||||
* @return the code challenge methods supported by the OAuth 2.0 Authorization Server
|
||||
* @return the {@code URL} of the OAuth 2.0 Token Introspection Endpoint
|
||||
*/
|
||||
default URL getTokenIntrospectionEndpoint() {
|
||||
return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the client authentication methods supported by the OAuth 2.0 Token Introspection Endpoint {@code (introspection_endpoint_auth_methods_supported)}.
|
||||
*
|
||||
* @return the client authentication methods supported by the OAuth 2.0 Token Introspection Endpoint
|
||||
*/
|
||||
default List<String> getTokenIntrospectionEndpointAuthenticationMethods() {
|
||||
return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Proof Key for Code Exchange (PKCE) {@code code_challenge_method} values supported {@code (code_challenge_methods_supported)}.
|
||||
*
|
||||
* @return the {@code code_challenge_method} values supported
|
||||
*/
|
||||
default List<String> getCodeChallengeMethods() {
|
||||
return this.getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED);
|
||||
return getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* Copyright 2020-2021 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.
|
||||
@@ -15,15 +15,12 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.core;
|
||||
|
||||
import org.springframework.security.oauth2.core.oidc.OidcProviderMetadataClaimNames;
|
||||
|
||||
/**
|
||||
* The names of the "claims" an Authorization Server can make about its configuration,
|
||||
* used either in OpenID Connect Discovery 1.0 or OAuth 2.0 Authorization Server Metadata.
|
||||
* The names of the "claims" an Authorization Server describes about its configuration,
|
||||
* used in OAuth 2.0 Authorization Server Metadata and OpenID Connect Discovery 1.0.
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
* @since 0.1.1
|
||||
* @see OidcProviderMetadataClaimNames
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc8414#section-2">2. Authorization Server Metadata</a>
|
||||
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata">3. OpenID Provider Metadata</a>
|
||||
*/
|
||||
@@ -54,6 +51,11 @@ public interface OAuth2AuthorizationServerMetadataClaimNames {
|
||||
*/
|
||||
String JWKS_URI = "jwks_uri";
|
||||
|
||||
/**
|
||||
* {@code scopes_supported} - the OAuth 2.0 {@code scope} values supported
|
||||
*/
|
||||
String SCOPES_SUPPORTED = "scopes_supported";
|
||||
|
||||
/**
|
||||
* {@code response_types_supported} - the OAuth 2.0 {@code response_type} values supported
|
||||
*/
|
||||
@@ -64,24 +66,29 @@ public interface OAuth2AuthorizationServerMetadataClaimNames {
|
||||
*/
|
||||
String GRANT_TYPES_SUPPORTED = "grant_types_supported";
|
||||
|
||||
/**
|
||||
* {@code scopes_supported} - the OAuth 2.0 {@code scope} values supported
|
||||
*/
|
||||
String SCOPES_SUPPORTED = "scopes_supported";
|
||||
|
||||
/**
|
||||
* {@code revocation_endpoint} - the {@code URL} of the OAuth 2.0 Token Revocation Endpoint
|
||||
*/
|
||||
String REVOCATION_ENDPOINT = "revocation_endpoint";
|
||||
|
||||
/**
|
||||
* {@code token_endpoint_auth_methods_supported} - the client authentication methods supported by the OAuth 2.0 Token Revocation Endpoint
|
||||
* {@code revocation_endpoint_auth_methods_supported} - the client authentication methods supported by the OAuth 2.0 Token Revocation Endpoint
|
||||
*/
|
||||
String REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED = "revocation_endpoint_auth_methods_supported";
|
||||
|
||||
/**
|
||||
* {@code code_challenge_methods_supported} - the Proof Key for Code Exchange (PKCE) code challenge methods
|
||||
* supported by the OAuth 2.0 Authorization Server
|
||||
* {@code introspection_endpoint} - the {@code URL} of the OAuth 2.0 Token Introspection Endpoint
|
||||
*/
|
||||
String INTROSPECTION_ENDPOINT = "introspection_endpoint";
|
||||
|
||||
/**
|
||||
* {@code introspection_endpoint_auth_methods_supported} - the client authentication methods supported by the OAuth 2.0 Token Introspection Endpoint
|
||||
*/
|
||||
String INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED = "introspection_endpoint_auth_methods_supported";
|
||||
|
||||
/**
|
||||
* {@code code_challenge_methods_supported} - the Proof Key for Code Exchange (PKCE) {@code code_challenge_method} values supported
|
||||
*/
|
||||
String CODE_CHALLENGE_METHODS_SUPPORTED = "code_challenge_methods_supported";
|
||||
|
||||
}
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 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.core.endpoint;
|
||||
|
||||
import org.springframework.security.oauth2.core.Version;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* This class is temporary and will be removed after upgrading to Spring Security 5.5.0 GA.
|
||||
*
|
||||
* The {@code code_challenge_method} is consumed by the authorization endpoint. The client sets
|
||||
* the {@code code_challenge_method} parameter and a {@code code_challenge} derived from the
|
||||
* {@code code_verifier} using the {@code code_challenge_method}.
|
||||
*
|
||||
* <p>
|
||||
* The {@code code_challenge_method} parameter value may be one of "S256" or
|
||||
* "plain". If the client is capable of using "S256", it MUST use
|
||||
* "S256", as it is mandatory to implement on the server.
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
* @since 0.1.1
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636#section-6.2">6.2. PKCE Code Challenge Method Registry</a>
|
||||
*/
|
||||
public final class PkceCodeChallengeMethod2 implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||
|
||||
public static final PkceCodeChallengeMethod2 S256 = new PkceCodeChallengeMethod2("S256");
|
||||
|
||||
public static final PkceCodeChallengeMethod2 PLAIN = new PkceCodeChallengeMethod2("plain");
|
||||
|
||||
private final String value;
|
||||
|
||||
private PkceCodeChallengeMethod2(String value) {
|
||||
Assert.hasText(value, "value cannot be empty");
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the authorization response type.
|
||||
*
|
||||
* @return the value of the authorization response type
|
||||
*/
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || this.getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
PkceCodeChallengeMethod2 that = (PkceCodeChallengeMethod2) obj;
|
||||
return this.getValue().equals(that.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.getValue().hashCode();
|
||||
}
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 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.core.http.converter;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.AbstractHttpMessageConverter;
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationServerMetadataClaimNames;
|
||||
import org.springframework.security.oauth2.core.converter.ClaimConversionService;
|
||||
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationServerConfiguration;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* A {@link HttpMessageConverter} for an {@link OAuth2AuthorizationServerConfiguration OAuth 2.0 Authorization Server Configuration Response}.
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
* @since 0.1.1
|
||||
* @see AbstractHttpMessageConverter
|
||||
* @see OAuth2AuthorizationServerConfiguration
|
||||
*/
|
||||
public class OAuth2AuthorizationServerConfigurationHttpMessageConverter
|
||||
extends AbstractHttpMessageConverter<OAuth2AuthorizationServerConfiguration> {
|
||||
|
||||
private static final ParameterizedTypeReference<Map<String, Object>> STRING_OBJECT_MAP =
|
||||
new ParameterizedTypeReference<Map<String, Object>>() {};
|
||||
|
||||
private final GenericHttpMessageConverter<Object> jsonMessageConverter = HttpMessageConverters.getJsonMessageConverter();
|
||||
|
||||
private Converter<Map<String, Object>, OAuth2AuthorizationServerConfiguration> authorizationServerConfigurationConverter = new OAuth2AuthorizationServerConfigurationConverter();
|
||||
private Converter<OAuth2AuthorizationServerConfiguration, Map<String, Object>> authorizationServerConfigurationParametersConverter = OAuth2AuthorizationServerConfiguration::getClaims;
|
||||
|
||||
public OAuth2AuthorizationServerConfigurationHttpMessageConverter() {
|
||||
super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supports(Class<?> clazz) {
|
||||
return OAuth2AuthorizationServerConfiguration.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected OAuth2AuthorizationServerConfiguration readInternal(Class<? extends OAuth2AuthorizationServerConfiguration> clazz, HttpInputMessage inputMessage)
|
||||
throws HttpMessageNotReadableException {
|
||||
try {
|
||||
Map<String, Object> serverConfigurationParameters =
|
||||
(Map<String, Object>) this.jsonMessageConverter.read(STRING_OBJECT_MAP.getType(), null, inputMessage);
|
||||
return this.authorizationServerConfigurationConverter.convert(serverConfigurationParameters);
|
||||
} catch (Exception ex) {
|
||||
throw new HttpMessageNotReadableException(
|
||||
"An error occurred reading the OAuth 2.0 Authorization Server Configuration: " + ex.getMessage(), ex, inputMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeInternal(OAuth2AuthorizationServerConfiguration providerConfiguration, HttpOutputMessage outputMessage)
|
||||
throws HttpMessageNotWritableException {
|
||||
try {
|
||||
Map<String, Object> providerConfigurationResponseParameters =
|
||||
this.authorizationServerConfigurationParametersConverter.convert(providerConfiguration);
|
||||
this.jsonMessageConverter.write(
|
||||
providerConfigurationResponseParameters,
|
||||
STRING_OBJECT_MAP.getType(),
|
||||
MediaType.APPLICATION_JSON,
|
||||
outputMessage
|
||||
);
|
||||
} catch (Exception ex) {
|
||||
throw new HttpMessageNotWritableException(
|
||||
"An error occurred writing the OAuth 2.0 Authorization Server Configuration: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Converter} used for converting the OAuth 2.0 Authorization Server Configuration
|
||||
* parameters to an {@link OAuth2AuthorizationServerConfiguration}.
|
||||
*
|
||||
* @param authorizationServerConfigurationConverter the {@link Converter} used for converting to
|
||||
* an {@link OAuth2AuthorizationServerConfiguration}.
|
||||
*/
|
||||
public void setAuthorizationServerConfigurationConverter(Converter<Map<String, Object>, OAuth2AuthorizationServerConfiguration> authorizationServerConfigurationConverter) {
|
||||
Assert.notNull(authorizationServerConfigurationConverter, "authorizationServerConfigurationConverter cannot be null");
|
||||
this.authorizationServerConfigurationConverter = authorizationServerConfigurationConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Converter} used for converting the {@link OAuth2AuthorizationServerConfiguration} to a
|
||||
* {@code Map} representation of the OAuth 2.0 Authorization Server Configuration.
|
||||
*
|
||||
* @param authorizationServerConfigurationParametersConverter the {@link Converter} used for converting to a
|
||||
* {@code Map} representation of the OAuth 2.0 Authorization Server Configuration.
|
||||
*/
|
||||
public void setAuthorizationServerConfigurationParametersConverter(Converter<OAuth2AuthorizationServerConfiguration, Map<String, Object>> authorizationServerConfigurationParametersConverter) {
|
||||
Assert.notNull(authorizationServerConfigurationParametersConverter, "authorizationServerConfigurationParametersConverter cannot be null");
|
||||
this.authorizationServerConfigurationParametersConverter = authorizationServerConfigurationParametersConverter;
|
||||
}
|
||||
|
||||
private static final class OAuth2AuthorizationServerConfigurationConverter implements Converter<Map<String, Object>, OAuth2AuthorizationServerConfiguration> {
|
||||
private static final ClaimConversionService CLAIM_CONVERSION_SERVICE = ClaimConversionService.getSharedInstance();
|
||||
private static final TypeDescriptor OBJECT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Object.class);
|
||||
private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
|
||||
private static final TypeDescriptor URL_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(URL.class);
|
||||
private final ClaimTypeConverter claimTypeConverter;
|
||||
|
||||
private OAuth2AuthorizationServerConfigurationConverter() {
|
||||
Map<String, Converter<Object, ?>> claimNameToConverter = new HashMap<>();
|
||||
Converter<Object, ?> collectionStringConverter = getConverter(
|
||||
TypeDescriptor.collection(Collection.class, STRING_TYPE_DESCRIPTOR));
|
||||
Converter<Object, ?> urlConverter = getConverter(URL_TYPE_DESCRIPTOR);
|
||||
|
||||
claimNameToConverter.put(OAuth2AuthorizationServerMetadataClaimNames.ISSUER, urlConverter);
|
||||
claimNameToConverter.put(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT, urlConverter);
|
||||
claimNameToConverter.put(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT, urlConverter);
|
||||
claimNameToConverter.put(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT, urlConverter);
|
||||
claimNameToConverter.put(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI, urlConverter);
|
||||
claimNameToConverter.put(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED, collectionStringConverter);
|
||||
claimNameToConverter.put(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, collectionStringConverter);
|
||||
claimNameToConverter.put(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED, collectionStringConverter);
|
||||
claimNameToConverter.put(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, collectionStringConverter);
|
||||
claimNameToConverter.put(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, collectionStringConverter);
|
||||
claimNameToConverter.put(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED, collectionStringConverter);
|
||||
this.claimTypeConverter = new ClaimTypeConverter(claimNameToConverter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2AuthorizationServerConfiguration convert(Map<String, Object> source) {
|
||||
Map<String, Object> parsedClaims = this.claimTypeConverter.convert(source);
|
||||
return OAuth2AuthorizationServerConfiguration.withClaims(parsedClaims).build();
|
||||
}
|
||||
|
||||
private static Converter<Object, ?> getConverter(TypeDescriptor targetDescriptor) {
|
||||
return (source) -> CLAIM_CONVERSION_SERVICE.convert(source, OBJECT_TYPE_DESCRIPTOR, targetDescriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright 2020-2021 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.core.http.converter;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.AbstractHttpMessageConverter;
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationServerMetadata;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationServerMetadataClaimNames;
|
||||
import org.springframework.security.oauth2.core.converter.ClaimConversionService;
|
||||
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link HttpMessageConverter} for an {@link OAuth2AuthorizationServerMetadata OAuth 2.0 Authorization Server Metadata Response}.
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
* @since 0.1.1
|
||||
* @see AbstractHttpMessageConverter
|
||||
* @see OAuth2AuthorizationServerMetadata
|
||||
*/
|
||||
public class OAuth2AuthorizationServerMetadataHttpMessageConverter
|
||||
extends AbstractHttpMessageConverter<OAuth2AuthorizationServerMetadata> {
|
||||
|
||||
private static final ParameterizedTypeReference<Map<String, Object>> STRING_OBJECT_MAP =
|
||||
new ParameterizedTypeReference<Map<String, Object>>() {};
|
||||
|
||||
private final GenericHttpMessageConverter<Object> jsonMessageConverter = HttpMessageConverters.getJsonMessageConverter();
|
||||
|
||||
private Converter<Map<String, Object>, OAuth2AuthorizationServerMetadata> authorizationServerMetadataConverter = new OAuth2AuthorizationServerMetadataConverter();
|
||||
private Converter<OAuth2AuthorizationServerMetadata, Map<String, Object>> authorizationServerMetadataParametersConverter = OAuth2AuthorizationServerMetadata::getClaims;
|
||||
|
||||
public OAuth2AuthorizationServerMetadataHttpMessageConverter() {
|
||||
super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supports(Class<?> clazz) {
|
||||
return OAuth2AuthorizationServerMetadata.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected OAuth2AuthorizationServerMetadata readInternal(Class<? extends OAuth2AuthorizationServerMetadata> clazz, HttpInputMessage inputMessage)
|
||||
throws HttpMessageNotReadableException {
|
||||
try {
|
||||
Map<String, Object> authorizationServerMetadataParameters =
|
||||
(Map<String, Object>) this.jsonMessageConverter.read(STRING_OBJECT_MAP.getType(), null, inputMessage);
|
||||
return this.authorizationServerMetadataConverter.convert(authorizationServerMetadataParameters);
|
||||
} catch (Exception ex) {
|
||||
throw new HttpMessageNotReadableException(
|
||||
"An error occurred reading the OAuth 2.0 Authorization Server Metadata: " + ex.getMessage(), ex, inputMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeInternal(OAuth2AuthorizationServerMetadata authorizationServerMetadata, HttpOutputMessage outputMessage)
|
||||
throws HttpMessageNotWritableException {
|
||||
try {
|
||||
Map<String, Object> authorizationServerMetadataResponseParameters =
|
||||
this.authorizationServerMetadataParametersConverter.convert(authorizationServerMetadata);
|
||||
this.jsonMessageConverter.write(
|
||||
authorizationServerMetadataResponseParameters,
|
||||
STRING_OBJECT_MAP.getType(),
|
||||
MediaType.APPLICATION_JSON,
|
||||
outputMessage
|
||||
);
|
||||
} catch (Exception ex) {
|
||||
throw new HttpMessageNotWritableException(
|
||||
"An error occurred writing the OAuth 2.0 Authorization Server Metadata: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Converter} used for converting the OAuth 2.0 Authorization Server Metadata
|
||||
* parameters to an {@link OAuth2AuthorizationServerMetadata}.
|
||||
*
|
||||
* @param authorizationServerMetadataConverter the {@link Converter} used for converting to
|
||||
* an {@link OAuth2AuthorizationServerMetadata}.
|
||||
*/
|
||||
public final void setAuthorizationServerMetadataConverter(Converter<Map<String, Object>, OAuth2AuthorizationServerMetadata> authorizationServerMetadataConverter) {
|
||||
Assert.notNull(authorizationServerMetadataConverter, "authorizationServerMetadataConverter cannot be null");
|
||||
this.authorizationServerMetadataConverter = authorizationServerMetadataConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Converter} used for converting the {@link OAuth2AuthorizationServerMetadata} to a
|
||||
* {@code Map} representation of the OAuth 2.0 Authorization Server Metadata.
|
||||
*
|
||||
* @param authorizationServerMetadataParametersConverter the {@link Converter} used for converting to a
|
||||
* {@code Map} representation of the OAuth 2.0 Authorization Server Metadata.
|
||||
*/
|
||||
public final void setAuthorizationServerMetadataParametersConverter(Converter<OAuth2AuthorizationServerMetadata, Map<String, Object>> authorizationServerMetadataParametersConverter) {
|
||||
Assert.notNull(authorizationServerMetadataParametersConverter, "authorizationServerMetadataParametersConverter cannot be null");
|
||||
this.authorizationServerMetadataParametersConverter = authorizationServerMetadataParametersConverter;
|
||||
}
|
||||
|
||||
private static final class OAuth2AuthorizationServerMetadataConverter implements Converter<Map<String, Object>, OAuth2AuthorizationServerMetadata> {
|
||||
private static final ClaimConversionService CLAIM_CONVERSION_SERVICE = ClaimConversionService.getSharedInstance();
|
||||
private static final TypeDescriptor OBJECT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Object.class);
|
||||
private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
|
||||
private static final TypeDescriptor URL_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(URL.class);
|
||||
private final ClaimTypeConverter claimTypeConverter;
|
||||
|
||||
private OAuth2AuthorizationServerMetadataConverter() {
|
||||
Converter<Object, ?> collectionStringConverter = getConverter(
|
||||
TypeDescriptor.collection(Collection.class, STRING_TYPE_DESCRIPTOR));
|
||||
Converter<Object, ?> urlConverter = getConverter(URL_TYPE_DESCRIPTOR);
|
||||
|
||||
Map<String, Converter<Object, ?>> claimConverters = new HashMap<>();
|
||||
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.ISSUER, urlConverter);
|
||||
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT, urlConverter);
|
||||
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT, urlConverter);
|
||||
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, collectionStringConverter);
|
||||
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI, urlConverter);
|
||||
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, collectionStringConverter);
|
||||
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, collectionStringConverter);
|
||||
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED, collectionStringConverter);
|
||||
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT, urlConverter);
|
||||
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED, collectionStringConverter);
|
||||
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT, urlConverter);
|
||||
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED, collectionStringConverter);
|
||||
claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED, collectionStringConverter);
|
||||
this.claimTypeConverter = new ClaimTypeConverter(claimConverters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2AuthorizationServerMetadata convert(Map<String, Object> source) {
|
||||
Map<String, Object> parsedClaims = this.claimTypeConverter.convert(source);
|
||||
return OAuth2AuthorizationServerMetadata.withClaims(parsedClaims).build();
|
||||
}
|
||||
|
||||
private static Converter<Object, ?> getConverter(TypeDescriptor targetDescriptor) {
|
||||
return (source) -> CLAIM_CONVERSION_SERVICE.convert(source, OBJECT_TYPE_DESCRIPTOR, targetDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* Copyright 2020-2021 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.
|
||||
@@ -15,16 +15,15 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.core.oidc;
|
||||
|
||||
import org.springframework.security.oauth2.core.AbstractOAuth2AuthorizationServerConfiguration;
|
||||
import org.springframework.security.oauth2.core.Version;
|
||||
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.security.oauth2.core.AbstractOAuth2AuthorizationServerMetadata;
|
||||
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A representation of an OpenID Provider Configuration Response,
|
||||
* which is returned from an Issuer's Discovery Endpoint,
|
||||
@@ -33,13 +32,12 @@ import java.util.function.Consumer;
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
* @since 0.1.0
|
||||
* @see AbstractOAuth2AuthorizationServerMetadata
|
||||
* @see OidcProviderMetadataClaimAccessor
|
||||
* @see AbstractOAuth2AuthorizationServerConfiguration
|
||||
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse">4.2. OpenID Provider Configuration Response</a>
|
||||
*/
|
||||
public final class OidcProviderConfiguration extends AbstractOAuth2AuthorizationServerConfiguration
|
||||
implements OidcProviderMetadataClaimAccessor, Serializable {
|
||||
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||
public final class OidcProviderConfiguration extends AbstractOAuth2AuthorizationServerMetadata
|
||||
implements OidcProviderMetadataClaimAccessor {
|
||||
|
||||
private OidcProviderConfiguration(Map<String, Object> claims) {
|
||||
super(claims);
|
||||
@@ -66,9 +64,10 @@ public final class OidcProviderConfiguration extends AbstractOAuth2Authorization
|
||||
}
|
||||
|
||||
/**
|
||||
* Helps configure an {@link OidcProviderConfiguration}
|
||||
* Helps configure an {@link OidcProviderConfiguration}.
|
||||
*/
|
||||
public static class Builder extends AbstractOAuth2AuthorizationServerConfiguration.AbstractBuilder<OidcProviderConfiguration, Builder> {
|
||||
public static class Builder extends AbstractBuilder<OidcProviderConfiguration, Builder> {
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
@@ -119,32 +118,6 @@ public final class OidcProviderConfiguration extends AbstractOAuth2Authorization
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this claim in the resulting {@link OidcProviderConfiguration}.
|
||||
*
|
||||
* @param name the claim name
|
||||
* @param value the claim value
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
public Builder claim(String name, Object value) {
|
||||
Assert.hasText(name, "name cannot be empty");
|
||||
Assert.notNull(value, "value cannot be null");
|
||||
this.claims.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides access to every {@link #claim(String, Object)} declared so far with
|
||||
* the possibility to add, replace, or remove.
|
||||
*
|
||||
* @param claimsConsumer a {@code Consumer} of the claims
|
||||
* @return the {@link Builder} for further configurations
|
||||
*/
|
||||
public Builder claims(Consumer<Map<String, Object>> claimsConsumer) {
|
||||
claimsConsumer.accept(this.claims);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the claims and build the {@link OidcProviderConfiguration}.
|
||||
* <p>
|
||||
@@ -157,19 +130,38 @@ public final class OidcProviderConfiguration extends AbstractOAuth2Authorization
|
||||
*/
|
||||
@Override
|
||||
public OidcProviderConfiguration build() {
|
||||
validateCommonClaims();
|
||||
validateOidcSpecificClaims();
|
||||
removeEmptyClaims();
|
||||
return new OidcProviderConfiguration(this.claims);
|
||||
validate();
|
||||
return new OidcProviderConfiguration(getClaims());
|
||||
}
|
||||
|
||||
private void validateOidcSpecificClaims() {
|
||||
Assert.notNull(this.claims.get(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED), "subjectTypes cannot be null");
|
||||
Assert.isInstanceOf(List.class, this.claims.get(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED), "subjectTypes must be of type List");
|
||||
Assert.notEmpty((List<?>) this.claims.get(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED), "subjectTypes cannot be empty");
|
||||
Assert.notNull(this.claims.get(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED), "idTokenSigningAlgorithms cannot be null");
|
||||
Assert.isInstanceOf(List.class, this.claims.get(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED), "idTokenSigningAlgorithms must be of type List");
|
||||
Assert.notEmpty((List<?>) this.claims.get(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED), "idTokenSigningAlgorithms cannot be empty");
|
||||
@Override
|
||||
protected void validate() {
|
||||
super.validate();
|
||||
Assert.notNull(getClaims().get(OidcProviderMetadataClaimNames.JWKS_URI), "jwksUri cannot be null");
|
||||
Assert.notNull(getClaims().get(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED), "subjectTypes cannot be null");
|
||||
Assert.isInstanceOf(List.class, getClaims().get(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED), "subjectTypes must be of type List");
|
||||
Assert.notEmpty((List<?>) getClaims().get(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED), "subjectTypes cannot be empty");
|
||||
Assert.notNull(getClaims().get(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED), "idTokenSigningAlgorithms cannot be null");
|
||||
Assert.isInstanceOf(List.class, getClaims().get(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED), "idTokenSigningAlgorithms must be of type List");
|
||||
Assert.notEmpty((List<?>) getClaims().get(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED), "idTokenSigningAlgorithms cannot be empty");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void addClaimToClaimList(String name, String value) {
|
||||
Assert.hasText(name, "name cannot be empty");
|
||||
Assert.notNull(value, "value cannot be null");
|
||||
getClaims().computeIfAbsent(name, k -> new LinkedList<String>());
|
||||
((List<String>) getClaims().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");
|
||||
getClaims().computeIfAbsent(name, k -> new LinkedList<String>());
|
||||
List<String> values = (List<String>) getClaims().get(name);
|
||||
valuesConsumer.accept(values);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* Copyright 2020-2021 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.
|
||||
@@ -16,14 +16,13 @@
|
||||
package org.springframework.security.oauth2.core.oidc;
|
||||
|
||||
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationServerMetadataClaimAccessor;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.security.oauth2.core.ClaimAccessor;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationServerMetadataClaimAccessor;
|
||||
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A {@link ClaimAccessor} for the "claims" that can be returned
|
||||
* in the OpenID Provider Configuration Response.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* Copyright 2020-2021 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,6 +24,7 @@ import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
* @since 0.1.0
|
||||
* @see OAuth2AuthorizationServerMetadataClaimNames
|
||||
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata">3. OpenID Provider Metadata</a>
|
||||
*/
|
||||
public interface OidcProviderMetadataClaimNames extends OAuth2AuthorizationServerMetadataClaimNames {
|
||||
|
||||
@@ -31,7 +31,6 @@ import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.endpoint.PkceCodeChallengeMethod2;
|
||||
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
@@ -157,9 +156,9 @@ public class OAuth2ClientAuthenticationProvider implements AuthenticationProvide
|
||||
private static boolean codeVerifierValid(String codeVerifier, String codeChallenge, String codeChallengeMethod) {
|
||||
if (!StringUtils.hasText(codeVerifier)) {
|
||||
return false;
|
||||
} else if (!StringUtils.hasText(codeChallengeMethod) || PkceCodeChallengeMethod2.PLAIN.getValue().equals(codeChallengeMethod)) {
|
||||
} else if (!StringUtils.hasText(codeChallengeMethod) || "plain".equals(codeChallengeMethod)) {
|
||||
return codeVerifier.equals(codeChallenge);
|
||||
} else if (PkceCodeChallengeMethod2.S256.getValue().equals(codeChallengeMethod)) {
|
||||
} else if ("S256".equals(codeChallengeMethod)) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
byte[] digest = md.digest(codeVerifier.getBytes(StandardCharsets.US_ASCII));
|
||||
|
||||
@@ -70,8 +70,7 @@ public class ProviderSettings extends Settings {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Provider's OAuth 2.0 Authorization endpoint.
|
||||
* The default is {@code /oauth2/authorize}.
|
||||
* Returns the Provider's OAuth 2.0 Authorization endpoint. The default is {@code /oauth2/authorize}.
|
||||
*
|
||||
* @return the Authorization endpoint
|
||||
*/
|
||||
@@ -90,8 +89,7 @@ public class ProviderSettings extends Settings {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Provider's OAuth 2.0 Token endpoint.
|
||||
* The default is {@code /oauth2/token}.
|
||||
* Returns the Provider's OAuth 2.0 Token endpoint. The default is {@code /oauth2/token}.
|
||||
*
|
||||
* @return the Token endpoint
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* Copyright 2020-2021 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.
|
||||
@@ -75,21 +75,20 @@ public class OidcProviderConfigurationEndpointFilter extends OncePerRequestFilte
|
||||
return;
|
||||
}
|
||||
|
||||
OidcProviderConfiguration providerConfiguration = OidcProviderConfiguration
|
||||
.builder()
|
||||
OidcProviderConfiguration providerConfiguration = OidcProviderConfiguration.builder()
|
||||
.issuer(this.providerSettings.issuer())
|
||||
.authorizationEndpoint(asUrl(this.providerSettings.issuer(), this.providerSettings.authorizationEndpoint()))
|
||||
.tokenEndpoint(asUrl(this.providerSettings.issuer(), this.providerSettings.tokenEndpoint()))
|
||||
.tokenEndpointAuthenticationMethod("client_secret_basic") // TODO: Use ClientAuthenticationMethod.CLIENT_SECRET_BASIC in Spring Security 5.5.0
|
||||
.tokenEndpointAuthenticationMethod("client_secret_post") // TODO: Use ClientAuthenticationMethod.CLIENT_SECRET_POST in Spring Security 5.5.0
|
||||
.jwkSetUri(asUrl(this.providerSettings.issuer(), this.providerSettings.jwkSetEndpoint()))
|
||||
.tokenEndpointAuthenticationMethod("client_secret_basic") // TODO: Use ClientAuthenticationMethod.CLIENT_SECRET_BASIC in Spring Security 5.5.0
|
||||
.tokenEndpointAuthenticationMethod("client_secret_post") // TODO: Use ClientAuthenticationMethod.CLIENT_SECRET_POST in Spring Security 5.5.0
|
||||
.jwkSetUrl(asUrl(this.providerSettings.issuer(), this.providerSettings.jwkSetEndpoint()))
|
||||
.responseType(OAuth2AuthorizationResponseType.CODE.getValue())
|
||||
.grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())
|
||||
.grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
|
||||
.grantType(AuthorizationGrantType.REFRESH_TOKEN.getValue())
|
||||
.responseType(OAuth2AuthorizationResponseType.CODE.getValue())
|
||||
.scope(OidcScopes.OPENID)
|
||||
.subjectType("public")
|
||||
.idTokenSigningAlgorithm(SignatureAlgorithm.RS256.getName())
|
||||
.scope(OidcScopes.OPENID)
|
||||
.build();
|
||||
|
||||
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
|
||||
|
||||
@@ -48,7 +48,6 @@ import org.springframework.security.oauth2.core.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.endpoint.PkceCodeChallengeMethod2;
|
||||
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
@@ -371,7 +370,7 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
|
||||
String codeChallengeMethod = authorizationRequestContext.getParameters().getFirst(PkceParameterNames.CODE_CHALLENGE_METHOD);
|
||||
if (StringUtils.hasText(codeChallengeMethod)) {
|
||||
if (authorizationRequestContext.getParameters().get(PkceParameterNames.CODE_CHALLENGE_METHOD).size() != 1 ||
|
||||
(!PkceCodeChallengeMethod2.S256.getValue().equals(codeChallengeMethod) && !PkceCodeChallengeMethod2.PLAIN.getValue().equals(codeChallengeMethod))) {
|
||||
(!"S256".equals(codeChallengeMethod) && !"plain".equals(codeChallengeMethod))) {
|
||||
authorizationRequestContext.setError(
|
||||
createError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE_METHOD, PKCE_ERROR_URI));
|
||||
return;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* Copyright 2020-2021 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.
|
||||
@@ -15,15 +15,22 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization.web;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.server.ServletServerHttpResponse;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationServerMetadata;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationServerConfiguration;
|
||||
import org.springframework.security.oauth2.core.endpoint.PkceCodeChallengeMethod2;
|
||||
import org.springframework.security.oauth2.core.http.converter.OAuth2AuthorizationServerConfigurationHttpMessageConverter;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
||||
import org.springframework.security.oauth2.core.http.converter.OAuth2AuthorizationServerMetadataHttpMessageConverter;
|
||||
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
@@ -31,36 +38,31 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A {@code Filter} that processes OAuth 2.0 Authorization Server Configuration Requests.
|
||||
* A {@code Filter} that processes OAuth 2.0 Authorization Server Metadata Requests.
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
* @since 0.1.1
|
||||
* @see OAuth2AuthorizationServerMetadata
|
||||
* @see ProviderSettings
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc8414#section-3">3. Obtaining Authorization Server Metadata</a>
|
||||
*/
|
||||
public class OAuth2AuthorizationServerConfigurationEndpointFilter extends OncePerRequestFilter {
|
||||
public class OAuth2AuthorizationServerMetadataEndpointFilter extends OncePerRequestFilter {
|
||||
/**
|
||||
* The default endpoint {@code URI} for OAuth 2.0 Authorization Server Configuration requests.
|
||||
* The default endpoint {@code URI} for OAuth 2.0 Authorization Server Metadata requests.
|
||||
*/
|
||||
public static final String DEFAULT_OAUTH2_AUTHORIZATION_SERVER_CONFIGURATION_ENDPOINT_URI = "/.well-known/oauth-authorization-server";
|
||||
public static final String DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI = "/.well-known/oauth-authorization-server";
|
||||
|
||||
private final RequestMatcher requestMatcher;
|
||||
private final ProviderSettings providerSettings;
|
||||
private final OAuth2AuthorizationServerConfigurationHttpMessageConverter authorizationServerConfigurationHttpMessageConverter
|
||||
= new OAuth2AuthorizationServerConfigurationHttpMessageConverter();
|
||||
private final RequestMatcher requestMatcher;
|
||||
private final OAuth2AuthorizationServerMetadataHttpMessageConverter authorizationServerMetadataHttpMessageConverter =
|
||||
new OAuth2AuthorizationServerMetadataHttpMessageConverter();
|
||||
|
||||
public OAuth2AuthorizationServerConfigurationEndpointFilter(ProviderSettings providerSettings) {
|
||||
public OAuth2AuthorizationServerMetadataEndpointFilter(ProviderSettings providerSettings) {
|
||||
Assert.notNull(providerSettings, "providerSettings cannot be null");
|
||||
this.providerSettings = providerSettings;
|
||||
this.requestMatcher = new AntPathRequestMatcher(
|
||||
DEFAULT_OAUTH2_AUTHORIZATION_SERVER_CONFIGURATION_ENDPOINT_URI,
|
||||
DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI,
|
||||
HttpMethod.GET.name()
|
||||
);
|
||||
}
|
||||
@@ -68,37 +70,44 @@ public class OAuth2AuthorizationServerConfigurationEndpointFilter extends OncePe
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
|
||||
if (!this.requestMatcher.matches(request)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
OAuth2AuthorizationServerConfiguration authorizationServerConfiguration = OAuth2AuthorizationServerConfiguration
|
||||
.builder()
|
||||
OAuth2AuthorizationServerMetadata authorizationServerMetadata = OAuth2AuthorizationServerMetadata.builder()
|
||||
.issuer(this.providerSettings.issuer())
|
||||
.authorizationEndpoint(asUrl(this.providerSettings.issuer(), this.providerSettings.authorizationEndpoint()))
|
||||
.tokenEndpoint(asUrl(this.providerSettings.issuer(), this.providerSettings.tokenEndpoint()))
|
||||
.tokenEndpointAuthenticationMethod("client_secret_basic") // TODO: Use ClientAuthenticationMethod.CLIENT_SECRET_BASIC in Spring Security 5.5.0
|
||||
.tokenEndpointAuthenticationMethod("client_secret_post") // TODO: Use ClientAuthenticationMethod.CLIENT_SECRET_POST in Spring Security 5.5.0
|
||||
.tokenRevocationEndpoint(asUrl(this.providerSettings.issuer(), this.providerSettings.tokenRevocationEndpoint()))
|
||||
.tokenRevocationEndpointAuthenticationMethod("client_secret_basic") // TODO: Use ClientAuthenticationMethod.CLIENT_SECRET_BASIC in Spring Security 5.5.0
|
||||
.tokenRevocationEndpointAuthenticationMethod("client_secret_post") // TODO: Use ClientAuthenticationMethod.CLIENT_SECRET_POST in Spring Security 5.5.0
|
||||
.jwkSetUri(asUrl(this.providerSettings.issuer(), this.providerSettings.jwkSetEndpoint()))
|
||||
.tokenEndpointAuthenticationMethods(clientAuthenticationMethods())
|
||||
.jwkSetUrl(asUrl(this.providerSettings.issuer(), this.providerSettings.jwkSetEndpoint()))
|
||||
.responseType(OAuth2AuthorizationResponseType.CODE.getValue())
|
||||
.grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())
|
||||
.grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
|
||||
.grantType(AuthorizationGrantType.REFRESH_TOKEN.getValue())
|
||||
.responseType(OAuth2AuthorizationResponseType.CODE.getValue())
|
||||
.scope(OidcScopes.OPENID)
|
||||
.codeChallengeMethod(PkceCodeChallengeMethod2.PLAIN.getValue())
|
||||
.codeChallengeMethod(PkceCodeChallengeMethod2.S256.getValue())
|
||||
.tokenRevocationEndpoint(asUrl(this.providerSettings.issuer(), this.providerSettings.tokenRevocationEndpoint()))
|
||||
.tokenRevocationEndpointAuthenticationMethods(clientAuthenticationMethods())
|
||||
.tokenIntrospectionEndpoint(asUrl(this.providerSettings.issuer(), this.providerSettings.tokenIntrospectionEndpoint()))
|
||||
.tokenIntrospectionEndpointAuthenticationMethods(clientAuthenticationMethods())
|
||||
.codeChallengeMethod("plain")
|
||||
.codeChallengeMethod("S256")
|
||||
.build();
|
||||
|
||||
ServletServerHttpResponse resp = new ServletServerHttpResponse(response);
|
||||
this.authorizationServerConfigurationHttpMessageConverter.write(
|
||||
authorizationServerConfiguration, MediaType.APPLICATION_JSON, resp);
|
||||
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
|
||||
this.authorizationServerMetadataHttpMessageConverter.write(
|
||||
authorizationServerMetadata, MediaType.APPLICATION_JSON, httpResponse);
|
||||
}
|
||||
|
||||
private static Consumer<List<String>> clientAuthenticationMethods() {
|
||||
return (authenticationMethods) -> {
|
||||
authenticationMethods.add("client_secret_basic"); // TODO: Use ClientAuthenticationMethod.CLIENT_SECRET_BASIC in Spring Security 5.5.0
|
||||
authenticationMethods.add("client_secret_post"); // TODO: Use ClientAuthenticationMethod.CLIENT_SECRET_POST in Spring Security 5.5.0
|
||||
};
|
||||
}
|
||||
|
||||
private static String asUrl(String issuer, String endpoint) {
|
||||
return UriComponentsBuilder.fromUriString(issuer).path(endpoint).toUriString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* Copyright 2020-2021 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.
|
||||
@@ -21,6 +21,7 @@ import com.nimbusds.jose.proc.SecurityContext;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
@@ -30,7 +31,7 @@ import org.springframework.security.config.test.SpringTestRule;
|
||||
import org.springframework.security.oauth2.jose.TestJwks;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerConfigurationEndpointFilter;
|
||||
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerMetadataEndpointFilter;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -39,11 +40,11 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* Integration tests for OAuth 2.0 Authorization Server Configuration.
|
||||
* Integration tests for the OAuth 2.0 Authorization Server Metadata endpoint.
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
*/
|
||||
public class OAuth2AuthorizationServerConfigurationTests {
|
||||
public class OAuth2AuthorizationServerMetadataTests {
|
||||
private static final String issuerUrl = "https://example.com/issuer1";
|
||||
private static JWKSource<SecurityContext> jwkSource;
|
||||
|
||||
@@ -60,10 +61,10 @@ public class OAuth2AuthorizationServerConfigurationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenServerConfigurationRequestAndIssuerSetThenReturnServerConfigurationResponse() throws Exception {
|
||||
public void requestWhenAuthorizationServerMetadataRequestAndIssuerSetThenReturnMetadataResponse() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||
|
||||
this.mvc.perform(get(OAuth2AuthorizationServerConfigurationEndpointFilter.DEFAULT_OAUTH2_AUTHORIZATION_SERVER_CONFIGURATION_ENDPOINT_URI))
|
||||
this.mvc.perform(get(OAuth2AuthorizationServerMetadataEndpointFilter.DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpect(jsonPath("issuer").value(issuerUrl))
|
||||
.andReturn();
|
||||
@@ -88,4 +89,5 @@ public class OAuth2AuthorizationServerConfigurationTests {
|
||||
return new ProviderSettings().issuer(issuerUrl);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,585 @@
|
||||
/*
|
||||
* Copyright 2020-2021 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.core;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationServerMetadata.Builder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link OAuth2AuthorizationServerMetadata}.
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
*/
|
||||
public class OAuth2AuthorizationServerMetadataTests {
|
||||
// @formatter:off
|
||||
private final Builder minimalBuilder =
|
||||
OAuth2AuthorizationServerMetadata.builder()
|
||||
.issuer("https://example.com/issuer1")
|
||||
.authorizationEndpoint("https://example.com/issuer1/oauth2/authorize")
|
||||
.tokenEndpoint("https://example.com/issuer1/oauth2/token")
|
||||
.responseType("code");
|
||||
// @formatter:on
|
||||
|
||||
@Test
|
||||
public void buildWhenAllClaimsProvidedThenCreated() {
|
||||
OAuth2AuthorizationServerMetadata authorizationServerMetadata = OAuth2AuthorizationServerMetadata.builder()
|
||||
.issuer("https://example.com/issuer1")
|
||||
.authorizationEndpoint("https://example.com/issuer1/oauth2/authorize")
|
||||
.tokenEndpoint("https://example.com/issuer1/oauth2/token")
|
||||
.tokenEndpointAuthenticationMethod("client_secret_basic")
|
||||
.jwkSetUrl("https://example.com/issuer1/oauth2/jwks")
|
||||
.scope("openid")
|
||||
.responseType("code")
|
||||
.grantType("authorization_code")
|
||||
.grantType("client_credentials")
|
||||
.tokenRevocationEndpoint("https://example.com/issuer1/oauth2/revoke")
|
||||
.tokenRevocationEndpointAuthenticationMethod("client_secret_basic")
|
||||
.tokenIntrospectionEndpoint("https://example.com/issuer1/oauth2/introspect")
|
||||
.tokenIntrospectionEndpointAuthenticationMethod("client_secret_basic")
|
||||
.codeChallengeMethod("plain")
|
||||
.codeChallengeMethod("S256")
|
||||
.claim("a-claim", "a-value")
|
||||
.build();
|
||||
|
||||
assertThat(authorizationServerMetadata.getIssuer()).isEqualTo(url("https://example.com/issuer1"));
|
||||
assertThat(authorizationServerMetadata.getAuthorizationEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/authorize"));
|
||||
assertThat(authorizationServerMetadata.getTokenEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/token"));
|
||||
assertThat(authorizationServerMetadata.getTokenEndpointAuthenticationMethods()).containsExactly("client_secret_basic");
|
||||
assertThat(authorizationServerMetadata.getJwkSetUrl()).isEqualTo(url("https://example.com/issuer1/oauth2/jwks"));
|
||||
assertThat(authorizationServerMetadata.getScopes()).containsExactly("openid");
|
||||
assertThat(authorizationServerMetadata.getResponseTypes()).containsExactly("code");
|
||||
assertThat(authorizationServerMetadata.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", "client_credentials");
|
||||
assertThat(authorizationServerMetadata.getTokenRevocationEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/revoke"));
|
||||
assertThat(authorizationServerMetadata.getTokenRevocationEndpointAuthenticationMethods()).containsExactly("client_secret_basic");
|
||||
assertThat(authorizationServerMetadata.getTokenIntrospectionEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/introspect"));
|
||||
assertThat(authorizationServerMetadata.getTokenIntrospectionEndpointAuthenticationMethods()).containsExactly("client_secret_basic");
|
||||
assertThat(authorizationServerMetadata.getCodeChallengeMethods()).containsExactlyInAnyOrder("plain", "S256");
|
||||
assertThat(authorizationServerMetadata.getClaimAsString("a-claim")).isEqualTo("a-value");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenOnlyRequiredClaimsProvidedThenCreated() {
|
||||
OAuth2AuthorizationServerMetadata authorizationServerMetadata = OAuth2AuthorizationServerMetadata.builder()
|
||||
.issuer("https://example.com/issuer1")
|
||||
.authorizationEndpoint("https://example.com/issuer1/oauth2/authorize")
|
||||
.tokenEndpoint("https://example.com/issuer1/oauth2/token")
|
||||
.responseType("code")
|
||||
.build();
|
||||
|
||||
assertThat(authorizationServerMetadata.getIssuer()).isEqualTo(url("https://example.com/issuer1"));
|
||||
assertThat(authorizationServerMetadata.getAuthorizationEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/authorize"));
|
||||
assertThat(authorizationServerMetadata.getTokenEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/token"));
|
||||
assertThat(authorizationServerMetadata.getTokenEndpointAuthenticationMethods()).isNull();
|
||||
assertThat(authorizationServerMetadata.getJwkSetUrl()).isNull();
|
||||
assertThat(authorizationServerMetadata.getScopes()).isNull();
|
||||
assertThat(authorizationServerMetadata.getResponseTypes()).containsExactly("code");
|
||||
assertThat(authorizationServerMetadata.getGrantTypes()).isNull();
|
||||
assertThat(authorizationServerMetadata.getTokenRevocationEndpoint()).isNull();
|
||||
assertThat(authorizationServerMetadata.getTokenRevocationEndpointAuthenticationMethods()).isNull();
|
||||
assertThat(authorizationServerMetadata.getTokenIntrospectionEndpoint()).isNull();
|
||||
assertThat(authorizationServerMetadata.getTokenIntrospectionEndpointAuthenticationMethods()).isNull();
|
||||
assertThat(authorizationServerMetadata.getCodeChallengeMethods()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withClaimsWhenClaimsProvidedThenCreated() {
|
||||
HashMap<String, Object> claims = new HashMap<>();
|
||||
claims.put(OAuth2AuthorizationServerMetadataClaimNames.ISSUER, "https://example.com/issuer1");
|
||||
claims.put(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT, "https://example.com/issuer1/oauth2/authorize");
|
||||
claims.put(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT, "https://example.com/issuer1/oauth2/token");
|
||||
claims.put(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI, "https://example.com/issuer1/oauth2/jwks");
|
||||
claims.put(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, Collections.singletonList("openid"));
|
||||
claims.put(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, Collections.singletonList("code"));
|
||||
claims.put(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT, "https://example.com/issuer1/oauth2/revoke");
|
||||
claims.put(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT, "https://example.com/issuer1/oauth2/introspect");
|
||||
claims.put("some-claim", "some-value");
|
||||
|
||||
OAuth2AuthorizationServerMetadata authorizationServerMetadata = OAuth2AuthorizationServerMetadata.withClaims(claims).build();
|
||||
|
||||
assertThat(authorizationServerMetadata.getIssuer()).isEqualTo(url("https://example.com/issuer1"));
|
||||
assertThat(authorizationServerMetadata.getAuthorizationEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/authorize"));
|
||||
assertThat(authorizationServerMetadata.getTokenEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/token"));
|
||||
assertThat(authorizationServerMetadata.getTokenEndpointAuthenticationMethods()).isNull();
|
||||
assertThat(authorizationServerMetadata.getJwkSetUrl()).isEqualTo(url("https://example.com/issuer1/oauth2/jwks"));
|
||||
assertThat(authorizationServerMetadata.getScopes()).containsExactly("openid");
|
||||
assertThat(authorizationServerMetadata.getResponseTypes()).containsExactly("code");
|
||||
assertThat(authorizationServerMetadata.getGrantTypes()).isNull();
|
||||
assertThat(authorizationServerMetadata.getTokenRevocationEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/revoke"));
|
||||
assertThat(authorizationServerMetadata.getTokenRevocationEndpointAuthenticationMethods()).isNull();
|
||||
assertThat(authorizationServerMetadata.getTokenIntrospectionEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/introspect"));
|
||||
assertThat(authorizationServerMetadata.getTokenIntrospectionEndpointAuthenticationMethods()).isNull();
|
||||
assertThat(authorizationServerMetadata.getCodeChallengeMethods()).isNull();
|
||||
assertThat(authorizationServerMetadata.getClaimAsString("some-claim")).isEqualTo("some-value");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withClaimsWhenClaimsWithUrlsProvidedThenCreated() {
|
||||
HashMap<String, Object> claims = new HashMap<>();
|
||||
claims.put(OAuth2AuthorizationServerMetadataClaimNames.ISSUER, url("https://example.com/issuer1"));
|
||||
claims.put(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT, url("https://example.com/issuer1/oauth2/authorize"));
|
||||
claims.put(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT, url("https://example.com/issuer1/oauth2/token"));
|
||||
claims.put(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI, url("https://example.com/issuer1/oauth2/jwks"));
|
||||
claims.put(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, Collections.singletonList("code"));
|
||||
claims.put(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT, url("https://example.com/issuer1/oauth2/revoke"));
|
||||
claims.put(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT, url("https://example.com/issuer1/oauth2/introspect"));
|
||||
claims.put("some-claim", "some-value");
|
||||
|
||||
OAuth2AuthorizationServerMetadata authorizationServerMetadata = OAuth2AuthorizationServerMetadata.withClaims(claims).build();
|
||||
|
||||
assertThat(authorizationServerMetadata.getIssuer()).isEqualTo(url("https://example.com/issuer1"));
|
||||
assertThat(authorizationServerMetadata.getAuthorizationEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/authorize"));
|
||||
assertThat(authorizationServerMetadata.getTokenEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/token"));
|
||||
assertThat(authorizationServerMetadata.getTokenEndpointAuthenticationMethods()).isNull();
|
||||
assertThat(authorizationServerMetadata.getJwkSetUrl()).isEqualTo(url("https://example.com/issuer1/oauth2/jwks"));
|
||||
assertThat(authorizationServerMetadata.getScopes()).isNull();
|
||||
assertThat(authorizationServerMetadata.getResponseTypes()).containsExactly("code");
|
||||
assertThat(authorizationServerMetadata.getGrantTypes()).isNull();
|
||||
assertThat(authorizationServerMetadata.getTokenRevocationEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/revoke"));
|
||||
assertThat(authorizationServerMetadata.getTokenRevocationEndpointAuthenticationMethods()).isNull();
|
||||
assertThat(authorizationServerMetadata.getTokenIntrospectionEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/introspect"));
|
||||
assertThat(authorizationServerMetadata.getTokenIntrospectionEndpointAuthenticationMethods()).isNull();
|
||||
assertThat(authorizationServerMetadata.getCodeChallengeMethods()).isNull();
|
||||
assertThat(authorizationServerMetadata.getClaimAsString("some-claim")).isEqualTo("some-value");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withClaimsWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> OAuth2AuthorizationServerMetadata.withClaims(null))
|
||||
.withMessage("claims cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withClaimsWhenMissingRequiredClaimsThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> OAuth2AuthorizationServerMetadata.withClaims(Collections.emptyMap()))
|
||||
.withMessage("claims cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenCalledTwiceThenGeneratesTwoConfigurations() {
|
||||
OAuth2AuthorizationServerMetadata first = this.minimalBuilder
|
||||
.grantType("client_credentials")
|
||||
.build();
|
||||
|
||||
OAuth2AuthorizationServerMetadata second = this.minimalBuilder
|
||||
.claims((claims) ->
|
||||
{
|
||||
List<String> newGrantTypes = new ArrayList<>();
|
||||
newGrantTypes.add("authorization_code");
|
||||
newGrantTypes.add("custom_grant");
|
||||
claims.put(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED, newGrantTypes);
|
||||
}
|
||||
)
|
||||
.build();
|
||||
|
||||
assertThat(first.getGrantTypes()).containsExactly("client_credentials");
|
||||
assertThat(second.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", "custom_grant");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenMissingIssuerThenThrowIllegalArgumentException() {
|
||||
Builder builder = this.minimalBuilder
|
||||
.claims((claims) -> claims.remove(OAuth2AuthorizationServerMetadataClaimNames.ISSUER));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessage("issuer cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenIssuerNotUrlThenThrowIllegalArgumentException() {
|
||||
Builder builder = this.minimalBuilder
|
||||
.claims((claims) -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.ISSUER, "not an url"));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessage("issuer must be a valid URL");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenMissingAuthorizationEndpointThenThrowIllegalArgumentException() {
|
||||
Builder builder = this.minimalBuilder
|
||||
.claims((claims) -> claims.remove(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessage("authorizationEndpoint cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenAuthorizationEndpointNotUrlThenThrowIllegalArgumentException() {
|
||||
Builder builder = this.minimalBuilder
|
||||
.claims((claims) -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT, "not an url"));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessage("authorizationEndpoint must be a valid URL");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenMissingTokenEndpointThenThrowsIllegalArgumentException() {
|
||||
Builder builder = this.minimalBuilder
|
||||
.claims((claims) -> claims.remove(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessage("tokenEndpoint cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenTokenEndpointNotUrlThenThrowIllegalArgumentException() {
|
||||
Builder builder = this.minimalBuilder
|
||||
.claims((claims) -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT, "not an url"));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessage("tokenEndpoint must be a valid URL");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenTokenEndpointAuthenticationMethodsNotListThenThrowIllegalArgumentException() {
|
||||
Builder builder = this.minimalBuilder
|
||||
.claims((claims) -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, "not-a-list"));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessageStartingWith("tokenEndpointAuthenticationMethods must be of type List");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenTokenEndpointAuthenticationMethodsEmptyListThenThrowIllegalArgumentException() {
|
||||
Builder builder = this.minimalBuilder
|
||||
.claims((claims) -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED, Collections.emptyList()));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessage("tokenEndpointAuthenticationMethods cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenTokenEndpointAuthenticationMethodsAddingOrRemovingThenCorrectValues() {
|
||||
OAuth2AuthorizationServerMetadata authorizationServerMetadata = this.minimalBuilder
|
||||
.tokenEndpointAuthenticationMethod("should-be-removed")
|
||||
.tokenEndpointAuthenticationMethods(authMethods -> {
|
||||
authMethods.clear();
|
||||
authMethods.add("some-authentication-method");
|
||||
})
|
||||
.build();
|
||||
|
||||
assertThat(authorizationServerMetadata.getTokenEndpointAuthenticationMethods()).containsExactly("some-authentication-method");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenJwksUriNotUrlThenThrowIllegalArgumentException() {
|
||||
Builder builder = this.minimalBuilder
|
||||
.claims((claims) -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI, "not an url"));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessage("jwksUri must be a valid URL");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenScopesNotListThenThrowIllegalArgumentException() {
|
||||
Builder builder = this.minimalBuilder
|
||||
.claims((claims) -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, "not-a-list"));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessageStartingWith("scopes must be of type List");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenScopesEmptyListThenThrowIllegalArgumentException() {
|
||||
Builder builder = this.minimalBuilder
|
||||
.claims((claims) -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, Collections.emptyList()));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessage("scopes cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenScopesAddingOrRemovingThenCorrectValues() {
|
||||
OAuth2AuthorizationServerMetadata authorizationServerMetadata = this.minimalBuilder
|
||||
.scope("should-be-removed")
|
||||
.scopes(scopes -> {
|
||||
scopes.clear();
|
||||
scopes.add("some-scope");
|
||||
})
|
||||
.build();
|
||||
|
||||
assertThat(authorizationServerMetadata.getScopes()).containsExactly("some-scope");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenMissingResponseTypesThenThrowIllegalArgumentException() {
|
||||
Builder builder = this.minimalBuilder
|
||||
.claims((claims) -> claims.remove(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessage("responseTypes cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenResponseTypesNotListThenThrowIllegalArgumentException() {
|
||||
Builder builder = this.minimalBuilder
|
||||
.claims((claims) -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, "not-a-list"));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessageStartingWith("responseTypes must be of type List");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenResponseTypesEmptyListThenThrowIllegalArgumentException() {
|
||||
Builder builder = this.minimalBuilder
|
||||
.claims((claims) -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, Collections.emptyList()));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessage("responseTypes cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenResponseTypesAddingOrRemovingThenCorrectValues() {
|
||||
OAuth2AuthorizationServerMetadata authorizationServerMetadata = this.minimalBuilder
|
||||
.responseType("should-be-removed")
|
||||
.responseTypes(responseTypes -> {
|
||||
responseTypes.clear();
|
||||
responseTypes.add("some-response-type");
|
||||
})
|
||||
.build();
|
||||
|
||||
assertThat(authorizationServerMetadata.getResponseTypes()).containsExactly("some-response-type");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenResponseTypesNotPresentAndAddingThenCorrectValues() {
|
||||
OAuth2AuthorizationServerMetadata authorizationServerMetadata = this.minimalBuilder
|
||||
.claims(claims -> claims.remove(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED))
|
||||
.responseTypes(responseTypes -> responseTypes.add("some-response-type"))
|
||||
.build();
|
||||
|
||||
assertThat(authorizationServerMetadata.getResponseTypes()).containsExactly("some-response-type");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenGrantTypesNotListThenThrowIllegalArgumentException() {
|
||||
Builder builder = this.minimalBuilder
|
||||
.claims((claims) -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED, "not-a-list"));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessageStartingWith("grantTypes must be of type List");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenGrantTypesEmptyListThenThrowIllegalArgumentException() {
|
||||
Builder builder = this.minimalBuilder
|
||||
.claims((claims) -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED, Collections.emptyList()));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessage("grantTypes cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenGrantTypesAddingOrRemovingThenCorrectValues() {
|
||||
OAuth2AuthorizationServerMetadata authorizationServerMetadata = this.minimalBuilder
|
||||
.grantType("should-be-removed")
|
||||
.grantTypes(grantTypes -> {
|
||||
grantTypes.clear();
|
||||
grantTypes.add("some-grant-type");
|
||||
})
|
||||
.build();
|
||||
|
||||
assertThat(authorizationServerMetadata.getGrantTypes()).containsExactly("some-grant-type");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenTokenRevocationEndpointNotUrlThenThrowIllegalArgumentException() {
|
||||
Builder builder = this.minimalBuilder
|
||||
.tokenRevocationEndpoint("not a valid URL");
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessage("tokenRevocationEndpoint must be a valid URL");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenTokenRevocationEndpointAuthenticationMethodsNotListThenThrowIllegalArgumentException() {
|
||||
Builder builder = this.minimalBuilder
|
||||
.claims((claims) -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED, "not-a-list"));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessageStartingWith("tokenRevocationEndpointAuthenticationMethods must be of type List");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenTokenRevocationEndpointAuthenticationMethodsEmptyListThenThrowIllegalArgumentException() {
|
||||
Builder builder = this.minimalBuilder
|
||||
.claims((claims) -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT_AUTH_METHODS_SUPPORTED, Collections.emptyList()));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessage("tokenRevocationEndpointAuthenticationMethods cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenTokenRevocationEndpointAuthenticationMethodsAddingOrRemovingThenCorrectValues() {
|
||||
OAuth2AuthorizationServerMetadata authorizationServerMetadata = this.minimalBuilder
|
||||
.tokenRevocationEndpointAuthenticationMethod("should-be-removed")
|
||||
.tokenRevocationEndpointAuthenticationMethods(authMethods -> {
|
||||
authMethods.clear();
|
||||
authMethods.add("some-authentication-method");
|
||||
})
|
||||
.build();
|
||||
|
||||
assertThat(authorizationServerMetadata.getTokenRevocationEndpointAuthenticationMethods()).containsExactly("some-authentication-method");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenTokenIntrospectionEndpointNotUrlThenThrowIllegalArgumentException() {
|
||||
Builder builder = this.minimalBuilder
|
||||
.tokenIntrospectionEndpoint("not a valid URL");
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessage("tokenIntrospectionEndpoint must be a valid URL");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenTokenIntrospectionEndpointAuthenticationMethodsNotListThenThrowIllegalArgumentException() {
|
||||
Builder builder = this.minimalBuilder
|
||||
.claims((claims) -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED, "not-a-list"));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessageStartingWith("tokenIntrospectionEndpointAuthenticationMethods must be of type List");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenTokenIntrospectionEndpointAuthenticationMethodsEmptyListThenThrowIllegalArgumentException() {
|
||||
Builder builder = this.minimalBuilder
|
||||
.claims((claims) -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.INTROSPECTION_ENDPOINT_AUTH_METHODS_SUPPORTED, Collections.emptyList()));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessage("tokenIntrospectionEndpointAuthenticationMethods cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenTokenIntrospectionEndpointAuthenticationMethodsAddingOrRemovingThenCorrectValues() {
|
||||
OAuth2AuthorizationServerMetadata authorizationServerMetadata = this.minimalBuilder
|
||||
.tokenIntrospectionEndpointAuthenticationMethod("should-be-removed")
|
||||
.tokenIntrospectionEndpointAuthenticationMethods(authMethods -> {
|
||||
authMethods.clear();
|
||||
authMethods.add("some-authentication-method");
|
||||
})
|
||||
.build();
|
||||
|
||||
assertThat(authorizationServerMetadata.getTokenIntrospectionEndpointAuthenticationMethods()).containsExactly("some-authentication-method");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenCodeChallengeMethodsNotListThenThrowIllegalArgumentException() {
|
||||
Builder builder = this.minimalBuilder
|
||||
.claims((claims) -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED, "not-a-list"));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessageStartingWith("codeChallengeMethods must be of type List");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenCodeChallengeMethodsEmptyListThenThrowIllegalArgumentException() {
|
||||
Builder builder = this.minimalBuilder
|
||||
.claims((claims) -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.CODE_CHALLENGE_METHODS_SUPPORTED, Collections.emptyList()));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessage("codeChallengeMethods cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenCodeChallengeMethodsAddingOrRemovingThenCorrectValues() {
|
||||
OAuth2AuthorizationServerMetadata authorizationServerMetadata = this.minimalBuilder
|
||||
.codeChallengeMethod("should-be-removed")
|
||||
.codeChallengeMethods(codeChallengeMethods -> {
|
||||
codeChallengeMethods.clear();
|
||||
codeChallengeMethods.add("some-authentication-method");
|
||||
})
|
||||
.build();
|
||||
|
||||
assertThat(authorizationServerMetadata.getCodeChallengeMethods()).containsExactly("some-authentication-method");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void claimWhenNameNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> OAuth2AuthorizationServerMetadata.builder().claim(null, "claim-value"))
|
||||
.withMessage("name cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void claimWhenValueNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> OAuth2AuthorizationServerMetadata.builder().claim("claim-name", null))
|
||||
.withMessage("value cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void claimsWhenRemovingClaimThenNotPresent() {
|
||||
OAuth2AuthorizationServerMetadata authorizationServerMetadata =
|
||||
this.minimalBuilder
|
||||
.claim("claim-name", "claim-value")
|
||||
.claims((claims) -> claims.remove("claim-name"))
|
||||
.build();
|
||||
assertThat(authorizationServerMetadata.containsClaim("claim-name")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void claimsWhenAddingClaimThenPresent() {
|
||||
OAuth2AuthorizationServerMetadata authorizationServerMetadata =
|
||||
this.minimalBuilder
|
||||
.claim("claim-name", "claim-value")
|
||||
.build();
|
||||
assertThat(authorizationServerMetadata.containsClaim("claim-name")).isTrue();
|
||||
}
|
||||
|
||||
private static URL url(String urlString) {
|
||||
try {
|
||||
return new URL(urlString);
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalArgumentException("urlString must be a valid URL and valid URI");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,451 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 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.core.endpoint;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationServerMetadataClaimNames;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationServerConfiguration.Builder;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link OAuth2AuthorizationServerConfiguration}.
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
*/
|
||||
public class OAuth2AuthorizationServerConfigurationTests {
|
||||
private final Builder minimalConfigurationBuilder =
|
||||
OAuth2AuthorizationServerConfiguration.builder()
|
||||
.issuer("https://example.com/issuer1")
|
||||
.authorizationEndpoint("https://example.com/issuer1/oauth2/authorize")
|
||||
.tokenEndpoint("https://example.com/issuer1/oauth2/token")
|
||||
.jwkSetUri("https://example.com/issuer1/oauth2/jwks")
|
||||
.scope("openid")
|
||||
.responseType("code");
|
||||
|
||||
@Test
|
||||
public void buildWhenAllRequiredClaimsAndAdditionalClaimsThenCreated() {
|
||||
OAuth2AuthorizationServerConfiguration authorizationServerConfiguration = OAuth2AuthorizationServerConfiguration.builder()
|
||||
.issuer("https://example.com/issuer1")
|
||||
.authorizationEndpoint("https://example.com/issuer1/oauth2/authorize")
|
||||
.tokenEndpoint("https://example.com/issuer1/oauth2/token")
|
||||
.tokenRevocationEndpoint("https://example.com/issuer1/oauth2/revoke")
|
||||
.jwkSetUri("https://example.com/issuer1/oauth2/jwks")
|
||||
.scope("openid")
|
||||
.responseType("code")
|
||||
.grantType("authorization_code")
|
||||
.grantType("client_credentials")
|
||||
.tokenEndpointAuthenticationMethod("client_secret_basic")
|
||||
.tokenRevocationEndpointAuthenticationMethod("client_secret_basic")
|
||||
.codeChallengeMethod("plain")
|
||||
.codeChallengeMethod("S256")
|
||||
.claim("a-claim", "a-value")
|
||||
.build();
|
||||
|
||||
assertThat(authorizationServerConfiguration.getIssuer()).isEqualTo(url("https://example.com/issuer1"));
|
||||
assertThat(authorizationServerConfiguration.getAuthorizationEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/authorize"));
|
||||
assertThat(authorizationServerConfiguration.getTokenEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/token"));
|
||||
assertThat(authorizationServerConfiguration.getTokenRevocationEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/revoke"));
|
||||
assertThat(authorizationServerConfiguration.getJwkSetUri()).isEqualTo(url("https://example.com/issuer1/oauth2/jwks"));
|
||||
assertThat(authorizationServerConfiguration.getScopes()).containsExactly("openid");
|
||||
assertThat(authorizationServerConfiguration.getResponseTypes()).containsExactly("code");
|
||||
assertThat(authorizationServerConfiguration.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", "client_credentials");
|
||||
assertThat(authorizationServerConfiguration.getTokenEndpointAuthenticationMethods()).containsExactly("client_secret_basic");
|
||||
assertThat(authorizationServerConfiguration.getTokenRevocationEndpointAuthenticationMethods()).containsExactly("client_secret_basic");
|
||||
assertThat(authorizationServerConfiguration.getCodeChallengeMethods()).containsExactlyInAnyOrder("plain", "S256");
|
||||
assertThat(authorizationServerConfiguration.getClaimAsString("a-claim")).isEqualTo("a-value");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenOnlyRequiredClaimsThenCreated() {
|
||||
OAuth2AuthorizationServerConfiguration authorizationServerConfiguration = OAuth2AuthorizationServerConfiguration.builder()
|
||||
.issuer("https://example.com/issuer1")
|
||||
.authorizationEndpoint("https://example.com/issuer1/oauth2/authorize")
|
||||
.tokenEndpoint("https://example.com/issuer1/oauth2/token")
|
||||
.jwkSetUri("https://example.com/issuer1/oauth2/jwks")
|
||||
.scope("openid")
|
||||
.responseType("code")
|
||||
.build();
|
||||
|
||||
assertThat(authorizationServerConfiguration.getIssuer()).isEqualTo(url("https://example.com/issuer1"));
|
||||
assertThat(authorizationServerConfiguration.getAuthorizationEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/authorize"));
|
||||
assertThat(authorizationServerConfiguration.getTokenEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/token"));
|
||||
assertThat(authorizationServerConfiguration.getJwkSetUri()).isEqualTo(url("https://example.com/issuer1/oauth2/jwks"));
|
||||
assertThat(authorizationServerConfiguration.getScopes()).containsExactly("openid");
|
||||
assertThat(authorizationServerConfiguration.getResponseTypes()).containsExactly("code");
|
||||
assertThat(authorizationServerConfiguration.getGrantTypes()).isNull();
|
||||
assertThat(authorizationServerConfiguration.getTokenEndpointAuthenticationMethods()).isNull();
|
||||
assertThat(authorizationServerConfiguration.getTokenRevocationEndpoint()).isNull();
|
||||
assertThat(authorizationServerConfiguration.getTokenRevocationEndpointAuthenticationMethods()).isNull();
|
||||
assertThat(authorizationServerConfiguration.getCodeChallengeMethods()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildFromClaimsThenCreated() {
|
||||
HashMap<String, Object> claims = new HashMap<>();
|
||||
claims.put(OAuth2AuthorizationServerMetadataClaimNames.ISSUER, "https://example.com/issuer1");
|
||||
claims.put(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT, "https://example.com/issuer1/oauth2/authorize");
|
||||
claims.put(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT, "https://example.com/issuer1/oauth2/token");
|
||||
claims.put(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI, "https://example.com/issuer1/oauth2/jwks");
|
||||
claims.put(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, Collections.singletonList("openid"));
|
||||
claims.put(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, Collections.singletonList("code"));
|
||||
claims.put("some-claim", "some-value");
|
||||
|
||||
OAuth2AuthorizationServerConfiguration authorizationServerConfiguration = OAuth2AuthorizationServerConfiguration.withClaims(claims).build();
|
||||
|
||||
assertThat(authorizationServerConfiguration.getIssuer()).isEqualTo(url("https://example.com/issuer1"));
|
||||
assertThat(authorizationServerConfiguration.getAuthorizationEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/authorize"));
|
||||
assertThat(authorizationServerConfiguration.getTokenEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/token"));
|
||||
assertThat(authorizationServerConfiguration.getJwkSetUri()).isEqualTo(url("https://example.com/issuer1/oauth2/jwks"));
|
||||
assertThat(authorizationServerConfiguration.getScopes()).containsExactly("openid");
|
||||
assertThat(authorizationServerConfiguration.getResponseTypes()).containsExactly("code");
|
||||
assertThat(authorizationServerConfiguration.getGrantTypes()).isNull();
|
||||
assertThat(authorizationServerConfiguration.getTokenEndpointAuthenticationMethods()).isNull();
|
||||
assertThat(authorizationServerConfiguration.getTokenRevocationEndpoint()).isNull();
|
||||
assertThat(authorizationServerConfiguration.getTokenRevocationEndpointAuthenticationMethods()).isNull();
|
||||
assertThat(authorizationServerConfiguration.getCodeChallengeMethods()).isNull();
|
||||
assertThat(authorizationServerConfiguration.getClaimAsString("some-claim")).isEqualTo("some-value");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildFromClaimsWhenUsingUrlsThenCreated() {
|
||||
HashMap<String, Object> claims = new HashMap<>();
|
||||
claims.put(OAuth2AuthorizationServerMetadataClaimNames.ISSUER, url("https://example.com/issuer1"));
|
||||
claims.put(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT, url("https://example.com/issuer1/oauth2/authorize"));
|
||||
claims.put(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT, url("https://example.com/issuer1/oauth2/token"));
|
||||
claims.put(OAuth2AuthorizationServerMetadataClaimNames.REVOCATION_ENDPOINT, url("https://example.com/issuer1/oauth2/revoke"));
|
||||
claims.put(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI, url("https://example.com/issuer1/oauth2/jwks"));
|
||||
claims.put(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, Collections.singletonList("openid"));
|
||||
claims.put(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, Collections.singletonList("code"));
|
||||
claims.put("some-claim", "some-value");
|
||||
|
||||
OAuth2AuthorizationServerConfiguration authorizationServerConfiguration = OAuth2AuthorizationServerConfiguration.withClaims(claims).build();
|
||||
|
||||
assertThat(authorizationServerConfiguration.getIssuer()).isEqualTo(url("https://example.com/issuer1"));
|
||||
assertThat(authorizationServerConfiguration.getAuthorizationEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/authorize"));
|
||||
assertThat(authorizationServerConfiguration.getTokenEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/token"));
|
||||
assertThat(authorizationServerConfiguration.getTokenRevocationEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/revoke"));
|
||||
assertThat(authorizationServerConfiguration.getJwkSetUri()).isEqualTo(url("https://example.com/issuer1/oauth2/jwks"));
|
||||
assertThat(authorizationServerConfiguration.getScopes()).containsExactly("openid");
|
||||
assertThat(authorizationServerConfiguration.getResponseTypes()).containsExactly("code");
|
||||
assertThat(authorizationServerConfiguration.getGrantTypes()).isNull();
|
||||
assertThat(authorizationServerConfiguration.getTokenEndpointAuthenticationMethods()).isNull();
|
||||
assertThat(authorizationServerConfiguration.getTokenRevocationEndpointAuthenticationMethods()).isNull();
|
||||
assertThat(authorizationServerConfiguration.getCodeChallengeMethods()).isNull();
|
||||
assertThat(authorizationServerConfiguration.getClaimAsString("some-claim")).isEqualTo("some-value");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withClaimsWhenNullThenThrowsIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> OAuth2AuthorizationServerConfiguration.withClaims(null))
|
||||
.withMessage("claims cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withClaimsWhenMissingRequiredClaimsThenThrowsIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> OAuth2AuthorizationServerConfiguration.withClaims(Collections.emptyMap()))
|
||||
.withMessage("claims cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenCalledTwiceThenGeneratesTwoConfigurations() {
|
||||
OAuth2AuthorizationServerConfiguration first = this.minimalConfigurationBuilder
|
||||
.grantType("client_credentials")
|
||||
.build();
|
||||
|
||||
OAuth2AuthorizationServerConfiguration second = this.minimalConfigurationBuilder
|
||||
.claims((claims) ->
|
||||
{
|
||||
LinkedHashSet<String> newGrantTypes = new LinkedHashSet<>();
|
||||
newGrantTypes.add("authorization_code");
|
||||
newGrantTypes.add("custom_grant");
|
||||
claims.put(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED, newGrantTypes);
|
||||
}
|
||||
)
|
||||
.build();
|
||||
|
||||
assertThat(first.getGrantTypes()).containsExactly("client_credentials");
|
||||
assertThat(second.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", "custom_grant");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenEmptyClaimsThenOmitted() {
|
||||
OAuth2AuthorizationServerConfiguration authorizationServerConfiguration = this.minimalConfigurationBuilder
|
||||
.claim("some-claim", Collections.emptyList())
|
||||
.claims(claims -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED, Collections.emptyList()))
|
||||
.build();
|
||||
|
||||
assertThat(authorizationServerConfiguration.getClaimAsStringList("some-claim")).isNull();
|
||||
assertThat(authorizationServerConfiguration.getClaimAsStringList(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenMissingIssuerThenThrowsIllegalArgumentException() {
|
||||
Builder builder = this.minimalConfigurationBuilder
|
||||
.claims((claims) -> claims.remove(OAuth2AuthorizationServerMetadataClaimNames.ISSUER));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessage("issuer cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenIssuerIsNotAnUrlThenThrowsIllegalArgumentException() {
|
||||
Builder builder = this.minimalConfigurationBuilder
|
||||
.claims((claims) -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.ISSUER, "not an url"));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessageStartingWith("issuer must be a valid URL");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenMissingAuthorizationEndpointThenThrowsIllegalArgumentException() {
|
||||
Builder builder = this.minimalConfigurationBuilder
|
||||
.claims((claims) -> claims.remove(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessage("authorizationEndpoint cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenAuthorizationEndpointIsNotAnUrlThenThrowsIllegalArgumentException() {
|
||||
Builder builder = this.minimalConfigurationBuilder
|
||||
.claims((claims) -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT, "not an url"));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessageStartingWith("authorizationEndpoint must be a valid URL");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenMissingTokenEndpointThenThrowsIllegalArgumentException() {
|
||||
Builder builder = this.minimalConfigurationBuilder
|
||||
.claims((claims) -> claims.remove(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessage("tokenEndpoint cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenTokenEndpointIsNotAnUrlThenThrowsIllegalArgumentException() {
|
||||
Builder builder = this.minimalConfigurationBuilder
|
||||
.claims((claims) -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT, "not an url"));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessageStartingWith("tokenEndpoint must be a valid URL");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenMissingJwksUriThenThrowsIllegalArgumentException() {
|
||||
Builder builder = this.minimalConfigurationBuilder
|
||||
.claims((claims) -> claims.remove(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessage("jwksUri cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenJwksUriIsNotAnUrlThenThrowsIllegalArgumentException() {
|
||||
Builder builder = this.minimalConfigurationBuilder
|
||||
.claims((claims) -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI, "not an url"));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessageStartingWith("jwksUri must be a valid URL");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenMissingResponseTypesThenThrowsIllegalArgumentException() {
|
||||
Builder builder = this.minimalConfigurationBuilder
|
||||
.claims((claims) -> claims.remove(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessage("responseTypes cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenResponseTypesNotListThenThrowIllegalArgumentException() {
|
||||
Builder builder = this.minimalConfigurationBuilder
|
||||
.claims((claims) -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, "not-a-list"));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessageStartingWith("responseTypes must be of type List");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenResponseTypesEmptyListThenThrowIllegalArgumentException() {
|
||||
Builder builder = this.minimalConfigurationBuilder
|
||||
.claims((claims) -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, Collections.emptyList()));
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessage("responseTypes cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenInvalidTokenRevocationEndpointThenThrowsIllegalArgumentException() {
|
||||
Builder builder = this.minimalConfigurationBuilder
|
||||
.tokenRevocationEndpoint("not a valid URL");
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(builder::build)
|
||||
.withMessage("tokenRevocationEndpoint must be a valid URL");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void responseTypesWhenAddingOrRemovingThenCorrectValues() {
|
||||
OAuth2AuthorizationServerConfiguration configuration = this.minimalConfigurationBuilder
|
||||
.responseType("should-be-removed")
|
||||
.responseTypes(responseTypes -> {
|
||||
responseTypes.clear();
|
||||
responseTypes.add("some-response-type");
|
||||
})
|
||||
.build();
|
||||
|
||||
assertThat(configuration.getResponseTypes()).containsExactly("some-response-type");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void responseTypesWhenNotPresentAndAddingThenCorrectValues() {
|
||||
OAuth2AuthorizationServerConfiguration configuration = this.minimalConfigurationBuilder
|
||||
.claims(claims -> claims.remove(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED))
|
||||
.responseTypes(responseTypes -> responseTypes.add("some-response-type"))
|
||||
.build();
|
||||
|
||||
assertThat(configuration.getResponseTypes()).containsExactly("some-response-type");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scopesWhenAddingOrRemovingThenCorrectValues() {
|
||||
OAuth2AuthorizationServerConfiguration configuration = this.minimalConfigurationBuilder
|
||||
.scope("should-be-removed")
|
||||
.scopes(scopes -> {
|
||||
scopes.clear();
|
||||
scopes.add("some-scope");
|
||||
})
|
||||
.build();
|
||||
|
||||
assertThat(configuration.getScopes()).containsExactly("some-scope");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void grantTypesWhenAddingOrRemovingThenCorrectValues() {
|
||||
OAuth2AuthorizationServerConfiguration configuration = this.minimalConfigurationBuilder
|
||||
.grantType("should-be-removed")
|
||||
.grantTypes(grantTypes -> {
|
||||
grantTypes.clear();
|
||||
grantTypes.add("some-grant-type");
|
||||
})
|
||||
.build();
|
||||
|
||||
assertThat(configuration.getGrantTypes()).containsExactly("some-grant-type");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tokenEndpointAuthenticationMethodsWhenAddingOrRemovingThenCorrectValues() {
|
||||
OAuth2AuthorizationServerConfiguration configuration = this.minimalConfigurationBuilder
|
||||
.tokenEndpointAuthenticationMethod("should-be-removed")
|
||||
.tokenEndpointAuthenticationMethods(authMethods -> {
|
||||
authMethods.clear();
|
||||
authMethods.add("some-authentication-method");
|
||||
})
|
||||
.build();
|
||||
|
||||
assertThat(configuration.getTokenEndpointAuthenticationMethods()).containsExactly("some-authentication-method");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tokenRevocationEndpointAuthenticationMethodsWhenAddingOrRemovingThenCorrectValues() {
|
||||
OAuth2AuthorizationServerConfiguration configuration = this.minimalConfigurationBuilder
|
||||
.tokenRevocationEndpointAuthenticationMethod("should-be-removed")
|
||||
.tokenRevocationEndpointAuthenticationMethods(authMethods -> {
|
||||
authMethods.clear();
|
||||
authMethods.add("some-authentication-method");
|
||||
})
|
||||
.build();
|
||||
|
||||
assertThat(configuration.getTokenRevocationEndpointAuthenticationMethods()).containsExactly("some-authentication-method");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void codeChallengeMethodsMethodsWhenAddingOrRemovingThenCorrectValues() {
|
||||
OAuth2AuthorizationServerConfiguration configuration = this.minimalConfigurationBuilder
|
||||
.codeChallengeMethod("should-be-removed")
|
||||
.codeChallengeMethods(codeChallengeMethods -> {
|
||||
codeChallengeMethods.clear();
|
||||
codeChallengeMethods.add("some-authentication-method");
|
||||
})
|
||||
.build();
|
||||
|
||||
assertThat(configuration.getCodeChallengeMethods()).containsExactly("some-authentication-method");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void claimWhenNameIsNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> OAuth2AuthorizationServerConfiguration.builder().claim(null, "value"))
|
||||
.withMessage("name cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void claimWhenValueIsNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> OAuth2AuthorizationServerConfiguration.builder().claim("claim-name", null))
|
||||
.withMessage("value cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void claimsWhenRemovingClaimThenNotPresent() {
|
||||
OAuth2AuthorizationServerConfiguration configuration =
|
||||
this.minimalConfigurationBuilder
|
||||
.grantType("some-grant-type")
|
||||
.claims((claims) -> claims.remove(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED))
|
||||
.build();
|
||||
assertThat(configuration.getGrantTypes()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void claimsWhenAddingClaimThenPresent() {
|
||||
OAuth2AuthorizationServerConfiguration configuration =
|
||||
this.minimalConfigurationBuilder
|
||||
.claims((claims) -> claims.put(OAuth2AuthorizationServerMetadataClaimNames.GRANT_TYPES_SUPPORTED, "authorization_code"))
|
||||
.build();
|
||||
assertThat(configuration.getGrantTypes()).containsExactly("authorization_code");
|
||||
}
|
||||
|
||||
private static URL url(String urlString) {
|
||||
try {
|
||||
return new URL(urlString);
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalArgumentException("urlString must be a valid URL and valid URI");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 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.core.endpoint;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* This class is temporary and will be removed after upgrading to Spring Security 5.5.0 GA.
|
||||
*
|
||||
* Tests for {@link PkceCodeChallengeMethod2}.
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
*/
|
||||
public class PkceCodeChallengeMethod2Test {
|
||||
|
||||
@Test
|
||||
public void getValueWhenCodeChallengeMethodPlainThenReturnPlain() {
|
||||
assertThat(PkceCodeChallengeMethod2.PLAIN.getValue()).isEqualTo("plain");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getValueWhenCodeChallengeMethodS256ThenReturnS256() {
|
||||
assertThat(PkceCodeChallengeMethod2.S256.getValue()).isEqualTo("S256");
|
||||
}
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 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.core.http.converter;
|
||||
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
import org.springframework.mock.http.MockHttpOutputMessage;
|
||||
import org.springframework.mock.http.client.MockClientHttpResponse;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationServerConfiguration;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link OAuth2AuthorizationServerConfigurationHttpMessageConverter}
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
*/
|
||||
public class OAuth2AuthorizationServerConfigurationHttpMessageConverterTests {
|
||||
private final OAuth2AuthorizationServerConfigurationHttpMessageConverter messageConverter = new OAuth2AuthorizationServerConfigurationHttpMessageConverter();
|
||||
|
||||
@Test
|
||||
public void supportsWhenOAuth2AuthorizationServerConfigurationThenTrue() {
|
||||
assertThat(this.messageConverter.supports(OAuth2AuthorizationServerConfiguration.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAuthorizationServerConfigurationParametersConverterWhenConverterIsNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.messageConverter.setAuthorizationServerConfigurationParametersConverter(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAuthorizationServerConfigurationConverterWhenConverterIsNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.messageConverter.setAuthorizationServerConfigurationConverter(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readInternalWhenRequiredParametersThenSuccess() throws Exception {
|
||||
// @formatter:off
|
||||
String serverConfigurationResponse = "{\n"
|
||||
+ " \"issuer\": \"https://example.com/issuer1\",\n"
|
||||
+ " \"authorization_endpoint\": \"https://example.com/issuer1/oauth2/authorize\",\n"
|
||||
+ " \"token_endpoint\": \"https://example.com/issuer1/oauth2/token\",\n"
|
||||
+ " \"jwks_uri\": \"https://example.com/issuer1/oauth2/jwks\",\n"
|
||||
+ " \"response_types_supported\": [\"code\"]\n"
|
||||
+ "}\n";
|
||||
// @formatter:on
|
||||
MockClientHttpResponse response = new MockClientHttpResponse(serverConfigurationResponse.getBytes(), HttpStatus.OK);
|
||||
OAuth2AuthorizationServerConfiguration serverConfiguration = this.messageConverter
|
||||
.readInternal(OAuth2AuthorizationServerConfiguration.class, response);
|
||||
|
||||
assertThat(serverConfiguration.getIssuer()).isEqualTo(new URL("https://example.com/issuer1"));
|
||||
assertThat(serverConfiguration.getAuthorizationEndpoint()).isEqualTo(new URL("https://example.com/issuer1/oauth2/authorize"));
|
||||
assertThat(serverConfiguration.getTokenEndpoint()).isEqualTo(new URL("https://example.com/issuer1/oauth2/token"));
|
||||
assertThat(serverConfiguration.getJwkSetUri()).isEqualTo(new URL("https://example.com/issuer1/oauth2/jwks"));
|
||||
assertThat(serverConfiguration.getResponseTypes()).containsExactly("code");
|
||||
assertThat(serverConfiguration.getScopes()).isNull();
|
||||
assertThat(serverConfiguration.getGrantTypes()).isNull();
|
||||
assertThat(serverConfiguration.getTokenEndpointAuthenticationMethods()).isNull();
|
||||
assertThat(serverConfiguration.getCodeChallengeMethods()).isNull();
|
||||
assertThat(serverConfiguration.getTokenRevocationEndpoint()).isNull();
|
||||
assertThat(serverConfiguration.getTokenRevocationEndpointAuthenticationMethods()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readInternalWhenValidParametersThenSuccess() throws Exception {
|
||||
// @formatter:off
|
||||
String serverConfigurationResponse = "{\n"
|
||||
+ " \"issuer\": \"https://example.com/issuer1\",\n"
|
||||
+ " \"authorization_endpoint\": \"https://example.com/issuer1/oauth2/authorize\",\n"
|
||||
+ " \"token_endpoint\": \"https://example.com/issuer1/oauth2/token\",\n"
|
||||
+ " \"revocation_endpoint\": \"https://example.com/issuer1/oauth2/revoke\",\n"
|
||||
+ " \"jwks_uri\": \"https://example.com/issuer1/oauth2/jwks\",\n"
|
||||
+ " \"response_types_supported\": [\"code\"],\n"
|
||||
+ " \"grant_types_supported\": [\"authorization_code\", \"client_credentials\"],\n"
|
||||
+ " \"scopes_supported\": [\"openid\"],\n"
|
||||
+ " \"token_endpoint_auth_methods_supported\": [\"client_secret_basic\"],\n"
|
||||
+ " \"revocation_endpoint_auth_methods_supported\": [\"client_secret_basic\"],\n"
|
||||
+ " \"code_challenge_methods_supported\": [\"plain\",\"S256\"],\n"
|
||||
+ " \"custom_claim\": \"value\",\n"
|
||||
+ " \"custom_collection_claim\": [\"value1\", \"value2\"]\n"
|
||||
+ "}\n";
|
||||
// @formatter:on
|
||||
MockClientHttpResponse response = new MockClientHttpResponse(serverConfigurationResponse.getBytes(), HttpStatus.OK);
|
||||
OAuth2AuthorizationServerConfiguration serverConfiguration = this.messageConverter
|
||||
.readInternal(OAuth2AuthorizationServerConfiguration.class, response);
|
||||
|
||||
assertThat(serverConfiguration.getClaims()).hasSize(13);
|
||||
assertThat(serverConfiguration.getIssuer()).isEqualTo(new URL("https://example.com/issuer1"));
|
||||
assertThat(serverConfiguration.getAuthorizationEndpoint()).isEqualTo(new URL("https://example.com/issuer1/oauth2/authorize"));
|
||||
assertThat(serverConfiguration.getTokenEndpoint()).isEqualTo(new URL("https://example.com/issuer1/oauth2/token"));
|
||||
assertThat(serverConfiguration.getTokenRevocationEndpoint()).isEqualTo(new URL("https://example.com/issuer1/oauth2/revoke"));
|
||||
assertThat(serverConfiguration.getJwkSetUri()).isEqualTo(new URL("https://example.com/issuer1/oauth2/jwks"));
|
||||
assertThat(serverConfiguration.getResponseTypes()).containsExactly("code");
|
||||
assertThat(serverConfiguration.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", "client_credentials");
|
||||
assertThat(serverConfiguration.getScopes()).containsExactly("openid");
|
||||
assertThat(serverConfiguration.getTokenEndpointAuthenticationMethods()).containsExactly("client_secret_basic");
|
||||
assertThat(serverConfiguration.getTokenRevocationEndpointAuthenticationMethods()).containsExactly("client_secret_basic");
|
||||
assertThat(serverConfiguration.getCodeChallengeMethods()).containsExactlyInAnyOrder("plain", "S256");
|
||||
assertThat(serverConfiguration.getClaimAsString("custom_claim")).isEqualTo("value");
|
||||
assertThat(serverConfiguration.getClaimAsStringList("custom_collection_claim")).containsExactlyInAnyOrder("value1", "value2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readInternalWhenFailingConverterThenThrowException() {
|
||||
String errorMessage = "this is not a valid converter";
|
||||
this.messageConverter.setAuthorizationServerConfigurationConverter(source -> {
|
||||
throw new RuntimeException(errorMessage);
|
||||
});
|
||||
MockClientHttpResponse response = new MockClientHttpResponse("{}".getBytes(), HttpStatus.OK);
|
||||
|
||||
assertThatExceptionOfType(HttpMessageNotReadableException.class)
|
||||
.isThrownBy(() -> this.messageConverter.readInternal(OAuth2AuthorizationServerConfiguration.class, response))
|
||||
.withMessageContaining("An error occurred reading the OAuth 2.0 Authorization Server Configuration")
|
||||
.withMessageContaining(errorMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readInternalWhenInvalidOAuth2AuthorizationServerConfigurationThenThrowException() {
|
||||
String providerConfigurationResponse = "{ \"issuer\": null }";
|
||||
MockClientHttpResponse response = new MockClientHttpResponse(providerConfigurationResponse.getBytes(), HttpStatus.OK);
|
||||
|
||||
assertThatExceptionOfType(HttpMessageNotReadableException.class)
|
||||
.isThrownBy(() -> this.messageConverter.readInternal(OAuth2AuthorizationServerConfiguration.class, response))
|
||||
.withMessageContaining("An error occurred reading the OAuth 2.0 Authorization Server Configuration")
|
||||
.withMessageContaining("issuer cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeInternalWhenOAuth2AuthorizationServerConfigurationThenSuccess() {
|
||||
OAuth2AuthorizationServerConfiguration serverConfiguration =
|
||||
OAuth2AuthorizationServerConfiguration
|
||||
.builder()
|
||||
.issuer("https://example.com/issuer1")
|
||||
.authorizationEndpoint("https://example.com/issuer1/oauth2/authorize")
|
||||
.tokenEndpoint("https://example.com/issuer1/oauth2/token")
|
||||
.tokenRevocationEndpoint("https://example.com/issuer1/oauth2/revoke")
|
||||
.jwkSetUri("https://example.com/issuer1/oauth2/jwks")
|
||||
.scope("openid")
|
||||
.responseType("code")
|
||||
.grantType("authorization_code")
|
||||
.grantType("client_credentials")
|
||||
.tokenEndpointAuthenticationMethod("client_secret_basic")
|
||||
.tokenRevocationEndpointAuthenticationMethod("client_secret_basic")
|
||||
.codeChallengeMethod("plain")
|
||||
.codeChallengeMethod("S256")
|
||||
.claim("custom_claim", "value")
|
||||
.claim("custom_collection_claim", Arrays.asList("value1", "value2"))
|
||||
.build();
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
|
||||
this.messageConverter.writeInternal(serverConfiguration, outputMessage);
|
||||
|
||||
String serverConfigurationResponse = outputMessage.getBodyAsString();
|
||||
assertThat(serverConfigurationResponse).contains("\"issuer\":\"https://example.com/issuer1\"");
|
||||
assertThat(serverConfigurationResponse).contains("\"authorization_endpoint\":\"https://example.com/issuer1/oauth2/authorize\"");
|
||||
assertThat(serverConfigurationResponse).contains("\"token_endpoint\":\"https://example.com/issuer1/oauth2/token\"");
|
||||
assertThat(serverConfigurationResponse).contains("\"revocation_endpoint\":\"https://example.com/issuer1/oauth2/revoke\"");
|
||||
assertThat(serverConfigurationResponse).contains("\"jwks_uri\":\"https://example.com/issuer1/oauth2/jwks\"");
|
||||
assertThat(serverConfigurationResponse).contains("\"scopes_supported\":[\"openid\"]");
|
||||
assertThat(serverConfigurationResponse).contains("\"response_types_supported\":[\"code\"]");
|
||||
assertThat(serverConfigurationResponse).contains("\"grant_types_supported\":[\"authorization_code\",\"client_credentials\"]");
|
||||
assertThat(serverConfigurationResponse).contains("\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\"]");
|
||||
assertThat(serverConfigurationResponse).contains("\"revocation_endpoint_auth_methods_supported\":[\"client_secret_basic\"]");
|
||||
assertThat(serverConfigurationResponse).contains("\"code_challenge_methods_supported\":[\"plain\",\"S256\"]");
|
||||
assertThat(serverConfigurationResponse).contains("\"custom_claim\":\"value\"");
|
||||
assertThat(serverConfigurationResponse).contains("\"custom_collection_claim\":[\"value1\",\"value2\"]");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeInternalWhenWriteFailsThenThrowsException() {
|
||||
String errorMessage = "this is not a valid converter";
|
||||
Converter<OAuth2AuthorizationServerConfiguration, Map<String, Object>> failingConverter =
|
||||
source -> {
|
||||
throw new RuntimeException(errorMessage);
|
||||
};
|
||||
this.messageConverter.setAuthorizationServerConfigurationParametersConverter(failingConverter);
|
||||
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
OAuth2AuthorizationServerConfiguration serverConfiguration =
|
||||
OAuth2AuthorizationServerConfiguration
|
||||
.builder()
|
||||
.issuer("https://example.com/issuer1")
|
||||
.authorizationEndpoint("https://example.com/issuer1/oauth2/authorize")
|
||||
.tokenEndpoint("https://example.com/issuer1/oauth2/token")
|
||||
.jwkSetUri("https://example.com/issuer1/oauth2/jwks")
|
||||
.responseType("code")
|
||||
.build();
|
||||
|
||||
assertThatExceptionOfType(HttpMessageNotWritableException.class)
|
||||
.isThrownBy(() -> this.messageConverter.writeInternal(serverConfiguration, outputMessage))
|
||||
.withMessageContaining("An error occurred writing the OAuth 2.0 Authorization Server Configuration")
|
||||
.withMessageContaining(errorMessage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Copyright 2020-2021 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.core.http.converter;
|
||||
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
import org.springframework.mock.http.MockHttpOutputMessage;
|
||||
import org.springframework.mock.http.client.MockClientHttpResponse;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationServerMetadata;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link OAuth2AuthorizationServerMetadataHttpMessageConverter}
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
*/
|
||||
public class OAuth2AuthorizationServerMetadataHttpMessageConverterTests {
|
||||
private final OAuth2AuthorizationServerMetadataHttpMessageConverter messageConverter = new OAuth2AuthorizationServerMetadataHttpMessageConverter();
|
||||
|
||||
@Test
|
||||
public void supportsWhenOAuth2AuthorizationServerMetadataThenTrue() {
|
||||
assertThat(this.messageConverter.supports(OAuth2AuthorizationServerMetadata.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAuthorizationServerMetadataParametersConverterWhenConverterIsNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.messageConverter.setAuthorizationServerMetadataParametersConverter(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAuthorizationServerMetadataConverterWhenConverterIsNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.messageConverter.setAuthorizationServerMetadataConverter(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readInternalWhenRequiredParametersThenSuccess() throws Exception {
|
||||
// @formatter:off
|
||||
String authorizationServerMetadataResponse = "{\n"
|
||||
+ " \"issuer\": \"https://example.com/issuer1\",\n"
|
||||
+ " \"authorization_endpoint\": \"https://example.com/issuer1/oauth2/authorize\",\n"
|
||||
+ " \"token_endpoint\": \"https://example.com/issuer1/oauth2/token\",\n"
|
||||
+ " \"response_types_supported\": [\"code\"]\n"
|
||||
+ "}\n";
|
||||
// @formatter:on
|
||||
MockClientHttpResponse response = new MockClientHttpResponse(authorizationServerMetadataResponse.getBytes(), HttpStatus.OK);
|
||||
OAuth2AuthorizationServerMetadata authorizationServerMetadata = this.messageConverter
|
||||
.readInternal(OAuth2AuthorizationServerMetadata.class, response);
|
||||
|
||||
assertThat(authorizationServerMetadata.getIssuer()).isEqualTo(new URL("https://example.com/issuer1"));
|
||||
assertThat(authorizationServerMetadata.getAuthorizationEndpoint()).isEqualTo(new URL("https://example.com/issuer1/oauth2/authorize"));
|
||||
assertThat(authorizationServerMetadata.getTokenEndpoint()).isEqualTo(new URL("https://example.com/issuer1/oauth2/token"));
|
||||
assertThat(authorizationServerMetadata.getTokenEndpointAuthenticationMethods()).isNull();
|
||||
assertThat(authorizationServerMetadata.getJwkSetUrl()).isNull();
|
||||
assertThat(authorizationServerMetadata.getResponseTypes()).containsExactly("code");
|
||||
assertThat(authorizationServerMetadata.getScopes()).isNull();
|
||||
assertThat(authorizationServerMetadata.getGrantTypes()).isNull();
|
||||
assertThat(authorizationServerMetadata.getTokenRevocationEndpoint()).isNull();
|
||||
assertThat(authorizationServerMetadata.getTokenRevocationEndpointAuthenticationMethods()).isNull();
|
||||
assertThat(authorizationServerMetadata.getTokenIntrospectionEndpoint()).isNull();
|
||||
assertThat(authorizationServerMetadata.getTokenIntrospectionEndpointAuthenticationMethods()).isNull();
|
||||
assertThat(authorizationServerMetadata.getCodeChallengeMethods()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readInternalWhenValidParametersThenSuccess() throws Exception {
|
||||
// @formatter:off
|
||||
String authorizationServerMetadataResponse = "{\n"
|
||||
+ " \"issuer\": \"https://example.com/issuer1\",\n"
|
||||
+ " \"authorization_endpoint\": \"https://example.com/issuer1/oauth2/authorize\",\n"
|
||||
+ " \"token_endpoint\": \"https://example.com/issuer1/oauth2/token\",\n"
|
||||
+ " \"token_endpoint_auth_methods_supported\": [\"client_secret_basic\"],\n"
|
||||
+ " \"jwks_uri\": \"https://example.com/issuer1/oauth2/jwks\",\n"
|
||||
+ " \"scopes_supported\": [\"openid\"],\n"
|
||||
+ " \"response_types_supported\": [\"code\"],\n"
|
||||
+ " \"grant_types_supported\": [\"authorization_code\", \"client_credentials\"],\n"
|
||||
+ " \"revocation_endpoint\": \"https://example.com/issuer1/oauth2/revoke\",\n"
|
||||
+ " \"revocation_endpoint_auth_methods_supported\": [\"client_secret_basic\"],\n"
|
||||
+ " \"introspection_endpoint\": \"https://example.com/issuer1/oauth2/introspect\",\n"
|
||||
+ " \"introspection_endpoint_auth_methods_supported\": [\"client_secret_basic\"],\n"
|
||||
+ " \"code_challenge_methods_supported\": [\"plain\",\"S256\"],\n"
|
||||
+ " \"custom_claim\": \"value\",\n"
|
||||
+ " \"custom_collection_claim\": [\"value1\", \"value2\"]\n"
|
||||
+ "}\n";
|
||||
// @formatter:on
|
||||
MockClientHttpResponse response = new MockClientHttpResponse(authorizationServerMetadataResponse.getBytes(), HttpStatus.OK);
|
||||
OAuth2AuthorizationServerMetadata authorizationServerMetadata = this.messageConverter
|
||||
.readInternal(OAuth2AuthorizationServerMetadata.class, response);
|
||||
|
||||
assertThat(authorizationServerMetadata.getClaims()).hasSize(15);
|
||||
assertThat(authorizationServerMetadata.getIssuer()).isEqualTo(new URL("https://example.com/issuer1"));
|
||||
assertThat(authorizationServerMetadata.getAuthorizationEndpoint()).isEqualTo(new URL("https://example.com/issuer1/oauth2/authorize"));
|
||||
assertThat(authorizationServerMetadata.getTokenEndpoint()).isEqualTo(new URL("https://example.com/issuer1/oauth2/token"));
|
||||
assertThat(authorizationServerMetadata.getTokenEndpointAuthenticationMethods()).containsExactly("client_secret_basic");
|
||||
assertThat(authorizationServerMetadata.getJwkSetUrl()).isEqualTo(new URL("https://example.com/issuer1/oauth2/jwks"));
|
||||
assertThat(authorizationServerMetadata.getScopes()).containsExactly("openid");
|
||||
assertThat(authorizationServerMetadata.getResponseTypes()).containsExactly("code");
|
||||
assertThat(authorizationServerMetadata.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", "client_credentials");
|
||||
assertThat(authorizationServerMetadata.getTokenRevocationEndpoint()).isEqualTo(new URL("https://example.com/issuer1/oauth2/revoke"));
|
||||
assertThat(authorizationServerMetadata.getTokenRevocationEndpointAuthenticationMethods()).containsExactly("client_secret_basic");
|
||||
assertThat(authorizationServerMetadata.getTokenIntrospectionEndpoint()).isEqualTo(new URL("https://example.com/issuer1/oauth2/introspect"));
|
||||
assertThat(authorizationServerMetadata.getTokenIntrospectionEndpointAuthenticationMethods()).containsExactly("client_secret_basic");
|
||||
assertThat(authorizationServerMetadata.getCodeChallengeMethods()).containsExactlyInAnyOrder("plain", "S256");
|
||||
assertThat(authorizationServerMetadata.getClaimAsString("custom_claim")).isEqualTo("value");
|
||||
assertThat(authorizationServerMetadata.getClaimAsStringList("custom_collection_claim")).containsExactlyInAnyOrder("value1", "value2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readInternalWhenFailingConverterThenThrowException() {
|
||||
String errorMessage = "this is not a valid converter";
|
||||
this.messageConverter.setAuthorizationServerMetadataConverter(source -> {
|
||||
throw new RuntimeException(errorMessage);
|
||||
});
|
||||
MockClientHttpResponse response = new MockClientHttpResponse("{}".getBytes(), HttpStatus.OK);
|
||||
|
||||
assertThatExceptionOfType(HttpMessageNotReadableException.class)
|
||||
.isThrownBy(() -> this.messageConverter.readInternal(OAuth2AuthorizationServerMetadata.class, response))
|
||||
.withMessageContaining("An error occurred reading the OAuth 2.0 Authorization Server Metadata")
|
||||
.withMessageContaining(errorMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readInternalWhenInvalidOAuth2AuthorizationServerMetadataThenThrowException() {
|
||||
String authorizationServerMetadataResponse = "{ \"issuer\": null }";
|
||||
MockClientHttpResponse response = new MockClientHttpResponse(authorizationServerMetadataResponse.getBytes(), HttpStatus.OK);
|
||||
|
||||
assertThatExceptionOfType(HttpMessageNotReadableException.class)
|
||||
.isThrownBy(() -> this.messageConverter.readInternal(OAuth2AuthorizationServerMetadata.class, response))
|
||||
.withMessageContaining("An error occurred reading the OAuth 2.0 Authorization Server Metadata")
|
||||
.withMessageContaining("issuer cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeInternalWhenOAuth2AuthorizationServerMetadataThenSuccess() {
|
||||
OAuth2AuthorizationServerMetadata authorizationServerMetadata =
|
||||
OAuth2AuthorizationServerMetadata.builder()
|
||||
.issuer("https://example.com/issuer1")
|
||||
.authorizationEndpoint("https://example.com/issuer1/oauth2/authorize")
|
||||
.tokenEndpoint("https://example.com/issuer1/oauth2/token")
|
||||
.tokenEndpointAuthenticationMethod("client_secret_basic")
|
||||
.jwkSetUrl("https://example.com/issuer1/oauth2/jwks")
|
||||
.scope("openid")
|
||||
.responseType("code")
|
||||
.grantType("authorization_code")
|
||||
.grantType("client_credentials")
|
||||
.tokenRevocationEndpoint("https://example.com/issuer1/oauth2/revoke")
|
||||
.tokenRevocationEndpointAuthenticationMethod("client_secret_basic")
|
||||
.tokenIntrospectionEndpoint("https://example.com/issuer1/oauth2/introspect")
|
||||
.tokenIntrospectionEndpointAuthenticationMethod("client_secret_basic")
|
||||
.codeChallengeMethod("plain")
|
||||
.codeChallengeMethod("S256")
|
||||
.claim("custom_claim", "value")
|
||||
.claim("custom_collection_claim", Arrays.asList("value1", "value2"))
|
||||
.build();
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
|
||||
this.messageConverter.writeInternal(authorizationServerMetadata, outputMessage);
|
||||
|
||||
String authorizationServerMetadataResponse = outputMessage.getBodyAsString();
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"issuer\":\"https://example.com/issuer1\"");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"authorization_endpoint\":\"https://example.com/issuer1/oauth2/authorize\"");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"token_endpoint\":\"https://example.com/issuer1/oauth2/token\"");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\"]");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"jwks_uri\":\"https://example.com/issuer1/oauth2/jwks\"");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"scopes_supported\":[\"openid\"]");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"response_types_supported\":[\"code\"]");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"grant_types_supported\":[\"authorization_code\",\"client_credentials\"]");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"revocation_endpoint\":\"https://example.com/issuer1/oauth2/revoke\"");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"revocation_endpoint_auth_methods_supported\":[\"client_secret_basic\"]");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"introspection_endpoint\":\"https://example.com/issuer1/oauth2/introspect\"");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"introspection_endpoint_auth_methods_supported\":[\"client_secret_basic\"]");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"code_challenge_methods_supported\":[\"plain\",\"S256\"]");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"custom_claim\":\"value\"");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"custom_collection_claim\":[\"value1\",\"value2\"]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeInternalWhenWriteFailsThenThrowException() {
|
||||
String errorMessage = "this is not a valid converter";
|
||||
Converter<OAuth2AuthorizationServerMetadata, Map<String, Object>> failingConverter =
|
||||
source -> {
|
||||
throw new RuntimeException(errorMessage);
|
||||
};
|
||||
this.messageConverter.setAuthorizationServerMetadataParametersConverter(failingConverter);
|
||||
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
OAuth2AuthorizationServerMetadata authorizationServerMetadata =
|
||||
OAuth2AuthorizationServerMetadata.builder()
|
||||
.issuer("https://example.com/issuer1")
|
||||
.authorizationEndpoint("https://example.com/issuer1/oauth2/authorize")
|
||||
.tokenEndpoint("https://example.com/issuer1/oauth2/token")
|
||||
.responseType("code")
|
||||
.build();
|
||||
|
||||
assertThatExceptionOfType(HttpMessageNotWritableException.class)
|
||||
.isThrownBy(() -> this.messageConverter.writeInternal(authorizationServerMetadata, outputMessage))
|
||||
.withMessageContaining("An error occurred writing the OAuth 2.0 Authorization Server Metadata")
|
||||
.withMessageContaining(errorMessage);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* Copyright 2020-2021 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.
|
||||
@@ -15,14 +15,14 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.core.oidc;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
@@ -38,7 +38,7 @@ public class OidcProviderConfigurationTests {
|
||||
.issuer("https://example.com/issuer1")
|
||||
.authorizationEndpoint("https://example.com/issuer1/oauth2/authorize")
|
||||
.tokenEndpoint("https://example.com/issuer1/oauth2/token")
|
||||
.jwkSetUri("https://example.com/issuer1/oauth2/jwks")
|
||||
.jwkSetUrl("https://example.com/issuer1/oauth2/jwks")
|
||||
.scope("openid")
|
||||
.responseType("code")
|
||||
.subjectType("public")
|
||||
@@ -50,7 +50,7 @@ public class OidcProviderConfigurationTests {
|
||||
.issuer("https://example.com/issuer1")
|
||||
.authorizationEndpoint("https://example.com/issuer1/oauth2/authorize")
|
||||
.tokenEndpoint("https://example.com/issuer1/oauth2/token")
|
||||
.jwkSetUri("https://example.com/issuer1/oauth2/jwks")
|
||||
.jwkSetUrl("https://example.com/issuer1/oauth2/jwks")
|
||||
.scope("openid")
|
||||
.responseType("code")
|
||||
.grantType("authorization_code")
|
||||
@@ -64,7 +64,7 @@ public class OidcProviderConfigurationTests {
|
||||
assertThat(providerConfiguration.getIssuer()).isEqualTo(url("https://example.com/issuer1"));
|
||||
assertThat(providerConfiguration.getAuthorizationEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/authorize"));
|
||||
assertThat(providerConfiguration.getTokenEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/token"));
|
||||
assertThat(providerConfiguration.getJwkSetUri()).isEqualTo(url("https://example.com/issuer1/oauth2/jwks"));
|
||||
assertThat(providerConfiguration.getJwkSetUrl()).isEqualTo(url("https://example.com/issuer1/oauth2/jwks"));
|
||||
assertThat(providerConfiguration.getScopes()).containsExactly("openid");
|
||||
assertThat(providerConfiguration.getResponseTypes()).containsExactly("code");
|
||||
assertThat(providerConfiguration.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", "client_credentials");
|
||||
@@ -80,7 +80,7 @@ public class OidcProviderConfigurationTests {
|
||||
.issuer("https://example.com/issuer1")
|
||||
.authorizationEndpoint("https://example.com/issuer1/oauth2/authorize")
|
||||
.tokenEndpoint("https://example.com/issuer1/oauth2/token")
|
||||
.jwkSetUri("https://example.com/issuer1/oauth2/jwks")
|
||||
.jwkSetUrl("https://example.com/issuer1/oauth2/jwks")
|
||||
.scope("openid")
|
||||
.responseType("code")
|
||||
.subjectType("public")
|
||||
@@ -90,7 +90,7 @@ public class OidcProviderConfigurationTests {
|
||||
assertThat(providerConfiguration.getIssuer()).isEqualTo(url("https://example.com/issuer1"));
|
||||
assertThat(providerConfiguration.getAuthorizationEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/authorize"));
|
||||
assertThat(providerConfiguration.getTokenEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/token"));
|
||||
assertThat(providerConfiguration.getJwkSetUri()).isEqualTo(url("https://example.com/issuer1/oauth2/jwks"));
|
||||
assertThat(providerConfiguration.getJwkSetUrl()).isEqualTo(url("https://example.com/issuer1/oauth2/jwks"));
|
||||
assertThat(providerConfiguration.getScopes()).containsExactly("openid");
|
||||
assertThat(providerConfiguration.getResponseTypes()).containsExactly("code");
|
||||
assertThat(providerConfiguration.getGrantTypes()).isNull();
|
||||
@@ -117,7 +117,7 @@ public class OidcProviderConfigurationTests {
|
||||
assertThat(providerConfiguration.getIssuer()).isEqualTo(url("https://example.com/issuer1"));
|
||||
assertThat(providerConfiguration.getAuthorizationEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/authorize"));
|
||||
assertThat(providerConfiguration.getTokenEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/token"));
|
||||
assertThat(providerConfiguration.getJwkSetUri()).isEqualTo(url("https://example.com/issuer1/oauth2/jwks"));
|
||||
assertThat(providerConfiguration.getJwkSetUrl()).isEqualTo(url("https://example.com/issuer1/oauth2/jwks"));
|
||||
assertThat(providerConfiguration.getScopes()).containsExactly("openid");
|
||||
assertThat(providerConfiguration.getResponseTypes()).containsExactly("code");
|
||||
assertThat(providerConfiguration.getGrantTypes()).isNull();
|
||||
@@ -145,7 +145,7 @@ public class OidcProviderConfigurationTests {
|
||||
assertThat(providerConfiguration.getIssuer()).isEqualTo(url("https://example.com/issuer1"));
|
||||
assertThat(providerConfiguration.getAuthorizationEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/authorize"));
|
||||
assertThat(providerConfiguration.getTokenEndpoint()).isEqualTo(url("https://example.com/issuer1/oauth2/token"));
|
||||
assertThat(providerConfiguration.getJwkSetUri()).isEqualTo(url("https://example.com/issuer1/oauth2/jwks"));
|
||||
assertThat(providerConfiguration.getJwkSetUrl()).isEqualTo(url("https://example.com/issuer1/oauth2/jwks"));
|
||||
assertThat(providerConfiguration.getScopes()).containsExactly("openid");
|
||||
assertThat(providerConfiguration.getResponseTypes()).containsExactly("code");
|
||||
assertThat(providerConfiguration.getGrantTypes()).isNull();
|
||||
@@ -178,7 +178,7 @@ public class OidcProviderConfigurationTests {
|
||||
OidcProviderConfiguration second = this.minimalConfigurationBuilder
|
||||
.claims((claims) ->
|
||||
{
|
||||
Set<String> newGrantTypes = new LinkedHashSet<>();
|
||||
List<String> newGrantTypes = new ArrayList<>();
|
||||
newGrantTypes.add("authorization_code");
|
||||
newGrantTypes.add("custom_grant");
|
||||
claims.put(OidcProviderMetadataClaimNames.GRANT_TYPES_SUPPORTED, newGrantTypes);
|
||||
@@ -190,17 +190,6 @@ public class OidcProviderConfigurationTests {
|
||||
assertThat(second.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", "custom_grant");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenEmptyClaimsThenOmitted() {
|
||||
OidcProviderConfiguration providerConfiguration = this.minimalConfigurationBuilder
|
||||
.claim("some-claim", Collections.emptyList())
|
||||
.claims(claims -> claims.put(OidcProviderMetadataClaimNames.GRANT_TYPES_SUPPORTED, Collections.emptyList()))
|
||||
.build();
|
||||
|
||||
assertThat(providerConfiguration.getClaimAsStringList("some-claim")).isNull();
|
||||
assertThat(providerConfiguration.getClaimAsStringList(OidcProviderMetadataClaimNames.GRANT_TYPES_SUPPORTED)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenMissingIssuerThenThrowIllegalArgumentException() {
|
||||
OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder
|
||||
@@ -505,9 +494,9 @@ public class OidcProviderConfigurationTests {
|
||||
public void claimsWhenAddingClaimThenPresent() {
|
||||
OidcProviderConfiguration configuration =
|
||||
this.minimalConfigurationBuilder
|
||||
.claims((claims) -> claims.put(OidcProviderMetadataClaimNames.GRANT_TYPES_SUPPORTED, "authorization_code"))
|
||||
.claim("claim-name", "claim-value")
|
||||
.build();
|
||||
assertThat(configuration.getGrantTypes()).containsExactly("authorization_code");
|
||||
assertThat(configuration.containsClaim("claim-name")).isTrue();
|
||||
}
|
||||
|
||||
private static URL url(String urlString) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* Copyright 2020-2021 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.
|
||||
@@ -15,7 +15,12 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.core.oidc.http.converter;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
@@ -24,10 +29,6 @@ import org.springframework.mock.http.MockHttpOutputMessage;
|
||||
import org.springframework.mock.http.client.MockClientHttpResponse;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcProviderConfiguration;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
@@ -75,7 +76,7 @@ public class OidcProviderConfigurationHttpMessageConverterTests {
|
||||
assertThat(providerConfiguration.getIssuer()).isEqualTo(new URL("https://example.com/issuer1"));
|
||||
assertThat(providerConfiguration.getAuthorizationEndpoint()).isEqualTo(new URL("https://example.com/issuer1/oauth2/authorize"));
|
||||
assertThat(providerConfiguration.getTokenEndpoint()).isEqualTo(new URL("https://example.com/issuer1/oauth2/token"));
|
||||
assertThat(providerConfiguration.getJwkSetUri()).isEqualTo(new URL("https://example.com/issuer1/oauth2/jwks"));
|
||||
assertThat(providerConfiguration.getJwkSetUrl()).isEqualTo(new URL("https://example.com/issuer1/oauth2/jwks"));
|
||||
assertThat(providerConfiguration.getResponseTypes()).containsExactly("code");
|
||||
assertThat(providerConfiguration.getSubjectTypes()).containsExactly("public");
|
||||
assertThat(providerConfiguration.getIdTokenSigningAlgorithms()).containsExactly("RS256");
|
||||
@@ -109,7 +110,7 @@ public class OidcProviderConfigurationHttpMessageConverterTests {
|
||||
assertThat(providerConfiguration.getIssuer()).isEqualTo(new URL("https://example.com/issuer1"));
|
||||
assertThat(providerConfiguration.getAuthorizationEndpoint()).isEqualTo(new URL("https://example.com/issuer1/oauth2/authorize"));
|
||||
assertThat(providerConfiguration.getTokenEndpoint()).isEqualTo(new URL("https://example.com/issuer1/oauth2/token"));
|
||||
assertThat(providerConfiguration.getJwkSetUri()).isEqualTo(new URL("https://example.com/issuer1/oauth2/jwks"));
|
||||
assertThat(providerConfiguration.getJwkSetUrl()).isEqualTo(new URL("https://example.com/issuer1/oauth2/jwks"));
|
||||
assertThat(providerConfiguration.getScopes()).containsExactly("openid");
|
||||
assertThat(providerConfiguration.getResponseTypes()).containsExactly("code");
|
||||
assertThat(providerConfiguration.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", "client_credentials");
|
||||
@@ -152,7 +153,7 @@ public class OidcProviderConfigurationHttpMessageConverterTests {
|
||||
.issuer("https://example.com/issuer1")
|
||||
.authorizationEndpoint("https://example.com/issuer1/oauth2/authorize")
|
||||
.tokenEndpoint("https://example.com/issuer1/oauth2/token")
|
||||
.jwkSetUri("https://example.com/issuer1/oauth2/jwks")
|
||||
.jwkSetUrl("https://example.com/issuer1/oauth2/jwks")
|
||||
.scope("openid")
|
||||
.responseType("code")
|
||||
.grantType("authorization_code")
|
||||
@@ -196,7 +197,7 @@ public class OidcProviderConfigurationHttpMessageConverterTests {
|
||||
.issuer("https://example.com/issuer1")
|
||||
.authorizationEndpoint("https://example.com/issuer1/oauth2/authorize")
|
||||
.tokenEndpoint("https://example.com/issuer1/oauth2/token")
|
||||
.jwkSetUri("https://example.com/issuer1/oauth2/jwks")
|
||||
.jwkSetUrl("https://example.com/issuer1/oauth2/jwks")
|
||||
.responseType("code")
|
||||
.subjectType("public")
|
||||
.idTokenSigningAlgorithm("RS256")
|
||||
|
||||
@@ -18,7 +18,7 @@ package org.springframework.security.oauth2.server.authorization.config;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* Tests for {@link ProviderSettings}.
|
||||
@@ -78,48 +78,48 @@ public class ProviderSettingsTests {
|
||||
@Test
|
||||
public void issuerWhenNullThenThrowIllegalArgumentException() {
|
||||
ProviderSettings settings = new ProviderSettings();
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> settings.issuer(null))
|
||||
.withMessage("value cannot be null");
|
||||
assertThatThrownBy(() -> settings.issuer(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("value cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authorizationEndpointWhenNullThenThrowIllegalArgumentException() {
|
||||
ProviderSettings settings = new ProviderSettings();
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> settings.authorizationEndpoint(null))
|
||||
.withMessage("value cannot be null");
|
||||
assertThatThrownBy(() -> settings.authorizationEndpoint(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("value cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tokenEndpointWhenNullThenThrowIllegalArgumentException() {
|
||||
ProviderSettings settings = new ProviderSettings();
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> settings.tokenEndpoint(null))
|
||||
.withMessage("value cannot be null");
|
||||
assertThatThrownBy(() -> settings.tokenEndpoint(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("value cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tokenRevocationEndpointWhenNullThenThrowIllegalArgumentException() {
|
||||
ProviderSettings settings = new ProviderSettings();
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> settings.tokenRevocationEndpoint(null))
|
||||
.withMessage("value cannot be null");
|
||||
assertThatThrownBy(() -> settings.tokenRevocationEndpoint(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("value cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tokenIntrospectionEndpointWhenNullThenThrowIllegalArgumentException() {
|
||||
ProviderSettings settings = new ProviderSettings();
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> settings.tokenIntrospectionEndpoint(null))
|
||||
.withMessage("value cannot be null");
|
||||
assertThatThrownBy(() -> settings.tokenIntrospectionEndpoint(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("value cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jwksEndpointWhenNullThenThrowIllegalArgumentException() {
|
||||
ProviderSettings settings = new ProviderSettings();
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> settings.jwkSetEndpoint(null))
|
||||
.withMessage("value cannot be null");
|
||||
assertThatThrownBy(() -> settings.jwkSetEndpoint(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("value cannot be null");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* Copyright 2020-2021 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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* Copyright 2020-2021 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.
|
||||
@@ -16,16 +16,17 @@
|
||||
package org.springframework.security.oauth2.server.authorization.web;
|
||||
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
@@ -34,23 +35,23 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
|
||||
/**
|
||||
* Tests for {@link OAuth2AuthorizationServerConfigurationEndpointFilter}.
|
||||
* Tests for {@link OAuth2AuthorizationServerMetadataEndpointFilter}.
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
*/
|
||||
public class OAuth2AuthorizationServerConfigurationEndpointFilterTests {
|
||||
public class OAuth2AuthorizationServerMetadataEndpointFilterTests {
|
||||
|
||||
@Test
|
||||
public void constructorWhenProviderSettingsNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new OAuth2AuthorizationServerConfigurationEndpointFilter(null))
|
||||
.isThrownBy(() -> new OAuth2AuthorizationServerMetadataEndpointFilter(null))
|
||||
.withMessage("providerSettings cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenNotAuthorizationServerConfigurationRequestThenNotProcessed() throws Exception {
|
||||
OAuth2AuthorizationServerConfigurationEndpointFilter filter =
|
||||
new OAuth2AuthorizationServerConfigurationEndpointFilter(new ProviderSettings().issuer("https://example.com"));
|
||||
public void doFilterWhenNotAuthorizationServerMetadataRequestThenNotProcessed() throws Exception {
|
||||
OAuth2AuthorizationServerMetadataEndpointFilter filter =
|
||||
new OAuth2AuthorizationServerMetadataEndpointFilter(new ProviderSettings().issuer("https://example.com"));
|
||||
|
||||
String requestUri = "/path";
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||
@@ -64,11 +65,11 @@ public class OAuth2AuthorizationServerConfigurationEndpointFilterTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenAuthorizationServerConfigurationRequestPostThenNotProcessed() throws Exception {
|
||||
OAuth2AuthorizationServerConfigurationEndpointFilter filter =
|
||||
new OAuth2AuthorizationServerConfigurationEndpointFilter(new ProviderSettings().issuer("https://example.com"));
|
||||
public void doFilterWhenAuthorizationServerMetadataRequestPostThenNotProcessed() throws Exception {
|
||||
OAuth2AuthorizationServerMetadataEndpointFilter filter =
|
||||
new OAuth2AuthorizationServerMetadataEndpointFilter(new ProviderSettings().issuer("https://example.com"));
|
||||
|
||||
String requestUri = OAuth2AuthorizationServerConfigurationEndpointFilter.DEFAULT_OAUTH2_AUTHORIZATION_SERVER_CONFIGURATION_ENDPOINT_URI;
|
||||
String requestUri = OAuth2AuthorizationServerMetadataEndpointFilter.DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI;
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri);
|
||||
request.setServletPath(requestUri);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
@@ -80,22 +81,24 @@ public class OAuth2AuthorizationServerConfigurationEndpointFilterTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenAuthorizationServerConfigurationRequestThenAuthorizationServerConfigurationResponse() throws Exception {
|
||||
public void doFilterWhenAuthorizationServerMetadataRequestThenMetadataResponse() throws Exception {
|
||||
String authorizationEndpoint = "/oauth2/v1/authorize";
|
||||
String tokenEndpoint = "/oauth2/v1/token";
|
||||
String tokenRevocationEndpoint = "/oauth2/v1/revoke";
|
||||
String jwkSetEndpoint = "/oauth2/v1/jwks";
|
||||
String tokenRevocationEndpoint = "/oauth2/v1/revoke";
|
||||
String tokenIntrospectionEndpoint = "/oauth2/v1/introspect";
|
||||
|
||||
ProviderSettings providerSettings = new ProviderSettings()
|
||||
.issuer("https://example.com/issuer1")
|
||||
.authorizationEndpoint(authorizationEndpoint)
|
||||
.tokenEndpoint(tokenEndpoint)
|
||||
.jwkSetEndpoint(jwkSetEndpoint)
|
||||
.tokenRevocationEndpoint(tokenRevocationEndpoint)
|
||||
.jwkSetEndpoint(jwkSetEndpoint);
|
||||
OAuth2AuthorizationServerConfigurationEndpointFilter filter =
|
||||
new OAuth2AuthorizationServerConfigurationEndpointFilter(providerSettings);
|
||||
.tokenIntrospectionEndpoint(tokenIntrospectionEndpoint);
|
||||
OAuth2AuthorizationServerMetadataEndpointFilter filter =
|
||||
new OAuth2AuthorizationServerMetadataEndpointFilter(providerSettings);
|
||||
|
||||
String requestUri = OAuth2AuthorizationServerConfigurationEndpointFilter.DEFAULT_OAUTH2_AUTHORIZATION_SERVER_CONFIGURATION_ENDPOINT_URI;
|
||||
String requestUri = OAuth2AuthorizationServerMetadataEndpointFilter.DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI;
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||
request.setServletPath(requestUri);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
@@ -106,28 +109,29 @@ public class OAuth2AuthorizationServerConfigurationEndpointFilterTests {
|
||||
verifyNoInteractions(filterChain);
|
||||
|
||||
assertThat(response.getContentType()).isEqualTo(MediaType.APPLICATION_JSON_VALUE);
|
||||
String serverConfigurationResponse = response.getContentAsString();
|
||||
assertThat(serverConfigurationResponse).contains("\"issuer\":\"https://example.com/issuer1\"");
|
||||
assertThat(serverConfigurationResponse).contains("\"authorization_endpoint\":\"https://example.com/issuer1/oauth2/v1/authorize\"");
|
||||
assertThat(serverConfigurationResponse).contains("\"token_endpoint\":\"https://example.com/issuer1/oauth2/v1/token\"");
|
||||
assertThat(serverConfigurationResponse).contains("\"revocation_endpoint\":\"https://example.com/issuer1/oauth2/v1/revoke\"");
|
||||
assertThat(serverConfigurationResponse).contains("\"jwks_uri\":\"https://example.com/issuer1/oauth2/v1/jwks\"");
|
||||
assertThat(serverConfigurationResponse).contains("\"scopes_supported\":[\"openid\"]");
|
||||
assertThat(serverConfigurationResponse).contains("\"response_types_supported\":[\"code\"]");
|
||||
assertThat(serverConfigurationResponse).contains("\"grant_types_supported\":[\"authorization_code\",\"client_credentials\",\"refresh_token\"]");
|
||||
assertThat(serverConfigurationResponse).contains("\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\"]");
|
||||
assertThat(serverConfigurationResponse).contains("\"revocation_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\"]");
|
||||
assertThat(serverConfigurationResponse).contains("\"code_challenge_methods_supported\":[\"plain\",\"S256\"]");
|
||||
String authorizationServerMetadataResponse = response.getContentAsString();
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"issuer\":\"https://example.com/issuer1\"");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"authorization_endpoint\":\"https://example.com/issuer1/oauth2/v1/authorize\"");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"token_endpoint\":\"https://example.com/issuer1/oauth2/v1/token\"");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\"]");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"jwks_uri\":\"https://example.com/issuer1/oauth2/v1/jwks\"");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"response_types_supported\":[\"code\"]");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"grant_types_supported\":[\"authorization_code\",\"client_credentials\",\"refresh_token\"]");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"revocation_endpoint\":\"https://example.com/issuer1/oauth2/v1/revoke\"");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"revocation_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\"]");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"introspection_endpoint\":\"https://example.com/issuer1/oauth2/v1/introspect\"");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"introspection_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\"]");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"code_challenge_methods_supported\":[\"plain\",\"S256\"]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenProviderSettingsWithInvalidIssuerThenThrowIllegalArgumentException() {
|
||||
ProviderSettings providerSettings = new ProviderSettings()
|
||||
.issuer("https://this is an invalid URL");
|
||||
OAuth2AuthorizationServerConfigurationEndpointFilter filter =
|
||||
new OAuth2AuthorizationServerConfigurationEndpointFilter(providerSettings);
|
||||
OAuth2AuthorizationServerMetadataEndpointFilter filter =
|
||||
new OAuth2AuthorizationServerMetadataEndpointFilter(providerSettings);
|
||||
|
||||
String requestUri = OAuth2AuthorizationServerConfigurationEndpointFilter.DEFAULT_OAUTH2_AUTHORIZATION_SERVER_CONFIGURATION_ENDPOINT_URI;
|
||||
String requestUri = OAuth2AuthorizationServerMetadataEndpointFilter.DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI;
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||
request.setServletPath(requestUri);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
@@ -138,4 +142,5 @@ public class OAuth2AuthorizationServerConfigurationEndpointFilterTests {
|
||||
.isThrownBy(() -> filter.doFilter(request, response, filterChain))
|
||||
.withMessage("issuer must be a valid URL");
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user