From 42683693c05a820e13b5e653d4abf64e05eb2eac Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Thu, 14 Jul 2022 14:14:12 -0400 Subject: [PATCH] Remove deprecated CustomUserTypesOAuth2UserService Closes gh-11511 --- .../oauth2/client/OAuth2LoginConfigurer.java | 34 +-- .../web/oauth2/login/UserInfoEndpointDsl.kt | 31 +- .../CustomUserTypesOAuth2UserService.java | 139 --------- ...CustomUserTypesOAuth2UserServiceTests.java | 266 ------------------ 4 files changed, 2 insertions(+), 468 deletions(-) delete mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/CustomUserTypesOAuth2UserService.java delete mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/CustomUserTypesOAuth2UserServiceTests.java diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java index be1707e394..dcdf53f121 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java @@ -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> ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2UserService.class, OAuth2UserRequest.class, OAuth2User.class); OAuth2UserService bean = getBeanOrNull(type); - if (bean != null) { - return bean; - } - if (this.userInfoEndpointConfig.customUserTypes.isEmpty()) { - return new DefaultOAuth2UserService(); - } - List> 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 getBeanOrNull(ResolvableType type) { @@ -666,8 +653,6 @@ public final class OAuth2LoginConfigurer> private OAuth2UserService oidcUserService; - private Map> customUserTypes = new HashMap<>(); - private UserInfoEndpointConfig() { } @@ -697,23 +682,6 @@ public final class OAuth2LoginConfigurer> 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 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()}. diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/oauth2/login/UserInfoEndpointDsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/oauth2/login/UserInfoEndpointDsl.kt index ab53658234..938a950716 100644 --- a/config/src/main/kotlin/org/springframework/security/config/annotation/web/oauth2/login/UserInfoEndpointDsl.kt +++ b/config/src/main/kotlin/org/springframework/security/config/annotation/web/oauth2/login/UserInfoEndpointDsl.kt @@ -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? = null var userAuthoritiesMapper: GrantedAuthoritiesMapper? = null - private var customUserTypePair: Pair, 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(clientRegistrationId)' instead.") - fun customUserType(customUserType: Class, 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 customUserType(clientRegistrationId: String) { - customUserType(T::class.java, clientRegistrationId) - } - internal fun get(): (OAuth2LoginConfigurer.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) } } } } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/CustomUserTypesOAuth2UserService.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/CustomUserTypesOAuth2UserService.java deleted file mode 100644 index eacca515a3..0000000000 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/CustomUserTypesOAuth2UserService.java +++ /dev/null @@ -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. - *

- * 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 - * reference - * manual for details on how to implement. - */ -@Deprecated -public class CustomUserTypesOAuth2UserService implements OAuth2UserService { - - private static final String INVALID_USER_INFO_RESPONSE_ERROR_CODE = "invalid_user_info_response"; - - private final Map> customUserTypes; - - private Converter> 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> 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 customUserType = this.customUserTypes.get(registrationId); - if (customUserType == null) { - return null; - } - RequestEntity request = this.requestEntityConverter.convert(userRequest); - ResponseEntity response = getResponse(customUserType, request); - OAuth2User oauth2User = response.getBody(); - return oauth2User; - } - - private ResponseEntity getResponse(Class 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> requestEntityConverter) { - Assert.notNull(requestEntityConverter, "requestEntityConverter cannot be null"); - this.requestEntityConverter = requestEntityConverter; - } - - /** - * Sets the {@link RestOperations} used when requesting the UserInfo resource. - * - *

- * NOTE: At a minimum, the supplied {@code restOperations} must be configured - * with the following: - *

    - *
  1. {@link ResponseErrorHandler} - {@link OAuth2ErrorResponseErrorHandler}
  2. - *
- * @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; - } - -} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/CustomUserTypesOAuth2UserServiceTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/CustomUserTypesOAuth2UserServiceTests.java deleted file mode 100644 index 87a9a9cbf2..0000000000 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/CustomUserTypesOAuth2UserServiceTests.java +++ /dev/null @@ -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> 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 authorities = AuthorityUtils.createAuthorityList("ROLE_USER"); - - private String id; - - private String name; - - private String login; - - private String email; - - public CustomOAuth2User() { - } - - @Override - public Collection getAuthorities() { - return this.authorities; - } - - @Override - public Map getAttributes() { - Map 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; - } - - } - -}