Skip to content

Commit b69bcf8

Browse files
committed
Improve error message when invalid content-type for UserInfo response
Closes gh-8764
1 parent 896b324 commit b69bcf8

File tree

4 files changed

+70
-5
lines changed

4 files changed

+70
-5
lines changed

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserService.java

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -40,6 +40,7 @@
4040
import org.springframework.web.client.RestClientException;
4141
import org.springframework.web.client.RestOperations;
4242
import org.springframework.web.client.RestTemplate;
43+
import org.springframework.web.client.UnknownContentTypeException;
4344

4445
/**
4546
* An implementation of an {@link OAuth2UserService} that supports standard OAuth 2.0 Provider's.
@@ -122,6 +123,17 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic
122123
oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE,
123124
"An error occurred while attempting to retrieve the UserInfo Resource: " + errorDetails.toString(), null);
124125
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
126+
} catch (UnknownContentTypeException ex) {
127+
String errorMessage = "An error occurred while attempting to retrieve the UserInfo Resource from '" +
128+
userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri() +
129+
"': response contains invalid content type '" + ex.getContentType().toString() + "'. " +
130+
"The UserInfo Response should return a JSON object (content type 'application/json') " +
131+
"that contains a collection of name and value pairs of the claims about the authenticated End-User. " +
132+
"Please ensure the UserInfo Uri in UserInfoEndpoint for Client Registration '" +
133+
userRequest.getClientRegistration().getRegistrationId() + "' conforms to the UserInfo Endpoint, " +
134+
"as defined in OpenID Connect 1.0: 'https://openid.net/specs/openid-connect-core-1_0.html#UserInfo'";
135+
OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE, errorMessage, null);
136+
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
125137
} catch (RestClientException ex) {
126138
OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE,
127139
"An error occurred while attempting to retrieve the UserInfo Resource: " + ex.getMessage(), null);

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserService.java

+15-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -38,6 +38,7 @@
3838
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
3939
import org.springframework.util.Assert;
4040
import org.springframework.util.StringUtils;
41+
import org.springframework.web.reactive.function.UnsupportedMediaTypeException;
4142
import org.springframework.web.reactive.function.client.ClientResponse;
4243
import org.springframework.web.reactive.function.client.WebClient;
4344

@@ -140,7 +141,19 @@ public Mono<OAuth2User> loadUser(OAuth2UserRequest userRequest)
140141

141142
return new DefaultOAuth2User(authorities, attrs, userNameAttributeName);
142143
})
143-
.onErrorMap(e -> e instanceof IOException, t -> new AuthenticationServiceException("Unable to access the userInfoEndpoint " + userInfoUri, t))
144+
.onErrorMap(IOException.class, e -> new AuthenticationServiceException("Unable to access the userInfoEndpoint " + userInfoUri, e))
145+
.onErrorMap(UnsupportedMediaTypeException.class, e -> {
146+
String errorMessage = "An error occurred while attempting to retrieve the UserInfo Resource from '" +
147+
userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri() +
148+
"': response contains invalid content type '" + e.getContentType().toString() + "'. " +
149+
"The UserInfo Response should return a JSON object (content type 'application/json') " +
150+
"that contains a collection of name and value pairs of the claims about the authenticated End-User. " +
151+
"Please ensure the UserInfo Uri in UserInfoEndpoint for Client Registration '" +
152+
userRequest.getClientRegistration().getRegistrationId() + "' conforms to the UserInfo Endpoint, " +
153+
"as defined in OpenID Connect 1.0: 'https://openid.net/specs/openid-connect-core-1_0.html#UserInfo'";
154+
OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE, errorMessage, null);
155+
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), e);
156+
})
144157
.onErrorMap(t -> !(t instanceof AuthenticationServiceException), t -> {
145158
OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE, "An error occurred reading the UserInfo Success response: " + t.getMessage(), null);
146159
return new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), t);

oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserServiceTests.java

+24-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -372,6 +372,29 @@ public void loadUserWhenTokenDoesNotContainScopesThenNoScopeAuthorities() {
372372
assertThat(authorities.next()).isInstanceOf(OAuth2UserAuthority.class);
373373
}
374374

375+
// gh-8764
376+
@Test
377+
public void loadUserWhenUserInfoSuccessResponseInvalidContentTypeThenThrowOAuth2AuthenticationException() {
378+
String userInfoUri = this.server.url("/user").toString();
379+
380+
this.exception.expect(OAuth2AuthenticationException.class);
381+
this.exception.expectMessage(containsString(
382+
"[invalid_user_info_response] An error occurred while attempting to retrieve the UserInfo Resource " +
383+
"from '" + userInfoUri + "': response contains invalid content type 'text/plain'."));
384+
385+
MockResponse response = new MockResponse();
386+
response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE);
387+
response.setBody("invalid content type");
388+
this.server.enqueue(response);
389+
390+
ClientRegistration clientRegistration = this.clientRegistrationBuilder
391+
.userInfoUri(userInfoUri)
392+
.userInfoAuthenticationMethod(AuthenticationMethod.HEADER)
393+
.userNameAttributeName("user-name").build();
394+
395+
this.userService.loadUser(new OAuth2UserRequest(clientRegistration, this.accessToken));
396+
}
397+
375398
private DefaultOAuth2UserService withMockResponse(Map<String, Object> response) {
376399
ResponseEntity<Map<String, Object>> responseEntity = new ResponseEntity<>(response, HttpStatus.OK);
377400
Converter<OAuth2UserRequest, RequestEntity<?>> requestEntityConverter = mock(Converter.class);

oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserServiceTests.java

+18-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -260,6 +260,23 @@ public void loadUserWhenTokenDoesNotContainScopesThenNoScopeAuthorities() {
260260
assertThat(authorities.next()).isInstanceOf(OAuth2UserAuthority.class);
261261
}
262262

263+
// gh-8764
264+
@Test
265+
public void loadUserWhenUserInfoSuccessResponseInvalidContentTypeThenThrowOAuth2AuthenticationException() {
266+
MockResponse response = new MockResponse();
267+
response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE);
268+
response.setBody("invalid content type");
269+
this.server.enqueue(response);
270+
271+
OAuth2UserRequest userRequest = oauth2UserRequest();
272+
273+
assertThatThrownBy(() -> this.userService.loadUser(userRequest).block())
274+
.isInstanceOf(OAuth2AuthenticationException.class)
275+
.hasMessageContaining("[invalid_user_info_response] An error occurred while attempting to retrieve the UserInfo Resource from '" +
276+
userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri() + "': " +
277+
"response contains invalid content type 'text/plain'");
278+
}
279+
263280
private DefaultReactiveOAuth2UserService withMockResponse(Map<String, Object> body) {
264281
WebClient real = WebClient.builder().build();
265282
WebClient.RequestHeadersUriSpec spec = spy(real.post());

0 commit comments

Comments
 (0)