diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/AuthorizationCodeAuthenticationFilterConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/AuthorizationCodeAuthenticationFilterConfigurer.java index e75aef3a71..4299c86b96 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/AuthorizationCodeAuthenticationFilterConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/AuthorizationCodeAuthenticationFilterConfigurer.java @@ -20,23 +20,26 @@ import org.springframework.security.config.annotation.web.configurers.AbstractAu import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.jwt.JwtDecoder; import org.springframework.security.jwt.nimbus.NimbusJwtDecoderJwkSupport; -import org.springframework.security.oauth2.client.web.AuthorizationCodeAuthenticationProcessingFilter; import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationProvider; import org.springframework.security.oauth2.client.authentication.AuthorizationCodeAuthenticationToken; -import org.springframework.security.oauth2.client.web.AuthorizationGrantTokenExchanger; import org.springframework.security.oauth2.client.authentication.jwt.DefaultProviderJwtDecoderRegistry; import org.springframework.security.oauth2.client.authentication.jwt.ProviderJwtDecoderRegistry; -import org.springframework.security.oauth2.client.web.nimbus.NimbusAuthorizationCodeTokenExchanger; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.token.InMemoryAccessTokenRepository; import org.springframework.security.oauth2.client.token.SecurityTokenRepository; +import org.springframework.security.oauth2.client.user.CustomUserTypesOAuth2UserService; +import org.springframework.security.oauth2.client.user.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.user.DelegatingOAuth2UserService; import org.springframework.security.oauth2.client.user.OAuth2UserService; -import org.springframework.security.oauth2.client.user.web.nimbus.NimbusOAuth2UserService; +import org.springframework.security.oauth2.client.web.AuthorizationCodeAuthenticationProcessingFilter; +import org.springframework.security.oauth2.client.web.AuthorizationGrantTokenExchanger; +import org.springframework.security.oauth2.client.web.nimbus.NimbusAuthorizationCodeTokenExchanger; import org.springframework.security.oauth2.core.AccessToken; import org.springframework.security.oauth2.core.provider.DefaultProviderMetadata; import org.springframework.security.oauth2.core.provider.ProviderMetadata; import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.security.oauth2.oidc.client.user.OidcUserService; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.matcher.RequestVariablesExtractor; import org.springframework.util.Assert; @@ -46,7 +49,9 @@ import org.springframework.web.util.UriComponentsBuilder; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -197,16 +202,28 @@ final class AuthorizationCodeAuthenticationFilterConfigurer + registration.getScope().stream().anyMatch(scope -> scope.equalsIgnoreCase("openid"))); + + } + private OAuth2UserService getUserInfoService() { if (this.userInfoService == null) { - NimbusOAuth2UserService nimbusOAuth2UserService = new NimbusOAuth2UserService(); - if (!this.customUserTypes.isEmpty()) { - nimbusOAuth2UserService.setCustomUserTypes(this.customUserTypes); - } + List oauth2UserServices = new ArrayList<>(); if (!this.userNameAttributeNames.isEmpty()) { - nimbusOAuth2UserService.setUserNameAttributeNames(this.userNameAttributeNames); + oauth2UserServices.add(new DefaultOAuth2UserService(this.userNameAttributeNames)); } - this.userInfoService = nimbusOAuth2UserService; + if (this.isOidcClientRegistered()) { + oauth2UserServices.add(new OidcUserService()); + } + if (!this.customUserTypes.isEmpty()) { + oauth2UserServices.add(new CustomUserTypesOAuth2UserService(this.customUserTypes)); + } + this.userInfoService = new DelegatingOAuth2UserService(oauth2UserServices); } return this.userInfoService; } diff --git a/oauth2/oauth2-client/spring-security-oauth2-client.gradle b/oauth2/oauth2-client/spring-security-oauth2-client.gradle index fd9e720231..bebf7a1469 100644 --- a/oauth2/oauth2-client/spring-security-oauth2-client.gradle +++ b/oauth2/oauth2-client/spring-security-oauth2-client.gradle @@ -3,11 +3,12 @@ apply plugin: 'io.spring.convention.spring-module' dependencies { compile project(':spring-security-core') compile project(':spring-security-oauth2-core') - compile project(':spring-security-jwt-jose') compile project(':spring-security-web') compile springCoreDependency compile 'org.springframework:spring-web' compile 'com.nimbusds:oauth2-oidc-sdk' + optional project(':spring-security-jwt-jose') + provided 'javax.servlet:javax.servlet-api' } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/CustomUserTypesOAuth2UserService.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/CustomUserTypesOAuth2UserService.java new file mode 100644 index 0000000000..b2e81ba9f4 --- /dev/null +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/CustomUserTypesOAuth2UserService.java @@ -0,0 +1,97 @@ +/* + * 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; + +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.PropertyAccessorFactory; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException; +import org.springframework.security.oauth2.client.authentication.OAuth2ClientAuthenticationToken; +import org.springframework.security.oauth2.client.user.nimbus.NimbusUserInfoRetriever; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.security.oauth2.oidc.client.authentication.OidcClientAuthenticationToken; +import org.springframework.util.Assert; + +import java.net.URI; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * An implementation of an {@link OAuth2UserService} that supports custom {@link OAuth2User} types. + *

+ * The custom user type(s) is supplied via the constructor, + * using a Map of {@link OAuth2User} type keyed by URI, + * representing the UserInfo Endpoint address. + *

+ * This implementation uses a {@link UserInfoRetriever} to obtain the user attributes + * of the End-User (resource owner) from the UserInfo Endpoint. + * + * @author Joe Grandja + * @since 5.0 + * @see OAuth2UserService + * @see OAuth2User + * @see UserInfoRetriever + */ +public class CustomUserTypesOAuth2UserService implements OAuth2UserService { + private final Map> customUserTypes; + private UserInfoRetriever userInfoRetriever = new NimbusUserInfoRetriever(); + + public CustomUserTypesOAuth2UserService(Map> customUserTypes) { + Assert.notEmpty(customUserTypes, "customUserTypes cannot be empty"); + this.customUserTypes = Collections.unmodifiableMap(new LinkedHashMap<>(customUserTypes)); + } + + @Override + public OAuth2User loadUser(OAuth2ClientAuthenticationToken clientAuthentication) throws OAuth2AuthenticationException { + URI userInfoUri = URI.create(clientAuthentication.getClientRegistration().getProviderDetails().getUserInfoUri()); + Class customUserType; + if ((customUserType = this.getCustomUserTypes().get(userInfoUri)) == null) { + return null; + } + + OAuth2User customUser; + try { + customUser = customUserType.newInstance(); + } catch (ReflectiveOperationException ex) { + throw new IllegalArgumentException("An error occurred while attempting to instantiate the custom OAuth2User \"" + + customUserType.getName() + "\": " + ex.getMessage(), ex); + } + + Map userAttributes = this.userInfoRetriever.retrieve(clientAuthentication); + if (OidcClientAuthenticationToken.class.isAssignableFrom(clientAuthentication.getClass())) { + userAttributes.putAll(((OidcClientAuthenticationToken)clientAuthentication).getIdToken().getClaims()); + } + + BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(customUser); + wrapper.setAutoGrowNestedPaths(true); + wrapper.setPropertyValues(userAttributes); + + return customUser; + } + + protected Map> getCustomUserTypes() { + return this.customUserTypes; + } + + protected UserInfoRetriever getUserInfoRetriever() { + return this.userInfoRetriever; + } + + public final void setUserInfoRetriever(UserInfoRetriever userInfoRetriever) { + Assert.notNull(userInfoRetriever, "userInfoRetriever cannot be null"); + this.userInfoRetriever = userInfoRetriever; + } +} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/DefaultOAuth2UserService.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/DefaultOAuth2UserService.java new file mode 100644 index 0000000000..d1502356f8 --- /dev/null +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/DefaultOAuth2UserService.java @@ -0,0 +1,96 @@ +/* + * 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; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException; +import org.springframework.security.oauth2.client.authentication.OAuth2ClientAuthenticationToken; +import org.springframework.security.oauth2.client.user.nimbus.NimbusUserInfoRetriever; +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.security.oauth2.oidc.client.authentication.OidcClientAuthenticationToken; +import org.springframework.util.Assert; + +import java.net.URI; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +/** + * An implementation of an {@link OAuth2UserService} that supports standard OAuth 2.0 Provider's. + *

+ * For standard OAuth 2.0 Provider's, the attribute name (from the UserInfo Response) + * for the "user's name" is required. This is supplied via the constructor, + * mapped by URI, which represents the UserInfo Endpoint address. + *

+ * NOTE: Attribute names are not standardized between providers and therefore will vary. + * Please consult the provider's API documentation for the set of supported user attribute names. + *

+ * This implementation uses a {@link UserInfoRetriever} to obtain the user attributes + * of the End-User (resource owner) from the UserInfo Endpoint. + * + * @author Joe Grandja + * @since 5.0 + * @see OAuth2UserService + * @see DefaultOAuth2User + * @see UserInfoRetriever + */ +public class DefaultOAuth2UserService implements OAuth2UserService { + private final Map userNameAttributeNames; + private UserInfoRetriever userInfoRetriever = new NimbusUserInfoRetriever(); + + public DefaultOAuth2UserService(Map userNameAttributeNames) { + Assert.notEmpty(userNameAttributeNames, "userNameAttributeNames cannot be empty"); + this.userNameAttributeNames = Collections.unmodifiableMap(new LinkedHashMap<>(userNameAttributeNames)); + } + + @Override + public OAuth2User loadUser(OAuth2ClientAuthenticationToken clientAuthentication) throws OAuth2AuthenticationException { + if (OidcClientAuthenticationToken.class.isAssignableFrom(clientAuthentication.getClass())) { + return null; + } + + URI userInfoUri = URI.create(clientAuthentication.getClientRegistration().getProviderDetails().getUserInfoUri()); + if (!this.getUserNameAttributeNames().containsKey(userInfoUri)) { + throw new IllegalArgumentException( + "Missing required \"user name\" attribute name for UserInfo Endpoint: " + userInfoUri.toString()); + } + String userNameAttributeName = this.getUserNameAttributeNames().get(userInfoUri); + + Map userAttributes = this.getUserInfoRetriever().retrieve(clientAuthentication); + GrantedAuthority authority = new OAuth2UserAuthority(userAttributes); + Set authorities = new HashSet<>(); + authorities.add(authority); + + return new DefaultOAuth2User(authorities, userAttributes, userNameAttributeName); + } + + protected Map getUserNameAttributeNames() { + return this.userNameAttributeNames; + } + + protected UserInfoRetriever getUserInfoRetriever() { + return this.userInfoRetriever; + } + + public final void setUserInfoRetriever(UserInfoRetriever userInfoRetriever) { + Assert.notNull(userInfoRetriever, "userInfoRetriever cannot be null"); + this.userInfoRetriever = userInfoRetriever; + } +} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/DelegatingOAuth2UserService.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/DelegatingOAuth2UserService.java new file mode 100644 index 0000000000..e6024113c4 --- /dev/null +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/DelegatingOAuth2UserService.java @@ -0,0 +1,56 @@ +/* + * 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; + +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException; +import org.springframework.security.oauth2.client.authentication.OAuth2ClientAuthenticationToken; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.util.Assert; + +import java.util.List; +import java.util.Objects; + +/** + * An implementation of an {@link OAuth2UserService} that simply delegates + * to it's internal List of {@link OAuth2UserService}'s. + *

+ * Each {@link OAuth2UserService} is given a chance to + * {@link OAuth2UserService#loadUser(OAuth2ClientAuthenticationToken) load} an {@link OAuth2User} + * with the first non-null {@link OAuth2User} being returned. + * + * @author Joe Grandja + * @since 5.0 + * @see OAuth2UserService + * @see OAuth2User + */ +public class DelegatingOAuth2UserService implements OAuth2UserService { + private final List oauth2UserServices; + + public DelegatingOAuth2UserService(List oauth2UserServices) { + Assert.notEmpty(oauth2UserServices, "oauth2UserServices cannot be empty"); + this.oauth2UserServices = oauth2UserServices; + } + + @Override + public OAuth2User loadUser(OAuth2ClientAuthenticationToken clientAuthentication) throws OAuth2AuthenticationException { + OAuth2User oauth2User = this.oauth2UserServices.stream() + .map(oauth2UserService -> oauth2UserService.loadUser(clientAuthentication)) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); + return oauth2User; + } +} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/OAuth2UserService.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/OAuth2UserService.java index 01c3f667d8..7538009192 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/OAuth2UserService.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/OAuth2UserService.java @@ -19,12 +19,10 @@ import org.springframework.security.core.AuthenticatedPrincipal; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException; import org.springframework.security.oauth2.client.authentication.OAuth2ClientAuthenticationToken; import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.security.oauth2.oidc.core.UserInfo; -import org.springframework.security.oauth2.oidc.core.user.OidcUser; /** - * Implementations of this interface are responsible for obtaining - * the end-user's (resource owner) attributes from the UserInfo Endpoint + * Implementations of this interface are responsible for obtaining the user attributes + * of the End-User (resource owner) from the UserInfo Endpoint * using the provided {@link OAuth2ClientAuthenticationToken#getAccessToken()} * and returning an {@link AuthenticatedPrincipal} in the form of an {@link OAuth2User}. * @@ -33,11 +31,9 @@ import org.springframework.security.oauth2.oidc.core.user.OidcUser; * @see OAuth2ClientAuthenticationToken * @see AuthenticatedPrincipal * @see OAuth2User - * @see OidcUser - * @see UserInfo */ public interface OAuth2UserService { - OAuth2User loadUser(OAuth2ClientAuthenticationToken token) throws OAuth2AuthenticationException; + OAuth2User loadUser(OAuth2ClientAuthenticationToken clientAuthentication) throws OAuth2AuthenticationException; } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/UserInfoRetriever.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/UserInfoRetriever.java new file mode 100644 index 0000000000..36e5936185 --- /dev/null +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/UserInfoRetriever.java @@ -0,0 +1,37 @@ +/* + * 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; + +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException; +import org.springframework.security.oauth2.client.authentication.OAuth2ClientAuthenticationToken; + +import java.util.Map; + +/** + * A strategy for retrieving the user attributes + * of the End-User (resource owner) from the UserInfo Endpoint + * using the provided {@link OAuth2ClientAuthenticationToken#getAccessToken()}. + * + * @author Joe Grandja + * @since 5.0 + * @see OAuth2ClientAuthenticationToken + * @see OAuth2UserService + */ +public interface UserInfoRetriever { + + Map retrieve(OAuth2ClientAuthenticationToken clientAuthentication) throws OAuth2AuthenticationException; + +} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/web/nimbus/NimbusClientHttpResponse.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/nimbus/NimbusClientHttpResponse.java similarity index 95% rename from oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/web/nimbus/NimbusClientHttpResponse.java rename to oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/nimbus/NimbusClientHttpResponse.java index 870d69094b..2bf866bc05 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/web/nimbus/NimbusClientHttpResponse.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/nimbus/NimbusClientHttpResponse.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.security.oauth2.client.user.web.nimbus; +package org.springframework.security.oauth2.client.user.nimbus; import com.nimbusds.oauth2.sdk.http.HTTPResponse; import org.springframework.http.HttpHeaders; @@ -27,7 +27,7 @@ import java.io.InputStream; import java.nio.charset.Charset; /** - * An implementation of a {@link ClientHttpResponse} which is used by {@link NimbusOAuth2UserService}. + * An implementation of a {@link ClientHttpResponse} which is used by {@link NimbusUserInfoRetriever}. * *

* NOTE: This class is intended for internal use only. diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/nimbus/NimbusUserInfoRetriever.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/nimbus/NimbusUserInfoRetriever.java new file mode 100644 index 0000000000..72387571f1 --- /dev/null +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/nimbus/NimbusUserInfoRetriever.java @@ -0,0 +1,103 @@ +/* + * 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.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException; +import org.springframework.security.oauth2.client.authentication.OAuth2ClientAuthenticationToken; +import org.springframework.security.oauth2.client.user.UserInfoRetriever; +import org.springframework.security.oauth2.core.OAuth2Error; + +import java.io.IOException; +import java.net.URI; +import java.util.Map; + +/** + * An implementation of a {@link UserInfoRetriever} that uses the Nimbus OAuth 2.0 SDK internally. + * + * @author Joe Grandja + * @since 5.0 + * @see UserInfoRetriever + * @see Nimbus OAuth 2.0 SDK + */ +public class NimbusUserInfoRetriever implements UserInfoRetriever { + private static final String INVALID_USER_INFO_RESPONSE_ERROR_CODE = "invalid_user_info_response"; + private final HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter(); + + @Override + public Map retrieve(OAuth2ClientAuthenticationToken clientAuthentication) throws OAuth2AuthenticationException { + URI userInfoUri = URI.create(clientAuthentication.getClientRegistration().getProviderDetails().getUserInfoUri()); + BearerAccessToken accessToken = new BearerAccessToken(clientAuthentication.getAccessToken().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) { + 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()); + } + + try { + return (Map) this.jackson2HttpMessageConverter.read(Map.class, new NimbusClientHttpResponse(httpResponse)); + } 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); + } + } +} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/web/nimbus/NimbusOAuth2UserService.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/web/nimbus/NimbusOAuth2UserService.java deleted file mode 100644 index 7c7bf400a9..0000000000 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/user/web/nimbus/NimbusOAuth2UserService.java +++ /dev/null @@ -1,232 +0,0 @@ -/* - * 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.web.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.beans.BeanWrapper; -import org.springframework.beans.PropertyAccessorFactory; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; -import org.springframework.security.authentication.AuthenticationServiceException; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException; -import org.springframework.security.oauth2.client.authentication.OAuth2ClientAuthenticationToken; -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.DefaultOAuth2User; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.security.oauth2.core.user.OAuth2UserAuthority; -import org.springframework.security.oauth2.oidc.client.authentication.OidcClientAuthenticationToken; -import org.springframework.security.oauth2.oidc.core.UserInfo; -import org.springframework.security.oauth2.oidc.core.user.DefaultOidcUser; -import org.springframework.security.oauth2.oidc.core.user.OidcUser; -import org.springframework.security.oauth2.oidc.core.user.OidcUserAuthority; -import org.springframework.util.Assert; - -import java.io.IOException; -import java.net.URI; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -/** - * An implementation of an {@link OAuth2UserService} that uses the Nimbus OAuth 2.0 SDK internally. - * - *

- * This implementation may be configured with a Map of custom {@link OAuth2User} types - * keyed by URI, which represents the UserInfo Endpoint address. - * - *

- * For {@link OAuth2User}'s registered at a standard OAuth 2.0 Provider, the attribute name - * for the "user's name" is required. This can be supplied via {@link #setUserNameAttributeNames(Map)}, - * keyed by URI, which represents the UserInfo Endpoint address. - * - * @author Joe Grandja - * @since 5.0 - * @see OAuth2ClientAuthenticationToken - * @see OidcClientAuthenticationToken - * @see OAuth2User - * @see OidcUser - * @see UserInfo - * @see Nimbus OAuth 2.0 SDK - */ -public class NimbusOAuth2UserService implements OAuth2UserService { - private static final String INVALID_USER_INFO_RESPONSE_ERROR_CODE = "invalid_user_info_response"; - private final HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter(); - private Map userNameAttributeNames = Collections.unmodifiableMap(Collections.emptyMap()); - private Map> customUserTypes = Collections.unmodifiableMap(Collections.emptyMap()); - - public NimbusOAuth2UserService() { - } - - @Override - public final OAuth2User loadUser(OAuth2ClientAuthenticationToken token) throws OAuth2AuthenticationException { - URI userInfoUri = this.getUserInfoUri(token); - - if (this.getCustomUserTypes().containsKey(userInfoUri)) { - return this.loadCustomUser(token); - } - if (OidcClientAuthenticationToken.class.isAssignableFrom(token.getClass())) { - return this.loadOidcUser((OidcClientAuthenticationToken)token); - } - - return this.loadOAuth2User(token); - } - - protected OidcUser loadOidcUser(OidcClientAuthenticationToken token) throws OAuth2AuthenticationException { - // TODO Retrieving the UserInfo should be optional. Need to add the capability for opting in/out - Map userAttributes = this.getUserInfo(token); - UserInfo userInfo = new UserInfo(userAttributes); - - GrantedAuthority authority = new OidcUserAuthority(token.getIdToken(), userInfo); - Set authorities = new HashSet<>(); - authorities.add(authority); - - return new DefaultOidcUser(authorities, token.getIdToken(), userInfo); - } - - protected OAuth2User loadOAuth2User(OAuth2ClientAuthenticationToken token) throws OAuth2AuthenticationException { - URI userInfoUri = this.getUserInfoUri(token); - if (!this.getUserNameAttributeNames().containsKey(userInfoUri)) { - throw new IllegalArgumentException("The attribute name for the \"user's name\" is required for the OAuth2User " + - " retrieved from the UserInfo Endpoint -> " + userInfoUri.toString()); - } - String userNameAttributeName = this.getUserNameAttributeNames().get(userInfoUri); - - Map userAttributes = this.getUserInfo(token); - - GrantedAuthority authority = new OAuth2UserAuthority(userAttributes); - Set authorities = new HashSet<>(); - authorities.add(authority); - - return new DefaultOAuth2User(authorities, userAttributes, userNameAttributeName); - } - - protected OAuth2User loadCustomUser(OAuth2ClientAuthenticationToken token) throws OAuth2AuthenticationException { - URI userInfoUri = this.getUserInfoUri(token); - Class customUserType = this.getCustomUserTypes().get(userInfoUri); - - OAuth2User user; - try { - user = customUserType.newInstance(); - } catch (ReflectiveOperationException ex) { - throw new IllegalArgumentException("An error occurred while attempting to instantiate the custom OAuth2User \"" + - customUserType.getName() + "\" -> " + ex.getMessage(), ex); - } - - Map userAttributes = this.getUserInfo(token); - if (OidcClientAuthenticationToken.class.isAssignableFrom(token.getClass())) { - userAttributes.putAll(((OidcClientAuthenticationToken)token).getIdToken().getClaims()); - } - BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(user); - wrapper.setAutoGrowNestedPaths(true); - wrapper.setPropertyValues(userAttributes); - - return user; - } - - protected Map getUserInfo(OAuth2ClientAuthenticationToken token) throws OAuth2AuthenticationException { - URI userInfoUri = this.getUserInfoUri(token); - - BearerAccessToken accessToken = new BearerAccessToken(token.getAccessToken().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) { - 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()); - } - - try { - return (Map) this.jackson2HttpMessageConverter.read(Map.class, new NimbusClientHttpResponse(httpResponse)); - } 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); - } - } - - protected Map getUserNameAttributeNames() { - return this.userNameAttributeNames; - } - - public final void setUserNameAttributeNames(Map userNameAttributeNames) { - Assert.notEmpty(userNameAttributeNames, "userNameAttributeNames cannot be empty"); - this.userNameAttributeNames = Collections.unmodifiableMap(new HashMap<>(userNameAttributeNames)); - } - - protected Map> getCustomUserTypes() { - return this.customUserTypes; - } - - public final void setCustomUserTypes(Map> customUserTypes) { - Assert.notEmpty(customUserTypes, "customUserTypes cannot be empty"); - this.customUserTypes = Collections.unmodifiableMap(new HashMap<>(customUserTypes)); - } - - private URI getUserInfoUri(OAuth2ClientAuthenticationToken token) { - ClientRegistration clientRegistration = token.getClientRegistration(); - try { - return new URI(clientRegistration.getProviderDetails().getUserInfoUri()); - } catch (Exception ex) { - throw new IllegalArgumentException("An error occurred parsing the UserInfo URI: " + - clientRegistration.getProviderDetails().getUserInfoUri(), ex); - } - } -} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/oidc/client/user/OidcUserService.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/oidc/client/user/OidcUserService.java new file mode 100644 index 0000000000..2d539c274d --- /dev/null +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/oidc/client/user/OidcUserService.java @@ -0,0 +1,78 @@ +/* + * 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.oidc.client.user; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationException; +import org.springframework.security.oauth2.client.authentication.OAuth2ClientAuthenticationToken; +import org.springframework.security.oauth2.client.user.OAuth2UserService; +import org.springframework.security.oauth2.client.user.UserInfoRetriever; +import org.springframework.security.oauth2.client.user.nimbus.NimbusUserInfoRetriever; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.security.oauth2.oidc.client.authentication.OidcClientAuthenticationToken; +import org.springframework.security.oauth2.oidc.core.UserInfo; +import org.springframework.security.oauth2.oidc.core.user.DefaultOidcUser; +import org.springframework.security.oauth2.oidc.core.user.OidcUserAuthority; +import org.springframework.util.Assert; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * An implementation of an {@link OAuth2UserService} that supports OpenID Connect 1.0 Provider's. + *

+ * This implementation uses a {@link UserInfoRetriever} to obtain the user attributes + * of the End-User (resource owner) from the UserInfo Endpoint + * and constructs a {@link UserInfo} instance. + * + * @author Joe Grandja + * @since 5.0 + * @see OAuth2UserService + * @see OidcClientAuthenticationToken + * @see DefaultOidcUser + * @see UserInfo + * @see UserInfoRetriever + */ +public class OidcUserService implements OAuth2UserService { + private UserInfoRetriever userInfoRetriever = new NimbusUserInfoRetriever(); + + @Override + public OAuth2User loadUser(OAuth2ClientAuthenticationToken clientAuthentication) throws OAuth2AuthenticationException { + if (!OidcClientAuthenticationToken.class.isAssignableFrom(clientAuthentication.getClass())) { + return null; + } + OidcClientAuthenticationToken oidcClientAuthentication = (OidcClientAuthenticationToken)clientAuthentication; + + Map userAttributes = this.getUserInfoRetriever().retrieve(oidcClientAuthentication); + UserInfo userInfo = new UserInfo(userAttributes); + + GrantedAuthority authority = new OidcUserAuthority(oidcClientAuthentication.getIdToken(), userInfo); + Set authorities = new HashSet<>(); + authorities.add(authority); + + return new DefaultOidcUser(authorities, oidcClientAuthentication.getIdToken(), userInfo); + } + + protected UserInfoRetriever getUserInfoRetriever() { + return this.userInfoRetriever; + } + + public final void setUserInfoRetriever(UserInfoRetriever userInfoRetriever) { + Assert.notNull(userInfoRetriever, "userInfoRetriever cannot be null"); + this.userInfoRetriever = userInfoRetriever; + } +} diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/user/OAuth2UserAuthority.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/user/OAuth2UserAuthority.java index a3d7a25f34..2d460d5a8b 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/user/OAuth2UserAuthority.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/user/OAuth2UserAuthority.java @@ -43,7 +43,7 @@ public class OAuth2UserAuthority implements GrantedAuthority { Assert.hasText(authority, "authority cannot be empty"); Assert.notEmpty(attributes, "attributes cannot be empty"); this.authority = authority; - this.setAttributes(attributes); + this.attributes = Collections.unmodifiableMap(new LinkedHashMap<>(attributes)); } @Override @@ -55,11 +55,6 @@ public class OAuth2UserAuthority implements GrantedAuthority { return this.attributes; } - protected final void setAttributes(Map attributes) { - Assert.notEmpty(attributes, "attributes cannot be empty"); - this.attributes = Collections.unmodifiableMap(new LinkedHashMap<>(attributes)); - } - @Override public boolean equals(Object obj) { if (this == obj) { diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/oidc/core/user/DefaultOidcUser.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/oidc/core/user/DefaultOidcUser.java index 3a173fcfb4..33dbe781ec 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/oidc/core/user/DefaultOidcUser.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/oidc/core/user/DefaultOidcUser.java @@ -16,16 +16,14 @@ package org.springframework.security.oauth2.oidc.core.user; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - import org.springframework.security.core.GrantedAuthority; import org.springframework.security.oauth2.core.user.DefaultOAuth2User; import org.springframework.security.oauth2.oidc.core.IdToken; import org.springframework.security.oauth2.oidc.core.IdTokenClaim; import org.springframework.security.oauth2.oidc.core.UserInfo; -import org.springframework.util.Assert; + +import java.util.Map; +import java.util.Set; /** * The default implementation of an {@link OidcUser}. @@ -61,7 +59,7 @@ public class DefaultOidcUser extends DefaultOAuth2User implements OidcUser { public DefaultOidcUser(Set authorities, IdToken idToken, UserInfo userInfo, String nameAttributeKey) { - super(authorities, resolveAttributes(idToken, userInfo), nameAttributeKey); + super(authorities, OidcUser.collectClaims(idToken, userInfo), nameAttributeKey); this.idToken = idToken; this.userInfo = userInfo; } @@ -78,14 +76,4 @@ public class DefaultOidcUser extends DefaultOAuth2User implements OidcUser { public UserInfo getUserInfo() { return this.userInfo; } - - private static Map resolveAttributes(IdToken idToken, UserInfo userInfo) { - Assert.notNull(idToken, "idToken cannot be null"); - Map attributes = new HashMap<>(); - attributes.putAll(idToken.getClaims()); - if (userInfo != null) { - attributes.putAll(userInfo.getClaims()); - } - return attributes; - } } diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/oidc/core/user/OidcUser.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/oidc/core/user/OidcUser.java index 51197caec2..8c63fa8aac 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/oidc/core/user/OidcUser.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/oidc/core/user/OidcUser.java @@ -22,7 +22,9 @@ import org.springframework.security.oauth2.oidc.core.IdToken; import org.springframework.security.oauth2.oidc.core.IdTokenClaimAccessor; import org.springframework.security.oauth2.oidc.core.StandardClaimAccessor; import org.springframework.security.oauth2.oidc.core.UserInfo; +import org.springframework.util.Assert; +import java.util.HashMap; import java.util.Map; /** @@ -54,4 +56,13 @@ public interface OidcUser extends OAuth2User, IdTokenClaimAccessor { Map getClaims(); + static Map collectClaims(IdToken idToken, UserInfo userInfo) { + Assert.notNull(idToken, "idToken cannot be null"); + Map claims = new HashMap<>(); + if (userInfo != null) { + claims.putAll(userInfo.getClaims()); + } + claims.putAll(idToken.getClaims()); + return claims; + } } diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/oidc/core/user/OidcUserAuthority.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/oidc/core/user/OidcUserAuthority.java index 9a0fa5e361..23a98162ef 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/oidc/core/user/OidcUserAuthority.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/oidc/core/user/OidcUserAuthority.java @@ -21,10 +21,6 @@ import org.springframework.security.oauth2.core.user.OAuth2UserAuthority; import org.springframework.security.oauth2.oidc.core.IdToken; import org.springframework.security.oauth2.oidc.core.UserInfo; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; - /** * A {@link GrantedAuthority} that is associated with an {@link OidcUser}. * @@ -46,16 +42,9 @@ public class OidcUserAuthority extends OAuth2UserAuthority { } public OidcUserAuthority(String authority, IdToken idToken, UserInfo userInfo) { - super(authority, idToken.getClaims()); + super(authority, OidcUser.collectClaims(idToken, userInfo)); this.idToken = idToken; this.userInfo = userInfo; - if (userInfo != null) { - this.setAttributes( - Stream.of(this.getAttributes(), userInfo.getClaims()) - .flatMap(m -> m.entrySet().stream()) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (k1, k2) -> k1)) - ); - } } public IdToken getIdToken() { diff --git a/samples/boot/oauth2login/spring-security-samples-boot-oauth2login.gradle b/samples/boot/oauth2login/spring-security-samples-boot-oauth2login.gradle index 28ab539c51..6e682620a1 100644 --- a/samples/boot/oauth2login/spring-security-samples-boot-oauth2login.gradle +++ b/samples/boot/oauth2login/spring-security-samples-boot-oauth2login.gradle @@ -3,6 +3,7 @@ apply plugin: 'io.spring.convention.spring-sample-boot' dependencies { compile project(':spring-security-config') compile project(':spring-security-oauth2-client') + compile project(':spring-security-jwt-jose') compile 'org.springframework:spring-webflux' compile 'org.springframework.boot:spring-boot-starter-thymeleaf' compile 'org.springframework.boot:spring-boot-starter-web'