mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-06-01 09:42:13 +00:00
OidcUserService leverages DefaultOAuth2UserService
Fixes gh-5390
This commit is contained in:
parent
3332ccbe50
commit
fe979aa996
@ -1,168 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2002-2018 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.MediaType;
|
|
||||||
import org.springframework.http.client.AbstractClientHttpResponse;
|
|
||||||
import org.springframework.http.client.ClientHttpResponse;
|
|
||||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
|
||||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
|
||||||
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 | HttpMessageNotReadableException 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 | HttpMessageNotReadableException 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.setAccept(MediaType.APPLICATION_JSON_VALUE);
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2017 the original author or authors.
|
* Copyright 2002-2018 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.
|
||||||
@ -15,8 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.security.oauth2.client.oidc.userinfo;
|
package org.springframework.security.oauth2.client.oidc.userinfo;
|
||||||
|
|
||||||
import org.springframework.core.ParameterizedTypeReference;
|
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
|
||||||
|
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
|
||||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
|
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
|
||||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
@ -26,12 +27,12 @@ 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.DefaultOidcUser;
|
||||||
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||||
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
|
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
|
||||||
|
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -49,17 +50,15 @@ public class OidcUserService implements OAuth2UserService<OidcUserRequest, OidcU
|
|||||||
private static final String INVALID_USER_INFO_RESPONSE_ERROR_CODE = "invalid_user_info_response";
|
private static final String INVALID_USER_INFO_RESPONSE_ERROR_CODE = "invalid_user_info_response";
|
||||||
private final Set<String> userInfoScopes = new HashSet<>(
|
private final Set<String> userInfoScopes = new HashSet<>(
|
||||||
Arrays.asList(OidcScopes.PROFILE, OidcScopes.EMAIL, OidcScopes.ADDRESS, OidcScopes.PHONE));
|
Arrays.asList(OidcScopes.PROFILE, OidcScopes.EMAIL, OidcScopes.ADDRESS, OidcScopes.PHONE));
|
||||||
private NimbusUserInfoResponseClient userInfoResponseClient = new NimbusUserInfoResponseClient();
|
private final OAuth2UserService<OAuth2UserRequest, OAuth2User> defaultUserService = new DefaultOAuth2UserService();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
|
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
|
||||||
Assert.notNull(userRequest, "userRequest cannot be null");
|
Assert.notNull(userRequest, "userRequest cannot be null");
|
||||||
OidcUserInfo userInfo = null;
|
OidcUserInfo userInfo = null;
|
||||||
if (this.shouldRetrieveUserInfo(userRequest)) {
|
if (this.shouldRetrieveUserInfo(userRequest)) {
|
||||||
ParameterizedTypeReference<Map<String, Object>> typeReference =
|
OAuth2User oauth2User = this.defaultUserService.loadUser(userRequest);
|
||||||
new ParameterizedTypeReference<Map<String, Object>>() {};
|
userInfo = new OidcUserInfo(oauth2User.getAttributes());
|
||||||
Map<String, Object> userAttributes = this.userInfoResponseClient.getUserInfoResponse(userRequest, typeReference);
|
|
||||||
userInfo = new OidcUserInfo(userAttributes);
|
|
||||||
|
|
||||||
// http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
|
// http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
|
||||||
// Due to the possibility of token substitution attacks (see Section 16.11),
|
// Due to the possibility of token substitution attacks (see Section 16.11),
|
||||||
|
@ -79,6 +79,8 @@ public class OidcUserServiceTests {
|
|||||||
when(this.providerDetails.getUserInfoEndpoint()).thenReturn(this.userInfoEndpoint);
|
when(this.providerDetails.getUserInfoEndpoint()).thenReturn(this.userInfoEndpoint);
|
||||||
when(this.clientRegistration.getAuthorizationGrantType()).thenReturn(AuthorizationGrantType.AUTHORIZATION_CODE);
|
when(this.clientRegistration.getAuthorizationGrantType()).thenReturn(AuthorizationGrantType.AUTHORIZATION_CODE);
|
||||||
|
|
||||||
|
when(this.userInfoEndpoint.getUserNameAttributeName()).thenReturn(StandardClaimNames.SUB);
|
||||||
|
|
||||||
this.accessToken = mock(OAuth2AccessToken.class);
|
this.accessToken = mock(OAuth2AccessToken.class);
|
||||||
Set<String> authorizedScopes = new LinkedHashSet<>(Arrays.asList(OidcScopes.OPENID, OidcScopes.PROFILE));
|
Set<String> authorizedScopes = new LinkedHashSet<>(Arrays.asList(OidcScopes.OPENID, OidcScopes.PROFILE));
|
||||||
when(this.accessToken.getScopes()).thenReturn(authorizedScopes);
|
when(this.accessToken.getScopes()).thenReturn(authorizedScopes);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user