Remove deprecated CustomUserTypesOAuth2UserService

Closes gh-11511
This commit is contained in:
Joe Grandja 2022-07-14 14:14:12 -04:00
parent 67b27a41c3
commit 42683693c0
4 changed files with 2 additions and 468 deletions

View File

@ -17,11 +17,9 @@
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.BeanFactoryUtils;
@ -48,9 +46,7 @@ import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.userinfo.CustomUserTypesOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.DelegatingOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository;
@ -438,16 +434,7 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2UserService.class, OAuth2UserRequest.class,
OAuth2User.class);
OAuth2UserService<OAuth2UserRequest, OAuth2User> bean = getBeanOrNull(type);
if (bean != null) {
return bean;
}
if (this.userInfoEndpointConfig.customUserTypes.isEmpty()) {
return new DefaultOAuth2UserService();
}
List<OAuth2UserService<OAuth2UserRequest, OAuth2User>> userServices = new ArrayList<>();
userServices.add(new CustomUserTypesOAuth2UserService(this.userInfoEndpointConfig.customUserTypes));
userServices.add(new DefaultOAuth2UserService());
return new DelegatingOAuth2UserService<>(userServices);
return (bean != null) ? bean : new DefaultOAuth2UserService();
}
private <T> T getBeanOrNull(ResolvableType type) {
@ -666,8 +653,6 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService;
private Map<String, Class<? extends OAuth2User>> customUserTypes = new HashMap<>();
private UserInfoEndpointConfig() {
}
@ -697,23 +682,6 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
return this;
}
/**
* Sets a custom {@link OAuth2User} type and associates it to the provided client
* {@link ClientRegistration#getRegistrationId() registration identifier}.
* @param customUserType a custom {@link OAuth2User} type
* @param clientRegistrationId the client registration identifier
* @return the {@link UserInfoEndpointConfig} for further configuration
* @deprecated See {@link CustomUserTypesOAuth2UserService} for alternative usage.
*/
@Deprecated
public UserInfoEndpointConfig customUserType(Class<? extends OAuth2User> customUserType,
String clientRegistrationId) {
Assert.notNull(customUserType, "customUserType cannot be null");
Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
this.customUserTypes.put(clientRegistrationId, customUserType);
return this;
}
/**
* Sets the {@link GrantedAuthoritiesMapper} used for mapping
* {@link OAuth2User#getAuthorities()}.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,7 +20,6 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest
import org.springframework.security.oauth2.client.registration.ClientRegistration
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService
import org.springframework.security.oauth2.core.oidc.user.OidcUser
@ -44,39 +43,11 @@ class UserInfoEndpointDsl {
var oidcUserService: OAuth2UserService<OidcUserRequest, OidcUser>? = null
var userAuthoritiesMapper: GrantedAuthoritiesMapper? = null
private var customUserTypePair: Pair<Class<out OAuth2User>, String>? = null
/**
* Sets a custom [OAuth2User] type and associates it to the provided
* client [ClientRegistration.getRegistrationId] registration identifier.
*
* @param customUserType a custom [OAuth2User] type
* @param clientRegistrationId the client registration identifier
*/
@Deprecated("Use 'customUserType<T>(clientRegistrationId)' instead.")
fun customUserType(customUserType: Class<out OAuth2User>, clientRegistrationId: String) {
customUserTypePair = Pair(customUserType, clientRegistrationId)
}
/**
* Sets a custom [OAuth2User] type and associates it to the provided
* client [ClientRegistration.getRegistrationId] registration identifier.
* Variant that is leveraging Kotlin reified type parameters.
*
* @param T a custom [OAuth2User] type
* @param clientRegistrationId the client registration identifier
*/
@Suppress("DEPRECATION")
inline fun <reified T: OAuth2User> customUserType(clientRegistrationId: String) {
customUserType(T::class.java, clientRegistrationId)
}
internal fun get(): (OAuth2LoginConfigurer<HttpSecurity>.UserInfoEndpointConfig) -> Unit {
return { userInfoEndpoint ->
userService?.also { userInfoEndpoint.userService(userService) }
oidcUserService?.also { userInfoEndpoint.oidcUserService(oidcUserService) }
userAuthoritiesMapper?.also { userInfoEndpoint.userAuthoritiesMapper(userAuthoritiesMapper) }
customUserTypePair?.also { userInfoEndpoint.customUserType(customUserTypePair!!.first, customUserTypePair!!.second) }
}
}
}

View File

@ -1,139 +0,0 @@
/*
* Copyright 2002-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.client.userinfo;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.util.Assert;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;
/**
* An implementation of an {@link OAuth2UserService} that supports custom
* {@link OAuth2User} types.
* <p>
* The custom user type(s) is supplied via the constructor, using a {@code Map} of
* {@link OAuth2User} type(s) keyed by {@code String}, which represents the
* {@link ClientRegistration#getRegistrationId() Registration Id} of the Client.
*
* @author Joe Grandja
* @since 5.0
* @see OAuth2UserService
* @see OAuth2UserRequest
* @see OAuth2User
* @see ClientRegistration
* @deprecated It is recommended to use a delegation-based strategy of an
* {@link OAuth2UserService} to support custom {@link OAuth2User} types, as it provides
* much greater flexibility compared to this implementation. See the
* <a target="_blank" href=
* "https://docs.spring.io/spring-security/site/docs/current/reference/html5/#oauth2login-advanced-map-authorities-oauth2userservice">reference
* manual</a> for details on how to implement.
*/
@Deprecated
public class CustomUserTypesOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
private static final String INVALID_USER_INFO_RESPONSE_ERROR_CODE = "invalid_user_info_response";
private final Map<String, Class<? extends OAuth2User>> customUserTypes;
private Converter<OAuth2UserRequest, RequestEntity<?>> requestEntityConverter = new OAuth2UserRequestEntityConverter();
private RestOperations restOperations;
/**
* Constructs a {@code CustomUserTypesOAuth2UserService} using the provided
* parameters.
* @param customUserTypes a {@code Map} of {@link OAuth2User} type(s) keyed by
* {@link ClientRegistration#getRegistrationId() Registration Id}
*/
public CustomUserTypesOAuth2UserService(Map<String, Class<? extends OAuth2User>> customUserTypes) {
Assert.notEmpty(customUserTypes, "customUserTypes cannot be empty");
this.customUserTypes = Collections.unmodifiableMap(new LinkedHashMap<>(customUserTypes));
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
this.restOperations = restTemplate;
}
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
Assert.notNull(userRequest, "userRequest cannot be null");
String registrationId = userRequest.getClientRegistration().getRegistrationId();
Class<? extends OAuth2User> customUserType = this.customUserTypes.get(registrationId);
if (customUserType == null) {
return null;
}
RequestEntity<?> request = this.requestEntityConverter.convert(userRequest);
ResponseEntity<? extends OAuth2User> response = getResponse(customUserType, request);
OAuth2User oauth2User = response.getBody();
return oauth2User;
}
private ResponseEntity<? extends OAuth2User> getResponse(Class<? extends OAuth2User> customUserType,
RequestEntity<?> request) {
try {
return this.restOperations.exchange(request, customUserType);
}
catch (RestClientException ex) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE,
"An error occurred while attempting to retrieve the UserInfo Resource: " + ex.getMessage(), null);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
}
}
/**
* Sets the {@link Converter} used for converting the {@link OAuth2UserRequest} to a
* {@link RequestEntity} representation of the UserInfo Request.
* @param requestEntityConverter the {@link Converter} used for converting to a
* {@link RequestEntity} representation of the UserInfo Request
* @since 5.1
*/
public final void setRequestEntityConverter(Converter<OAuth2UserRequest, RequestEntity<?>> requestEntityConverter) {
Assert.notNull(requestEntityConverter, "requestEntityConverter cannot be null");
this.requestEntityConverter = requestEntityConverter;
}
/**
* Sets the {@link RestOperations} used when requesting the UserInfo resource.
*
* <p>
* <b>NOTE:</b> At a minimum, the supplied {@code restOperations} must be configured
* with the following:
* <ol>
* <li>{@link ResponseErrorHandler} - {@link OAuth2ErrorResponseErrorHandler}</li>
* </ol>
* @param restOperations the {@link RestOperations} used when requesting the UserInfo
* resource
* @since 5.1
*/
public final void setRestOperations(RestOperations restOperations) {
Assert.notNull(restOperations, "restOperations cannot be null");
this.restOperations = restOperations;
}
}

View File

@ -1,266 +0,0 @@
/*
* Copyright 2002-2019 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.client.userinfo;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.TestOAuth2AccessTokens;
import org.springframework.security.oauth2.core.user.OAuth2User;
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 CustomUserTypesOAuth2UserService}.
*
* @author Joe Grandja
* @author Eddú Meléndez
*/
public class CustomUserTypesOAuth2UserServiceTests {
private ClientRegistration.Builder clientRegistrationBuilder;
private OAuth2AccessToken accessToken;
private CustomUserTypesOAuth2UserService userService;
private MockWebServer server;
@BeforeEach
public void setUp() throws Exception {
this.server = new MockWebServer();
this.server.start();
String registrationId = "client-registration-id-1";
// @formatter:off
this.clientRegistrationBuilder = TestClientRegistrations.clientRegistration()
.registrationId(registrationId);
// @formatter:on
this.accessToken = TestOAuth2AccessTokens.noScopes();
Map<String, Class<? extends OAuth2User>> customUserTypes = new HashMap<>();
customUserTypes.put(registrationId, CustomOAuth2User.class);
this.userService = new CustomUserTypesOAuth2UserService(customUserTypes);
}
@AfterEach
public void cleanup() throws Exception {
this.server.shutdown();
}
@Test
public void constructorWhenCustomUserTypesIsNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException().isThrownBy(() -> new CustomUserTypesOAuth2UserService(null));
}
@Test
public void constructorWhenCustomUserTypesIsEmptyThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new CustomUserTypesOAuth2UserService(Collections.emptyMap()));
}
@Test
public void setRequestEntityConverterWhenNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.userService.setRequestEntityConverter(null));
}
@Test
public void setRestOperationsWhenNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.userService.setRestOperations(null));
}
@Test
public void loadUserWhenUserRequestIsNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.userService.loadUser(null));
}
@Test
public void loadUserWhenCustomUserTypeNotFoundThenReturnNull() {
// @formatter:off
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration()
.registrationId("other-client-registration-id-1")
.build();
// @formatter:on
OAuth2User user = this.userService.loadUser(new OAuth2UserRequest(clientRegistration, this.accessToken));
assertThat(user).isNull();
}
@Test
public void loadUserWhenUserInfoSuccessResponseThenReturnUser() {
// @formatter:off
String userInfoResponse = "{\n"
+ " \"id\": \"12345\",\n"
+ " \"name\": \"first last\",\n"
+ " \"login\": \"user1\",\n"
+ " \"email\": \"user1@example.com\"\n"
+ "}\n";
// @formatter:on
this.server.enqueue(jsonResponse(userInfoResponse));
String userInfoUri = this.server.url("/user").toString();
ClientRegistration clientRegistration = this.clientRegistrationBuilder.userInfoUri(userInfoUri).build();
OAuth2User user = this.userService.loadUser(new OAuth2UserRequest(clientRegistration, this.accessToken));
assertThat(user.getName()).isEqualTo("first last");
assertThat(user.getAttributes().size()).isEqualTo(4);
assertThat((String) user.getAttribute("id")).isEqualTo("12345");
assertThat((String) user.getAttribute("name")).isEqualTo("first last");
assertThat((String) user.getAttribute("login")).isEqualTo("user1");
assertThat((String) user.getAttribute("email")).isEqualTo("user1@example.com");
assertThat(user.getAuthorities().size()).isEqualTo(1);
assertThat(user.getAuthorities().iterator().next().getAuthority()).isEqualTo("ROLE_USER");
}
@Test
public void loadUserWhenUserInfoSuccessResponseInvalidThenThrowOAuth2AuthenticationException() {
// @formatter:off
String userInfoResponse = "{\n"
+ " \"id\": \"12345\",\n"
+ " \"name\": \"first last\",\n"
+ " \"login\": \"user1\",\n"
+ " \"email\": \"user1@example.com\"\n";
// "}\n"; // Make the JSON invalid/malformed
// @formatter:on
this.server.enqueue(jsonResponse(userInfoResponse));
String userInfoUri = this.server.url("/user").toString();
ClientRegistration clientRegistration = this.clientRegistrationBuilder.userInfoUri(userInfoUri).build();
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(
() -> this.userService.loadUser(new OAuth2UserRequest(clientRegistration, this.accessToken)))
.withMessageContaining(
"[invalid_user_info_response] An error occurred while attempting to retrieve the UserInfo Resource");
}
@Test
public void loadUserWhenServerErrorThenThrowOAuth2AuthenticationException() {
this.server.enqueue(new MockResponse().setResponseCode(500));
String userInfoUri = this.server.url("/user").toString();
ClientRegistration clientRegistration = this.clientRegistrationBuilder.userInfoUri(userInfoUri).build();
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(
() -> this.userService.loadUser(new OAuth2UserRequest(clientRegistration, this.accessToken)))
.withMessageContaining(
"[invalid_user_info_response] An error occurred while attempting to retrieve the UserInfo Resource: 500 Server Error");
}
@Test
public void loadUserWhenUserInfoUriInvalidThenThrowOAuth2AuthenticationException() {
String userInfoUri = "https://invalid-provider.com/user";
ClientRegistration clientRegistration = this.clientRegistrationBuilder.userInfoUri(userInfoUri).build();
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(
() -> this.userService.loadUser(new OAuth2UserRequest(clientRegistration, this.accessToken)))
.withMessageContaining(
"[invalid_user_info_response] An error occurred while attempting to retrieve the UserInfo Resource");
}
private ClientRegistration.Builder withRegistrationId(String registrationId) {
// @formatter:off
return ClientRegistration.withRegistrationId(registrationId)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.clientId("client")
.tokenUri("/token");
// @formatter:on
}
private MockResponse jsonResponse(String json) {
return new MockResponse().setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).setBody(json);
}
public static class CustomOAuth2User implements OAuth2User {
private List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_USER");
private String id;
private String name;
private String login;
private String email;
public CustomOAuth2User() {
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override
public Map<String, Object> getAttributes() {
Map<String, Object> attributes = new HashMap<>();
attributes.put("id", this.getId());
attributes.put("name", this.getName());
attributes.put("login", this.getLogin());
attributes.put("email", this.getEmail());
return attributes;
}
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
@Override
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getLogin() {
return this.login;
}
public void setLogin(String login) {
this.login = login;
}
public String getEmail() {
return this.email;
}
public void setEmail(String email) {
this.email = email;
}
}
}