OIDC Provider Configuration - ClientRegistrations
OIDC Provider Configuration is now being used to create more than just ClientRegistration instances. Also, the endpoint is being addressed in more contexts than just the client. To that end, this refactors OidcConfigurationProvider in the config project to ClientRegistrations in the oauth2-client project. Fixes: gh-5647
This commit is contained in:
@@ -1,241 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2018 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
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.config.oauth2.client.oidc;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @since 5.1
|
||||
*/
|
||||
public class OidcConfigurationProviderTests {
|
||||
|
||||
/**
|
||||
* Contains all optional parameters that are found in ClientRegistration
|
||||
*/
|
||||
private static final String DEFAULT_RESPONSE =
|
||||
"{\n"
|
||||
+ " \"authorization_endpoint\": \"https://example.com/o/oauth2/v2/auth\", \n"
|
||||
+ " \"claims_supported\": [\n"
|
||||
+ " \"aud\", \n"
|
||||
+ " \"email\", \n"
|
||||
+ " \"email_verified\", \n"
|
||||
+ " \"exp\", \n"
|
||||
+ " \"family_name\", \n"
|
||||
+ " \"given_name\", \n"
|
||||
+ " \"iat\", \n"
|
||||
+ " \"iss\", \n"
|
||||
+ " \"locale\", \n"
|
||||
+ " \"name\", \n"
|
||||
+ " \"picture\", \n"
|
||||
+ " \"sub\"\n"
|
||||
+ " ], \n"
|
||||
+ " \"code_challenge_methods_supported\": [\n"
|
||||
+ " \"plain\", \n"
|
||||
+ " \"S256\"\n"
|
||||
+ " ], \n"
|
||||
+ " \"id_token_signing_alg_values_supported\": [\n"
|
||||
+ " \"RS256\"\n"
|
||||
+ " ], \n"
|
||||
+ " \"issuer\": \"https://example.com\", \n"
|
||||
+ " \"jwks_uri\": \"https://example.com/oauth2/v3/certs\", \n"
|
||||
+ " \"response_types_supported\": [\n"
|
||||
+ " \"code\", \n"
|
||||
+ " \"token\", \n"
|
||||
+ " \"id_token\", \n"
|
||||
+ " \"code token\", \n"
|
||||
+ " \"code id_token\", \n"
|
||||
+ " \"token id_token\", \n"
|
||||
+ " \"code token id_token\", \n"
|
||||
+ " \"none\"\n"
|
||||
+ " ], \n"
|
||||
+ " \"revocation_endpoint\": \"https://example.com/o/oauth2/revoke\", \n"
|
||||
+ " \"scopes_supported\": [\n"
|
||||
+ " \"openid\", \n"
|
||||
+ " \"email\", \n"
|
||||
+ " \"profile\"\n"
|
||||
+ " ], \n"
|
||||
+ " \"subject_types_supported\": [\n"
|
||||
+ " \"public\"\n"
|
||||
+ " ], \n"
|
||||
+ " \"grant_types_supported\" : [\"authorization_code\"], \n"
|
||||
+ " \"token_endpoint\": \"https://example.com/oauth2/v4/token\", \n"
|
||||
+ " \"token_endpoint_auth_methods_supported\": [\n"
|
||||
+ " \"client_secret_post\", \n"
|
||||
+ " \"client_secret_basic\"\n"
|
||||
+ " ], \n"
|
||||
+ " \"userinfo_endpoint\": \"https://example.com/oauth2/v3/userinfo\"\n"
|
||||
+ "}";
|
||||
|
||||
private MockWebServer server;
|
||||
|
||||
private ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
private Map<String, Object> response;
|
||||
|
||||
private String issuer;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
this.server = new MockWebServer();
|
||||
this.server.start();
|
||||
this.response = this.mapper.readValue(DEFAULT_RESPONSE, new TypeReference<Map<String, Object>>(){});
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanup() throws Exception {
|
||||
this.server.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issuerWhenAllInformationThenSuccess() throws Exception {
|
||||
ClientRegistration registration = registration("");
|
||||
ClientRegistration.ProviderDetails provider = registration.getProviderDetails();
|
||||
|
||||
assertThat(registration.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.BASIC);
|
||||
assertThat(registration.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
|
||||
assertThat(registration.getRegistrationId()).isEqualTo(this.server.getHostName());
|
||||
assertThat(registration.getClientName()).isEqualTo(this.issuer);
|
||||
assertThat(registration.getScopes()).containsOnly("openid", "email", "profile");
|
||||
assertThat(provider.getAuthorizationUri()).isEqualTo("https://example.com/o/oauth2/v2/auth");
|
||||
assertThat(provider.getTokenUri()).isEqualTo("https://example.com/oauth2/v4/token");
|
||||
assertThat(provider.getJwkSetUri()).isEqualTo("https://example.com/oauth2/v3/certs");
|
||||
assertThat(provider.getUserInfoEndpoint().getUri()).isEqualTo("https://example.com/oauth2/v3/userinfo");
|
||||
}
|
||||
|
||||
/**
|
||||
* https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata
|
||||
*
|
||||
* RECOMMENDED. JSON array containing a list of the OAuth 2.0 [RFC6749] scope values that this server supports. The
|
||||
* server MUST support the openid scope value.
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void issuerWhenScopesNullThenScopesDefaulted() throws Exception {
|
||||
this.response.remove("scopes_supported");
|
||||
|
||||
ClientRegistration registration = registration("");
|
||||
|
||||
assertThat(registration.getScopes()).containsOnly("openid");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issuerWhenGrantTypesSupportedNullThenDefaulted() throws Exception {
|
||||
this.response.remove("grant_types_supported");
|
||||
|
||||
ClientRegistration registration = registration("");
|
||||
|
||||
assertThat(registration.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* We currently only support authorization_code, so verify we have a meaningful error until we add support.
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void issuerWhenGrantTypesSupportedInvalidThenException() throws Exception {
|
||||
this.response.put("grant_types_supported", Arrays.asList("implicit"));
|
||||
|
||||
assertThatThrownBy(() -> registration(""))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessageContaining("Only AuthorizationGrantType.AUTHORIZATION_CODE is supported. The issuer \"" + this.issuer + "\" returned a configuration of [implicit]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issuerWhenTokenEndpointAuthMethodsNullThenDefaulted() throws Exception {
|
||||
this.response.remove("token_endpoint_auth_methods_supported");
|
||||
|
||||
ClientRegistration registration = registration("");
|
||||
|
||||
assertThat(registration.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.BASIC);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issuerWhenTokenEndpointAuthMethodsPostThenMethodIsPost() throws Exception {
|
||||
this.response.put("token_endpoint_auth_methods_supported", Arrays.asList("client_secret_post"));
|
||||
|
||||
ClientRegistration registration = registration("");
|
||||
|
||||
assertThat(registration.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.POST);
|
||||
}
|
||||
|
||||
/**
|
||||
* We currently only support client_secret_basic, so verify we have a meaningful error until we add support.
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void issuerWhenTokenEndpointAuthMethodsInvalidThenException() throws Exception {
|
||||
this.response.put("token_endpoint_auth_methods_supported", Arrays.asList("tls_client_auth"));
|
||||
|
||||
assertThatThrownBy(() -> registration(""))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessageContaining("Only ClientAuthenticationMethod.BASIC and ClientAuthenticationMethod.POST are supported. The issuer \"" + this.issuer + "\" returned a configuration of [tls_client_auth]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issuerWhenEmptyStringThenMeaningfulErrorMessage() {
|
||||
assertThatThrownBy(() -> OidcConfigurationProvider.issuer(""))
|
||||
.hasMessageContaining("Unable to resolve the OpenID Configuration with the provided Issuer of \"\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void issuerWhenOpenIdConfigurationDoesNotMatchThenMeaningfulErrorMessage() throws Exception {
|
||||
this.issuer = createIssuerFromServer("");
|
||||
String body = this.mapper.writeValueAsString(this.response);
|
||||
MockResponse mockResponse = new MockResponse()
|
||||
.setBody(body)
|
||||
.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
||||
this.server.enqueue(mockResponse);
|
||||
assertThatThrownBy(() -> OidcConfigurationProvider.issuer(this.issuer))
|
||||
.hasMessageContaining("The Issuer \"https://example.com\" provided in the OpenID Configuration did not match the requested issuer \"" + this.issuer + "\"");
|
||||
}
|
||||
|
||||
private ClientRegistration registration(String path) throws Exception {
|
||||
this.issuer = createIssuerFromServer(path);
|
||||
this.response.put("issuer", this.issuer);
|
||||
String body = this.mapper.writeValueAsString(this.response);
|
||||
MockResponse mockResponse = new MockResponse()
|
||||
.setBody(body)
|
||||
.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
|
||||
this.server.enqueue(mockResponse);
|
||||
|
||||
return OidcConfigurationProvider.issuer(this.issuer)
|
||||
.clientId("client-id")
|
||||
.clientSecret("client-secret")
|
||||
.build();
|
||||
}
|
||||
|
||||
private String createIssuerFromServer(String path) {
|
||||
return this.server.url(path).toString();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user