/* * Copyright 2012-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.user.nimbus; 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.convert.converter.Converter; import org.springframework.http.MediaType; import org.springframework.http.client.ClientHttpResponse; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.core.AuthenticatedPrincipal; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.user.OAuth2UserService; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.oidc.user.UserInfo; import org.springframework.util.Assert; import java.io.IOException; import java.net.URI; import java.util.HashMap; import java.util.Map; /** * An implementation of an {@link OAuth2UserService} that uses the <b>Nimbus OAuth 2.0 SDK</b> internally. * * <p> * This implementation uses a <code>Map</code> of <code>Converter</code>'s <i>keyed</i> by <code>URI</code>. * The <code>URI</code> represents the <i>UserInfo Endpoint</i> address and the mapped <code>Converter</code> * is capable of converting the <i>UserInfo Response</i> to either an * {@link OAuth2User} (for a standard <i>OAuth 2.0 Provider</i>) or * {@link UserInfo} (for an <i>OpenID Connect 1.0 Provider</i>). * * @author Joe Grandja * @since 5.0 * @see OAuth2AuthenticationToken * @see AuthenticatedPrincipal * @see OAuth2User * @see UserInfo * @see Converter * @see <a target="_blank" href="https://connect2id.com/products/nimbus-oauth-openid-connect-sdk">Nimbus OAuth 2.0 SDK</a> */ public class NimbusOAuth2UserService implements OAuth2UserService { private static final String INVALID_USER_INFO_RESPONSE_ERROR_CODE = "invalid_user_info_response"; private final Map<URI, Converter<ClientHttpResponse, ? extends OAuth2User>> userInfoTypeConverters; public NimbusOAuth2UserService(Map<URI, Converter<ClientHttpResponse, ? extends OAuth2User>> userInfoTypeConverters) { Assert.notEmpty(userInfoTypeConverters, "userInfoTypeConverters cannot be empty"); this.userInfoTypeConverters = new HashMap<>(userInfoTypeConverters); } @Override public OAuth2User loadUser(OAuth2AuthenticationToken token) throws OAuth2AuthenticationException { OAuth2User user; try { ClientRegistration clientRegistration = token.getClientRegistration(); URI userInfoUri; try { userInfoUri = new URI(clientRegistration.getProviderDetails().getUserInfoUri()); } catch (Exception ex) { throw new IllegalArgumentException("An error occurred parsing the userInfo URI: " + clientRegistration.getProviderDetails().getUserInfoUri(), ex); } Converter<ClientHttpResponse, ? extends OAuth2User> userInfoConverter = this.userInfoTypeConverters.get(userInfoUri); if (userInfoConverter == null) { throw new IllegalArgumentException("There is no available User Info converter for " + userInfoUri.toString()); } BearerAccessToken accessToken = new BearerAccessToken(token.getAccessToken().getTokenValue()); // Request the User Info UserInfoRequest userInfoRequest = new UserInfoRequest(userInfoUri, accessToken); HTTPRequest httpRequest = userInfoRequest.toHTTPRequest(); httpRequest.setAccept(MediaType.APPLICATION_JSON_VALUE); HTTPResponse httpResponse = httpRequest.send(); if (httpResponse.getStatusCode() != HTTPResponse.SC_OK) { UserInfoErrorResponse userInfoErrorResponse = UserInfoErrorResponse.parse(httpResponse); ErrorObject errorObject = userInfoErrorResponse.getErrorObject(); OAuth2Error oauth2Error = new OAuth2Error(errorObject.getCode(), errorObject.getDescription(), (errorObject.getURI() != null ? errorObject.getURI().toString() : null)); throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); } user = userInfoConverter.convert(new NimbusClientHttpResponse(httpResponse)); } catch (ParseException ex) { // This error occurs if the User Info Response is not well-formed or invalid throw new OAuth2AuthenticationException(new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE), ex); } catch (IOException ex) { // This error occurs when there is a network-related issue throw new AuthenticationServiceException("An error occurred while sending the User Info Request: " + ex.getMessage(), ex); } return user; } }