diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java index 8a2f2ed5df..14f4685e61 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java @@ -15,11 +15,15 @@ */ package org.springframework.security.config.annotation.web.configurers.oauth2.client; +import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.context.ApplicationContext; import org.springframework.core.ResolvableType; import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; +import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.authentication.AuthorizationGrantTokenExchanger; import org.springframework.security.oauth2.client.authentication.NimbusAuthorizationCodeTokenExchanger; import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken; @@ -88,6 +92,12 @@ public final class OAuth2LoginConfigurer> exten return this; } + public OAuth2LoginConfigurer authorizedClientService(OAuth2AuthorizedClientService authorizedClientService) { + Assert.notNull(authorizedClientService, "authorizedClientService cannot be null"); + this.getBuilder().setSharedObject(OAuth2AuthorizedClientService.class, authorizedClientService); + return this; + } + @Override public OAuth2LoginConfigurer loginPage(String loginPage) { Assert.hasText(loginPage, "loginPage cannot be empty"); @@ -299,6 +309,7 @@ public final class OAuth2LoginConfigurer> exten authorizationResponseFilter.setAuthorizationRequestRepository( this.authorizationEndpointConfig.authorizationRequestRepository); } + authorizationResponseFilter.setAuthorizedClientService(this.getAuthorizedClientService()); if (this.tokenEndpointConfig.accessTokenRepository != null) { authorizationResponseFilter.setAccessTokenRepository( this.tokenEndpointConfig.accessTokenRepository); @@ -324,6 +335,26 @@ public final class OAuth2LoginConfigurer> exten return this.getBuilder().getSharedObject(ApplicationContext.class).getBean(ClientRegistrationRepository.class); } + private OAuth2AuthorizedClientService getAuthorizedClientService() { + OAuth2AuthorizedClientService authorizedClientService = this.getBuilder().getSharedObject(OAuth2AuthorizedClientService.class); + if (authorizedClientService == null) { + authorizedClientService = this.getAuthorizedClientServiceBean(); + if (authorizedClientService == null) { + authorizedClientService = new InMemoryOAuth2AuthorizedClientService<>(this.getClientRegistrationRepository()); + } + this.getBuilder().setSharedObject(OAuth2AuthorizedClientService.class, authorizedClientService); + } + return authorizedClientService; + } + + private OAuth2AuthorizedClientService getAuthorizedClientServiceBean() { + Map authorizedClientServiceMap = + BeanFactoryUtils.beansOfTypeIncludingAncestors( + this.getBuilder().getSharedObject(ApplicationContext.class), + OAuth2AuthorizedClientService.class); + return !authorizedClientServiceMap.isEmpty() ? authorizedClientServiceMap.values().iterator().next() : null; + } + private void initDefaultLoginFilter(B http) { DefaultLoginPageGeneratingFilter loginPageGeneratingFilter = http.getSharedObject(DefaultLoginPageGeneratingFilter.class); if (loginPageGeneratingFilter == null || this.isCustomLoginPage()) { diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/InMemoryOAuth2AuthorizedClientService.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/InMemoryOAuth2AuthorizedClientService.java new file mode 100644 index 0000000000..6640f63b39 --- /dev/null +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/InMemoryOAuth2AuthorizedClientService.java @@ -0,0 +1,85 @@ +/* + * 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; + +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.client.oidc.OidcAuthorizedClient; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.util.Assert; + +import java.util.Base64; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * An {@link OAuth2AuthorizedClientService} that stores + * {@link OAuth2AuthorizedClient Authorized Client(s)} in-memory. + * + * @author Joe Grandja + * @since 5.0 + * @see OAuth2AuthorizedClientService + * @see OAuth2AuthorizedClient + * @see OidcAuthorizedClient + * @see ClientRegistration + * @see Authentication + * + * @param The type of OAuth 2.0 Authorized Client + */ +public final class InMemoryOAuth2AuthorizedClientService implements OAuth2AuthorizedClientService { + private final Map authorizedClients = new ConcurrentHashMap<>(); + private final ClientRegistrationRepository clientRegistrationRepository; + + public InMemoryOAuth2AuthorizedClientService(ClientRegistrationRepository clientRegistrationRepository) { + Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null"); + this.clientRegistrationRepository = clientRegistrationRepository; + } + + @Override + public T loadAuthorizedClient(String clientRegistrationId, Authentication principal) { + Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty"); + Assert.notNull(principal, "principal cannot be null"); + ClientRegistration registration = this.clientRegistrationRepository.findByRegistrationId(clientRegistrationId); + if (registration == null) { + return null; + } + return this.authorizedClients.get(this.getIdentifier(registration, principal)); + } + + @Override + public void saveAuthorizedClient(T authorizedClient, Authentication principal) { + Assert.notNull(authorizedClient, "authorizedClient cannot be null"); + Assert.notNull(principal, "principal cannot be null"); + this.authorizedClients.put(this.getIdentifier( + authorizedClient.getClientRegistration(), principal), authorizedClient); + } + + @Override + public T removeAuthorizedClient(String clientRegistrationId, Authentication principal) { + Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty"); + Assert.notNull(principal, "principal cannot be null"); + ClientRegistration registration = this.clientRegistrationRepository.findByRegistrationId(clientRegistrationId); + if (registration == null) { + return null; + } + return this.authorizedClients.remove(this.getIdentifier(registration, principal)); + } + + private String getIdentifier(ClientRegistration registration, Authentication principal) { + String identifier = "[" + registration.getRegistrationId() + "][" + principal.getName() + "]"; + return Base64.getEncoder().encodeToString(identifier.getBytes()); + } +} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClient.java index 80c8d46116..5297416331 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClient.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClient.java @@ -33,7 +33,6 @@ import org.springframework.util.Assert; * @since 5.0 * @see ClientRegistration * @see OAuth2AccessToken - * @see Section 5.1 Access Token Response */ public class OAuth2AuthorizedClient { private final ClientRegistration clientRegistration; diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientService.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientService.java new file mode 100644 index 0000000000..9f9f21e242 --- /dev/null +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientService.java @@ -0,0 +1,47 @@ +/* + * 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; + +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.client.oidc.OidcAuthorizedClient; +import org.springframework.security.oauth2.client.registration.ClientRegistration; + +/** + * Implementations of this interface are responsible for the management + * of {@link OAuth2AuthorizedClient Authorized Client(s)}, which provide the purpose + * of associating an {@link OAuth2AuthorizedClient#getAccessToken() Access Token} to a + * {@link OAuth2AuthorizedClient#getClientRegistration() Client} and Resource Owner, + * who is the {@link OAuth2AuthorizedClient#getPrincipalName() Principal} + * that originally granted the authorization. + * + * @author Joe Grandja + * @since 5.0 + * @see OAuth2AuthorizedClient + * @see OidcAuthorizedClient + * @see ClientRegistration + * @see Authentication + * + * @param The type of OAuth 2.0 Authorized Client + */ +public interface OAuth2AuthorizedClientService { + + T loadAuthorizedClient(String clientRegistrationId, Authentication principal); + + void saveAuthorizedClient(T authorizedClient, Authentication principal); + + T removeAuthorizedClient(String clientRegistrationId, Authentication principal); + +} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/OidcAuthorizedClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/OidcAuthorizedClient.java index b06b763811..e24b6f502f 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/OidcAuthorizedClient.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/OidcAuthorizedClient.java @@ -36,7 +36,6 @@ import org.springframework.util.Assert; * @since 5.0 * @see OAuth2AuthorizedClient * @see OidcIdToken - * @see 3.1.3.3 Successful Token Response */ public class OidcAuthorizedClient extends OAuth2AuthorizedClient { private final OidcIdToken idToken; diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2LoginAuthenticationFilter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2LoginAuthenticationFilter.java index 8c1226e0ea..af05907d49 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2LoginAuthenticationFilter.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2LoginAuthenticationFilter.java @@ -19,6 +19,7 @@ import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken; import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider; @@ -84,6 +85,7 @@ public class OAuth2LoginAuthenticationFilter extends AbstractAuthenticationProce public static final String DEFAULT_FILTER_PROCESSES_URI = "/login/oauth2/code/*"; private static final String AUTHORIZATION_REQUEST_NOT_FOUND_ERROR_CODE = "authorization_request_not_found"; private ClientRegistrationRepository clientRegistrationRepository; + private OAuth2AuthorizedClientService authorizedClientService; private AuthorizationRequestRepository authorizationRequestRepository = new HttpSessionOAuth2AuthorizationRequestRepository(); private OAuth2TokenRepository accessTokenRepository = new InMemoryAccessTokenRepository(); @@ -100,6 +102,7 @@ public class OAuth2LoginAuthenticationFilter extends AbstractAuthenticationProce public void afterPropertiesSet() { super.afterPropertiesSet(); Assert.notNull(this.clientRegistrationRepository, "clientRegistrationRepository cannot be null"); + Assert.notNull(this.authorizedClientService, "authorizedClientService cannot be null"); } @Override @@ -140,6 +143,9 @@ public class OAuth2LoginAuthenticationFilter extends AbstractAuthenticationProce OAuth2AuthenticationToken oauth2Authentication = (OAuth2AuthenticationToken) this.getAuthenticationManager().authenticate(authorizationCodeAuthentication); + this.authorizedClientService.saveAuthorizedClient( + oauth2Authentication.getAuthorizedClient(), oauth2Authentication); + this.accessTokenRepository.saveToken( oauth2Authentication.getAuthorizedClient().getAccessToken(), oauth2Authentication.getAuthorizedClient().getClientRegistration(), @@ -153,6 +159,11 @@ public class OAuth2LoginAuthenticationFilter extends AbstractAuthenticationProce this.clientRegistrationRepository = clientRegistrationRepository; } + public final void setAuthorizedClientService(OAuth2AuthorizedClientService authorizedClientService) { + Assert.notNull(authorizedClientService, "authorizedClientService cannot be null"); + this.authorizedClientService = authorizedClientService; + } + public final void setAuthorizationRequestRepository(AuthorizationRequestRepository authorizationRequestRepository) { Assert.notNull(authorizationRequestRepository, "authorizationRequestRepository cannot be null"); this.authorizationRequestRepository = authorizationRequestRepository; diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2LoginAuthenticationFilterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2LoginAuthenticationFilterTests.java index 07b10d2b79..ba638ccf4d 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2LoginAuthenticationFilterTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2LoginAuthenticationFilterTests.java @@ -28,6 +28,7 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; @@ -184,6 +185,7 @@ public class OAuth2LoginAuthenticationFilterTests { OAuth2LoginAuthenticationFilter filter = new OAuth2LoginAuthenticationFilter(); filter.setClientRegistrationRepository(clientRegistrationRepository); filter.setAuthenticationManager(authenticationManager); + filter.setAuthorizedClientService(mock(OAuth2AuthorizedClientService.class)); return filter; }