Improve error message when invalid content-type for UserInfo response
Closes gh-8764
This commit is contained in:
		
							parent
							
								
									896b324722
								
							
						
					
					
						commit
						b69bcf88e0
					
				| 
						 | 
					@ -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");
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 * you may not use this file except in compliance with 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.RestClientException;
 | 
				
			||||||
import org.springframework.web.client.RestOperations;
 | 
					import org.springframework.web.client.RestOperations;
 | 
				
			||||||
import org.springframework.web.client.RestTemplate;
 | 
					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.
 | 
					 * An implementation of an {@link OAuth2UserService} that supports standard OAuth 2.0 Provider's.
 | 
				
			||||||
| 
						 | 
					@ -122,6 +123,17 @@ public class DefaultOAuth2UserService implements OAuth2UserService<OAuth2UserReq
 | 
				
			||||||
			oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE,
 | 
								oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE,
 | 
				
			||||||
					"An error occurred while attempting to retrieve the UserInfo Resource: " + errorDetails.toString(), null);
 | 
										"An error occurred while attempting to retrieve the UserInfo Resource: " + errorDetails.toString(), null);
 | 
				
			||||||
			throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
 | 
								throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
 | 
				
			||||||
 | 
							} catch (UnknownContentTypeException ex) {
 | 
				
			||||||
 | 
								String errorMessage = "An error occurred while attempting to retrieve the UserInfo Resource from '" +
 | 
				
			||||||
 | 
										userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri() +
 | 
				
			||||||
 | 
										"': response contains invalid content type '" + ex.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(), ex);
 | 
				
			||||||
		} catch (RestClientException ex) {
 | 
							} catch (RestClientException ex) {
 | 
				
			||||||
			OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE,
 | 
								OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE,
 | 
				
			||||||
					"An error occurred while attempting to retrieve the UserInfo Resource: " + ex.getMessage(), null);
 | 
										"An error occurred while attempting to retrieve the UserInfo Resource: " + ex.getMessage(), null);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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");
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 * you may not use this file except in compliance with the License.
 | 
					 * you may not use this file except in compliance with the License.
 | 
				
			||||||
| 
						 | 
					@ -38,6 +38,7 @@ import org.springframework.security.oauth2.core.user.OAuth2User;
 | 
				
			||||||
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
 | 
					import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
 | 
				
			||||||
import org.springframework.util.Assert;
 | 
					import org.springframework.util.Assert;
 | 
				
			||||||
import org.springframework.util.StringUtils;
 | 
					import org.springframework.util.StringUtils;
 | 
				
			||||||
 | 
					import org.springframework.web.reactive.function.UnsupportedMediaTypeException;
 | 
				
			||||||
import org.springframework.web.reactive.function.client.ClientResponse;
 | 
					import org.springframework.web.reactive.function.client.ClientResponse;
 | 
				
			||||||
import org.springframework.web.reactive.function.client.WebClient;
 | 
					import org.springframework.web.reactive.function.client.WebClient;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -140,7 +141,19 @@ public class DefaultReactiveOAuth2UserService implements ReactiveOAuth2UserServi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				return new DefaultOAuth2User(authorities, attrs, userNameAttributeName);
 | 
									return new DefaultOAuth2User(authorities, attrs, userNameAttributeName);
 | 
				
			||||||
			})
 | 
								})
 | 
				
			||||||
			.onErrorMap(e -> 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 -> {
 | 
								.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);
 | 
									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);
 | 
									return new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), t);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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");
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 * you may not use this file except in compliance with 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);
 | 
							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<String, Object> response) {
 | 
						private DefaultOAuth2UserService withMockResponse(Map<String, Object> response) {
 | 
				
			||||||
		ResponseEntity<Map<String, Object>> responseEntity = new ResponseEntity<>(response, HttpStatus.OK);
 | 
							ResponseEntity<Map<String, Object>> responseEntity = new ResponseEntity<>(response, HttpStatus.OK);
 | 
				
			||||||
		Converter<OAuth2UserRequest, RequestEntity<?>> requestEntityConverter = mock(Converter.class);
 | 
							Converter<OAuth2UserRequest, RequestEntity<?>> requestEntityConverter = mock(Converter.class);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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");
 | 
					 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 * you may not use this file except in compliance with 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);
 | 
							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<String, Object> body) {
 | 
						private DefaultReactiveOAuth2UserService withMockResponse(Map<String, Object> body) {
 | 
				
			||||||
		WebClient real = WebClient.builder().build();
 | 
							WebClient real = WebClient.builder().build();
 | 
				
			||||||
		WebClient.RequestHeadersUriSpec spec = spy(real.post());
 | 
							WebClient.RequestHeadersUriSpec spec = spy(real.post());
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue