Bladeren bron

Add OAuth2AuthorizedClientService

Fixes gh-4726
Joe Grandja 7 jaren geleden
bovenliggende
commit
16e69d06b4

+ 31 - 0
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<B extends HttpSecurityBuilder<B>> exten
 		return this;
 	}
 
+	public OAuth2LoginConfigurer<B> authorizedClientService(OAuth2AuthorizedClientService<OAuth2AuthorizedClient> authorizedClientService) {
+		Assert.notNull(authorizedClientService, "authorizedClientService cannot be null");
+		this.getBuilder().setSharedObject(OAuth2AuthorizedClientService.class, authorizedClientService);
+		return this;
+	}
+
 	@Override
 	public OAuth2LoginConfigurer<B> loginPage(String loginPage) {
 		Assert.hasText(loginPage, "loginPage cannot be empty");
@@ -299,6 +309,7 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>> 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<B extends HttpSecurityBuilder<B>> exten
 		return this.getBuilder().getSharedObject(ApplicationContext.class).getBean(ClientRegistrationRepository.class);
 	}
 
+	private OAuth2AuthorizedClientService<OAuth2AuthorizedClient> getAuthorizedClientService() {
+		OAuth2AuthorizedClientService<OAuth2AuthorizedClient> 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<OAuth2AuthorizedClient> getAuthorizedClientServiceBean() {
+		Map<String, OAuth2AuthorizedClientService> 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()) {

+ 85 - 0
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)} <i>in-memory</i>.
+ *
+ * @author Joe Grandja
+ * @since 5.0
+ * @see OAuth2AuthorizedClientService
+ * @see OAuth2AuthorizedClient
+ * @see OidcAuthorizedClient
+ * @see ClientRegistration
+ * @see Authentication
+ *
+ * @param <T> The type of <i>OAuth 2.0 Authorized Client</i>
+ */
+public final class InMemoryOAuth2AuthorizedClientService<T extends OAuth2AuthorizedClient> implements OAuth2AuthorizedClientService<T> {
+	private final Map<String, T> 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());
+	}
+}

+ 0 - 1
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 <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-5.1">Section 5.1 Access Token Response</a>
  */
 public class OAuth2AuthorizedClient {
 	private final ClientRegistration clientRegistration;

+ 47 - 0
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 <i>Resource Owner</i>,
+ * 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 <T> The type of <i>OAuth 2.0 Authorized Client</i>
+ */
+public interface OAuth2AuthorizedClientService<T extends OAuth2AuthorizedClient> {
+
+	T loadAuthorizedClient(String clientRegistrationId, Authentication principal);
+
+	void saveAuthorizedClient(T authorizedClient, Authentication principal);
+
+	T removeAuthorizedClient(String clientRegistrationId, Authentication principal);
+
+}

+ 0 - 1
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 <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#TokenResponse">3.1.3.3 Successful Token Response</a>
  */
 public class OidcAuthorizedClient extends OAuth2AuthorizedClient {
 	private final OidcIdToken idToken;

+ 11 - 0
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<OAuth2AuthorizedClient> authorizedClientService;
 	private AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository =
 		new HttpSessionOAuth2AuthorizationRequestRepository();
 	private OAuth2TokenRepository<OAuth2AccessToken> 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<OAuth2User, OAuth2AuthorizedClient> oauth2Authentication =
 			(OAuth2AuthenticationToken<OAuth2User, OAuth2AuthorizedClient>) 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<OAuth2AuthorizedClient> authorizedClientService) {
+		Assert.notNull(authorizedClientService, "authorizedClientService cannot be null");
+		this.authorizedClientService = authorizedClientService;
+	}
+
 	public final void setAuthorizationRequestRepository(AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository) {
 		Assert.notNull(authorizationRequestRepository, "authorizationRequestRepository cannot be null");
 		this.authorizationRequestRepository = authorizationRequestRepository;

+ 2 - 0
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;
 	}