瀏覽代碼

Remove UserInfoRetreiver

Fixes gh-4740
Joe Grandja 7 年之前
父節點
當前提交
2a00232a5b

+ 165 - 0
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/NimbusUserInfoResponseClient.java

@@ -0,0 +1,165 @@
+/*
+ * Copyright 2002-2017 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
+ *
+ *      http://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.oidc.userinfo;
+
+import com.nimbusds.oauth2.sdk.ErrorObject;
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.oauth2.sdk.http.HTTPRequest;
+import com.nimbusds.oauth2.sdk.http.HTTPResponse;
+import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
+import com.nimbusds.openid.connect.sdk.UserInfoErrorResponse;
+import com.nimbusds.openid.connect.sdk.UserInfoRequest;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.client.AbstractClientHttpResponse;
+import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.http.converter.GenericHttpMessageConverter;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.security.authentication.AuthenticationServiceException;
+import org.springframework.security.oauth2.client.registration.ClientRegistration;
+import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
+import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.OAuth2Error;
+import org.springframework.util.Assert;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.nio.charset.Charset;
+
+/**
+ * NOTE: This is a straight copy of org.springframework.security.oauth2.client.userinfo.NimbusUserInfoResponseClient
+ *
+ * @author Joe Grandja
+ * @since 5.0
+ */
+final class NimbusUserInfoResponseClient {
+	private static final String INVALID_USER_INFO_RESPONSE_ERROR_CODE = "invalid_user_info_response";
+	private final GenericHttpMessageConverter genericHttpMessageConverter = new MappingJackson2HttpMessageConverter();
+
+	<T> T getUserInfoResponse(OAuth2UserRequest userInfoRequest, Class<T> returnType) throws OAuth2AuthenticationException {
+		ClientHttpResponse userInfoResponse = this.getUserInfoResponse(
+			userInfoRequest.getClientRegistration(), userInfoRequest.getAccessToken());
+		try {
+			return (T) this.genericHttpMessageConverter.read(returnType, userInfoResponse);
+		} catch (IOException ex) {
+			OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE,
+				"An error occurred reading the UserInfo Success response: " + ex.getMessage(), null);
+			throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
+		}
+	}
+
+	<T> T getUserInfoResponse(OAuth2UserRequest userInfoRequest, ParameterizedTypeReference<T> typeReference) throws OAuth2AuthenticationException {
+		ClientHttpResponse userInfoResponse = this.getUserInfoResponse(
+			userInfoRequest.getClientRegistration(), userInfoRequest.getAccessToken());
+		try {
+			return (T) this.genericHttpMessageConverter.read(typeReference.getType(), null, userInfoResponse);
+		} catch (IOException ex) {
+			OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE,
+				"An error occurred reading the UserInfo Success response: " + ex.getMessage(), null);
+			throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
+		}
+	}
+
+	private ClientHttpResponse getUserInfoResponse(ClientRegistration clientRegistration,
+													OAuth2AccessToken oauth2AccessToken) throws OAuth2AuthenticationException {
+		URI userInfoUri = URI.create(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri());
+		BearerAccessToken accessToken = new BearerAccessToken(oauth2AccessToken.getTokenValue());
+
+		UserInfoRequest userInfoRequest = new UserInfoRequest(userInfoUri, accessToken);
+		HTTPRequest httpRequest = userInfoRequest.toHTTPRequest();
+		httpRequest.setConnectTimeout(30000);
+		httpRequest.setReadTimeout(30000);
+		HTTPResponse httpResponse;
+
+		try {
+			httpResponse = httpRequest.send();
+		} catch (IOException ex) {
+			throw new AuthenticationServiceException("An error occurred while sending the UserInfo Request: " +
+				ex.getMessage(), ex);
+		}
+
+		if (httpResponse.getStatusCode() == HTTPResponse.SC_OK) {
+			return new NimbusClientHttpResponse(httpResponse);
+		}
+
+		UserInfoErrorResponse userInfoErrorResponse;
+		try {
+			userInfoErrorResponse = UserInfoErrorResponse.parse(httpResponse);
+		} catch (ParseException ex) {
+			OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE,
+				"An error occurred parsing the UserInfo Error response: " + ex.getMessage(), null);
+			throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
+		}
+		ErrorObject errorObject = userInfoErrorResponse.getErrorObject();
+
+		StringBuilder errorDescription = new StringBuilder();
+		errorDescription.append("An error occurred while attempting to access the UserInfo Endpoint -> ");
+		errorDescription.append("Error details: [");
+		errorDescription.append("UserInfo Uri: ").append(userInfoUri.toString());
+		errorDescription.append(", Http Status: ").append(errorObject.getHTTPStatusCode());
+		if (errorObject.getCode() != null) {
+			errorDescription.append(", Error Code: ").append(errorObject.getCode());
+		}
+		if (errorObject.getDescription() != null) {
+			errorDescription.append(", Error Description: ").append(errorObject.getDescription());
+		}
+		errorDescription.append("]");
+
+		OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE, errorDescription.toString(), null);
+		throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
+	}
+
+	private static class NimbusClientHttpResponse extends AbstractClientHttpResponse {
+		private final HTTPResponse httpResponse;
+		private final HttpHeaders headers;
+
+		private NimbusClientHttpResponse(HTTPResponse httpResponse) {
+			Assert.notNull(httpResponse, "httpResponse cannot be null");
+			this.httpResponse = httpResponse;
+			this.headers = new HttpHeaders();
+			this.headers.setAll(httpResponse.getHeaders());
+		}
+
+		@Override
+		public int getRawStatusCode() throws IOException {
+			return this.httpResponse.getStatusCode();
+		}
+
+		@Override
+		public String getStatusText() throws IOException {
+			return String.valueOf(this.getRawStatusCode());
+		}
+
+		@Override
+		public void close() {
+		}
+
+		@Override
+		public InputStream getBody() throws IOException {
+			InputStream inputStream = new ByteArrayInputStream(
+				this.httpResponse.getContent().getBytes(Charset.forName("UTF-8")));
+			return inputStream;
+		}
+
+		@Override
+		public HttpHeaders getHeaders() {
+			return this.headers;
+		}
+	}
+}

+ 5 - 15
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserService.java

@@ -15,10 +15,9 @@
  */
 package org.springframework.security.oauth2.client.oidc.userinfo;
 
+import org.springframework.core.ParameterizedTypeReference;
 import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.oauth2.client.userinfo.NimbusUserInfoRetriever;
 import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
-import org.springframework.security.oauth2.client.userinfo.UserInfoRetriever;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2Error;
@@ -27,7 +26,6 @@ import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
 import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
 import org.springframework.security.oauth2.core.oidc.user.OidcUser;
 import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
-import org.springframework.util.Assert;
 import org.springframework.util.StringUtils;
 
 import java.util.Arrays;
@@ -37,10 +35,6 @@ import java.util.Set;
 
 /**
  * An implementation of an {@link OAuth2UserService} that supports <i>OpenID Connect 1.0 Provider's</i>.
- * <p>
- * This implementation uses a {@link UserInfoRetriever} to obtain the user attributes
- * of the <i>End-User</i> (resource owner) from the <i>UserInfo Endpoint</i>
- * and constructs a {@link OidcUserInfo} instance.
  *
  * @author Joe Grandja
  * @since 5.0
@@ -49,19 +43,20 @@ import java.util.Set;
  * @see OidcUser
  * @see DefaultOidcUser
  * @see OidcUserInfo
- * @see UserInfoRetriever
  */
 public class OidcUserService implements OAuth2UserService<OidcUserRequest, OidcUser> {
 	private static final String INVALID_USER_INFO_RESPONSE_ERROR_CODE = "invalid_user_info_response";
-	private UserInfoRetriever userInfoRetriever = new NimbusUserInfoRetriever();
 	private final Set<String> userInfoScopes = new HashSet<>(
 		Arrays.asList(OidcScopes.PROFILE, OidcScopes.EMAIL, OidcScopes.ADDRESS, OidcScopes.PHONE));
+	private NimbusUserInfoResponseClient userInfoResponseClient = new NimbusUserInfoResponseClient();
 
 	@Override
 	public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
 		OidcUserInfo userInfo = null;
 		if (this.shouldRetrieveUserInfo(userRequest)) {
-			Map<String, Object> userAttributes = this.userInfoRetriever.retrieve(userRequest, Map.class);
+			ParameterizedTypeReference<Map<String, Object>> typeReference =
+				new ParameterizedTypeReference<Map<String, Object>>() {};
+			Map<String, Object> userAttributes = this.userInfoResponseClient.getUserInfoResponse(userRequest, typeReference);
 			userInfo = new OidcUserInfo(userAttributes);
 
 			// http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
@@ -84,11 +79,6 @@ public class OidcUserService implements OAuth2UserService<OidcUserRequest, OidcU
 		return new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo);
 	}
 
-	public final void setUserInfoRetriever(UserInfoRetriever userInfoRetriever) {
-		Assert.notNull(userInfoRetriever, "userInfoRetriever cannot be null");
-		this.userInfoRetriever = userInfoRetriever;
-	}
-
 	private boolean shouldRetrieveUserInfo(OidcUserRequest userRequest) {
 		// Auto-disabled if UserInfo Endpoint URI is not provided
 		if (StringUtils.isEmpty(userRequest.getClientRegistration().getProviderDetails()

+ 2 - 12
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/CustomUserTypesOAuth2UserService.java

@@ -30,21 +30,17 @@ import java.util.Map;
  * The custom user type(s) is supplied via the constructor,
  * using a <code>Map</code> of {@link OAuth2User} type <i>keyed</i> by <code>String</code>,
  * which represents the {@link ClientRegistration#getRegistrationId() Registration Id} of the Client.
- * <p>
- * This implementation uses a {@link UserInfoRetriever} to obtain the user attributes
- * of the <i>End-User</i> (Resource Owner) from the <i>UserInfo Endpoint</i>.
  *
  * @author Joe Grandja
  * @since 5.0
  * @see OAuth2UserService
  * @see OAuth2UserRequest
  * @see OAuth2User
- * @see UserInfoRetriever
  * @see ClientRegistration
  */
 public class CustomUserTypesOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
 	private final Map<String, Class<? extends OAuth2User>> customUserTypes;
-	private UserInfoRetriever userInfoRetriever = new NimbusUserInfoRetriever();
+	private NimbusUserInfoResponseClient userInfoResponseClient = new NimbusUserInfoResponseClient();
 
 	public CustomUserTypesOAuth2UserService(Map<String, Class<? extends OAuth2User>> customUserTypes) {
 		Assert.notEmpty(customUserTypes, "customUserTypes cannot be empty");
@@ -58,12 +54,6 @@ public class CustomUserTypesOAuth2UserService implements OAuth2UserService<OAuth
 		if ((customUserType = this.customUserTypes.get(registrationId)) == null) {
 			return null;
 		}
-
-		return this.userInfoRetriever.retrieve(userRequest, customUserType);
-	}
-
-	public final void setUserInfoRetriever(UserInfoRetriever userInfoRetriever) {
-		Assert.notNull(userInfoRetriever, "userInfoRetriever cannot be null");
-		this.userInfoRetriever = userInfoRetriever;
+		return this.userInfoResponseClient.getUserInfoResponse(userRequest, customUserType);
 	}
 }

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

@@ -15,13 +15,13 @@
  */
 package org.springframework.security.oauth2.client.userinfo;
 
+import org.springframework.core.ParameterizedTypeReference;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.oauth2.client.registration.ClientRegistration;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
 import org.springframework.security.oauth2.core.user.OAuth2User;
 import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
-import org.springframework.util.Assert;
 import org.springframework.util.StringUtils;
 
 import java.util.HashSet;
@@ -37,9 +37,6 @@ import java.util.Set;
  * <p>
  * <b>NOTE:</b> Attribute names are <b><i>not</i></b> standardized between providers and therefore will vary.
  * Please consult the provider's API documentation for the set of supported user attribute names.
- * <p>
- * This implementation uses a {@link UserInfoRetriever} to obtain the user attributes
- * of the <i>End-User</i> (resource owner) from the <i>UserInfo Endpoint</i>.
  *
  * @author Joe Grandja
  * @since 5.0
@@ -47,10 +44,9 @@ import java.util.Set;
  * @see OAuth2UserRequest
  * @see OAuth2User
  * @see DefaultOAuth2User
- * @see UserInfoRetriever
  */
 public class DefaultOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
-	private UserInfoRetriever userInfoRetriever = new NimbusUserInfoRetriever();
+	private NimbusUserInfoResponseClient userInfoResponseClient = new NimbusUserInfoResponseClient();
 
 	@Override
 	public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
@@ -61,16 +57,13 @@ public class DefaultOAuth2UserService implements OAuth2UserService<OAuth2UserReq
 					userRequest.getClientRegistration().getRegistrationId());
 		}
 
-		Map<String, Object> userAttributes = this.userInfoRetriever.retrieve(userRequest, Map.class);
+		ParameterizedTypeReference<Map<String, Object>> typeReference =
+			new ParameterizedTypeReference<Map<String, Object>>() {};
+		Map<String, Object> userAttributes = this.userInfoResponseClient.getUserInfoResponse(userRequest, typeReference);
 		GrantedAuthority authority = new OAuth2UserAuthority(userAttributes);
 		Set<GrantedAuthority> authorities = new HashSet<>();
 		authorities.add(authority);
 
 		return new DefaultOAuth2User(authorities, userAttributes, userNameAttributeName);
 	}
-
-	public final void setUserInfoRetriever(UserInfoRetriever userInfoRetriever) {
-		Assert.notNull(userInfoRetriever, "userInfoRetriever cannot be null");
-		this.userInfoRetriever = userInfoRetriever;
-	}
 }

+ 15 - 16
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/NimbusUserInfoRetriever.java → oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/NimbusUserInfoResponseClient.java

@@ -29,6 +29,8 @@ import org.springframework.http.client.ClientHttpResponse;
 import org.springframework.http.converter.GenericHttpMessageConverter;
 import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
 import org.springframework.security.authentication.AuthenticationServiceException;
+import org.springframework.security.oauth2.client.registration.ClientRegistration;
+import org.springframework.security.oauth2.core.OAuth2AccessToken;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.util.Assert;
@@ -40,22 +42,18 @@ import java.net.URI;
 import java.nio.charset.Charset;
 
 /**
- * An implementation of a {@link UserInfoRetriever} that uses the <b>Nimbus OAuth 2.0 SDK</b> internally.
- *
  * @author Joe Grandja
  * @since 5.0
- * @see UserInfoRetriever
- * @see <a target="_blank" href="https://connect2id.com/products/nimbus-oauth-openid-connect-sdk">Nimbus OAuth 2.0 SDK</a>
  */
-public class NimbusUserInfoRetriever implements UserInfoRetriever {
+final class NimbusUserInfoResponseClient {
 	private static final String INVALID_USER_INFO_RESPONSE_ERROR_CODE = "invalid_user_info_response";
 	private final GenericHttpMessageConverter genericHttpMessageConverter = new MappingJackson2HttpMessageConverter();
 
-	@Override
-	public <T> T retrieve(OAuth2UserRequest userRequest, Class<T> returnType) throws OAuth2AuthenticationException {
+	<T> T getUserInfoResponse(OAuth2UserRequest userInfoRequest, Class<T> returnType) throws OAuth2AuthenticationException {
+		ClientHttpResponse userInfoResponse = this.getUserInfoResponse(
+			userInfoRequest.getClientRegistration(), userInfoRequest.getAccessToken());
 		try {
-			ClientHttpResponse userResponse = this.retrieveResponse(userRequest);
-			return (T) this.genericHttpMessageConverter.read(returnType, userResponse);
+			return (T) this.genericHttpMessageConverter.read(returnType, userInfoResponse);
 		} catch (IOException ex) {
 			OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE,
 				"An error occurred reading the UserInfo Success response: " + ex.getMessage(), null);
@@ -63,11 +61,11 @@ public class NimbusUserInfoRetriever implements UserInfoRetriever {
 		}
 	}
 
-	@Override
-	public <T> T retrieve(OAuth2UserRequest userRequest, ParameterizedTypeReference<T> typeReference) throws OAuth2AuthenticationException {
+	<T> T getUserInfoResponse(OAuth2UserRequest userInfoRequest, ParameterizedTypeReference<T> typeReference) throws OAuth2AuthenticationException {
+		ClientHttpResponse userInfoResponse = this.getUserInfoResponse(
+			userInfoRequest.getClientRegistration(), userInfoRequest.getAccessToken());
 		try {
-			ClientHttpResponse userResponse = this.retrieveResponse(userRequest);
-			return (T) this.genericHttpMessageConverter.read(typeReference.getType(), null, userResponse);
+			return (T) this.genericHttpMessageConverter.read(typeReference.getType(), null, userInfoResponse);
 		} catch (IOException ex) {
 			OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE,
 				"An error occurred reading the UserInfo Success response: " + ex.getMessage(), null);
@@ -75,9 +73,10 @@ public class NimbusUserInfoRetriever implements UserInfoRetriever {
 		}
 	}
 
-	private ClientHttpResponse retrieveResponse(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
-		URI userInfoUri = URI.create(userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri());
-		BearerAccessToken accessToken = new BearerAccessToken(userRequest.getAccessToken().getTokenValue());
+	private ClientHttpResponse getUserInfoResponse(ClientRegistration clientRegistration,
+													OAuth2AccessToken oauth2AccessToken) throws OAuth2AuthenticationException {
+		URI userInfoUri = URI.create(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri());
+		BearerAccessToken accessToken = new BearerAccessToken(oauth2AccessToken.getTokenValue());
 
 		UserInfoRequest userInfoRequest = new UserInfoRequest(userInfoUri, accessToken);
 		HTTPRequest httpRequest = userInfoRequest.toHTTPRequest();

+ 0 - 38
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/UserInfoRetriever.java

@@ -1,38 +0,0 @@
-/*
- * Copyright 2002-2017 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
- *
- *      http://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 org.springframework.core.ParameterizedTypeReference;
-import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
-
-/**
- * A strategy for retrieving the user attributes
- * of the <i>End-User</i> (Resource Owner) from the <i>UserInfo Endpoint</i>
- * using the provided {@link OAuth2UserRequest#getAccessToken() Access Token}.
- *
- * @author Joe Grandja
- * @author Rob Winch
- * @since 5.0
- * @see OAuth2UserRequest
- * @see OAuth2UserService
- */
-public interface UserInfoRetriever {
-
-	<T> T retrieve(OAuth2UserRequest userRequest, Class<T> responseType) throws OAuth2AuthenticationException;
-
-	<T> T retrieve(OAuth2UserRequest userRequest, ParameterizedTypeReference<T> typeReference) throws OAuth2AuthenticationException;
-
-}