diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserService.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserService.java index 0ca84ca6d4..b3d00a077c 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserService.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserService.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * 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. @@ -40,6 +40,7 @@ import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; +import org.springframework.web.client.UnknownContentTypeException; /** * An implementation of an {@link OAuth2UserService} that supports standard OAuth 2.0 Provider's. @@ -122,6 +123,17 @@ public class DefaultOAuth2UserService implements OAuth2UserService e instanceof IOException, t -> new AuthenticationServiceException("Unable to access the userInfoEndpoint " + userInfoUri, t)) + .onErrorMap(IOException.class, e -> new AuthenticationServiceException("Unable to access the userInfoEndpoint " + userInfoUri, e)) + .onErrorMap(UnsupportedMediaTypeException.class, e -> { + String errorMessage = "An error occurred while attempting to retrieve the UserInfo Resource from '" + + userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri() + + "': response contains invalid content type '" + e.getContentType().toString() + "'. " + + "The UserInfo Response should return a JSON object (content type 'application/json') " + + "that contains a collection of name and value pairs of the claims about the authenticated End-User. " + + "Please ensure the UserInfo Uri in UserInfoEndpoint for Client Registration '" + + userRequest.getClientRegistration().getRegistrationId() + "' conforms to the UserInfo Endpoint, " + + "as defined in OpenID Connect 1.0: 'https://openid.net/specs/openid-connect-core-1_0.html#UserInfo'"; + OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE, errorMessage, null); + throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), e); + }) .onErrorMap(t -> !(t instanceof AuthenticationServiceException), t -> { OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE, "An error occurred reading the UserInfo Success response: " + t.getMessage(), null); return new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), t); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserServiceTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserServiceTests.java index f5b4202a39..6324322bea 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserServiceTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserServiceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * 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. @@ -372,6 +372,29 @@ public class DefaultOAuth2UserServiceTests { assertThat(authorities.next()).isInstanceOf(OAuth2UserAuthority.class); } + // gh-8764 + @Test + public void loadUserWhenUserInfoSuccessResponseInvalidContentTypeThenThrowOAuth2AuthenticationException() { + String userInfoUri = this.server.url("/user").toString(); + + this.exception.expect(OAuth2AuthenticationException.class); + this.exception.expectMessage(containsString( + "[invalid_user_info_response] An error occurred while attempting to retrieve the UserInfo Resource " + + "from '" + userInfoUri + "': response contains invalid content type 'text/plain'.")); + + MockResponse response = new MockResponse(); + response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE); + response.setBody("invalid content type"); + this.server.enqueue(response); + + ClientRegistration clientRegistration = this.clientRegistrationBuilder + .userInfoUri(userInfoUri) + .userInfoAuthenticationMethod(AuthenticationMethod.HEADER) + .userNameAttributeName("user-name").build(); + + this.userService.loadUser(new OAuth2UserRequest(clientRegistration, this.accessToken)); + } + private DefaultOAuth2UserService withMockResponse(Map response) { ResponseEntity> responseEntity = new ResponseEntity<>(response, HttpStatus.OK); Converter> requestEntityConverter = mock(Converter.class); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserServiceTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserServiceTests.java index 812ce4e434..476033286a 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserServiceTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserServiceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * 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. @@ -260,6 +260,23 @@ public class DefaultReactiveOAuth2UserServiceTests { assertThat(authorities.next()).isInstanceOf(OAuth2UserAuthority.class); } + // gh-8764 + @Test + public void loadUserWhenUserInfoSuccessResponseInvalidContentTypeThenThrowOAuth2AuthenticationException() { + MockResponse response = new MockResponse(); + response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE); + response.setBody("invalid content type"); + this.server.enqueue(response); + + OAuth2UserRequest userRequest = oauth2UserRequest(); + + assertThatThrownBy(() -> this.userService.loadUser(userRequest).block()) + .isInstanceOf(OAuth2AuthenticationException.class) + .hasMessageContaining("[invalid_user_info_response] An error occurred while attempting to retrieve the UserInfo Resource from '" + + userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri() + "': " + + "response contains invalid content type 'text/plain'"); + } + private DefaultReactiveOAuth2UserService withMockResponse(Map body) { WebClient real = WebClient.builder().build(); WebClient.RequestHeadersUriSpec spec = spy(real.post());