Преглед на файлове

Add XML namespace support for oauth2-client

Fixes gh-5184
Joe Grandja преди 5 години
родител
ревизия
8a4ff4452b
променени са 12 файла, в които са добавени 810 реда и са изтрити 5 реда
  1. 1 0
      config/src/main/java/org/springframework/security/config/Elements.java
  2. 63 3
      config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java
  3. 172 0
      config/src/main/java/org/springframework/security/config/http/OAuth2ClientBeanDefinitionParser.java
  4. 1 0
      config/src/main/java/org/springframework/security/config/http/SecurityFilters.java
  5. 28 2
      config/src/main/resources/org/springframework/security/config/spring-security-5.3.rnc
  6. 63 0
      config/src/main/resources/org/springframework/security/config/spring-security-5.3.xsd
  7. 219 0
      config/src/test/java/org/springframework/security/config/http/OAuth2ClientBeanDefinitionParserTests.java
  8. 60 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ClientBeanDefinitionParserTests-CustomAuthorizedClientService.xml
  9. 36 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ClientBeanDefinitionParserTests-CustomClientRegistrationRepository.xml
  10. 60 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ClientBeanDefinitionParserTests-CustomConfiguration.xml
  11. 41 0
      config/src/test/resources/org/springframework/security/config/http/OAuth2ClientBeanDefinitionParserTests-Minimal.xml
  12. 66 0
      docs/manual/src/docs/asciidoc/_includes/servlet/appendix/namespace.adoc

+ 1 - 0
config/src/main/java/org/springframework/security/config/Elements.java

@@ -74,5 +74,6 @@ public abstract class Elements {
 	public static final String INTERCEPT_MESSAGE = "intercept-message";
 
 	public static final String OAUTH2_LOGIN = "oauth2-login";
+	public static final String OAUTH2_CLIENT = "oauth2-client";
 	public static final String CLIENT_REGISTRATIONS = "client-registrations";
 }

+ 63 - 3
config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java

@@ -15,8 +15,6 @@
  */
 package org.springframework.security.config.http;
 
-import static org.springframework.security.config.http.SecurityFilters.*;
-
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.springframework.beans.BeanMetadataElement;
@@ -59,9 +57,27 @@ import org.w3c.dom.Element;
 
 import javax.servlet.http.HttpServletRequest;
 import java.security.SecureRandom;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
 import java.util.function.Function;
 
+import static org.springframework.security.config.http.SecurityFilters.ANONYMOUS_FILTER;
+import static org.springframework.security.config.http.SecurityFilters.BASIC_AUTH_FILTER;
+import static org.springframework.security.config.http.SecurityFilters.EXCEPTION_TRANSLATION_FILTER;
+import static org.springframework.security.config.http.SecurityFilters.FORM_LOGIN_FILTER;
+import static org.springframework.security.config.http.SecurityFilters.LOGIN_PAGE_FILTER;
+import static org.springframework.security.config.http.SecurityFilters.LOGOUT_FILTER;
+import static org.springframework.security.config.http.SecurityFilters.LOGOUT_PAGE_FILTER;
+import static org.springframework.security.config.http.SecurityFilters.OAUTH2_AUTHORIZATION_CODE_GRANT_FILTER;
+import static org.springframework.security.config.http.SecurityFilters.OAUTH2_AUTHORIZATION_REQUEST_FILTER;
+import static org.springframework.security.config.http.SecurityFilters.OAUTH2_LOGIN_FILTER;
+import static org.springframework.security.config.http.SecurityFilters.OPENID_FILTER;
+import static org.springframework.security.config.http.SecurityFilters.PRE_AUTH_FILTER;
+import static org.springframework.security.config.http.SecurityFilters.REMEMBER_ME_FILTER;
+import static org.springframework.security.config.http.SecurityFilters.X509_FILTER;
+
 /**
  * Handles creation of authentication mechanism filters and related beans for <http>
  * parsing.
@@ -145,6 +161,10 @@ final class AuthenticationConfigBuilder {
 	private BeanReference oauth2LoginOidcAuthenticationProviderRef;
 	private BeanDefinition oauth2LoginLinks;
 
+	private BeanDefinition authorizationRequestRedirectFilter;
+	private BeanDefinition authorizationCodeGrantFilter;
+	private BeanReference authorizationCodeAuthenticationProviderRef;
+
 	AuthenticationConfigBuilder(Element element, boolean forceAutoConfig,
 			ParserContext pc, SessionCreationPolicy sessionPolicy,
 			BeanReference requestCache, BeanReference authenticationManager,
@@ -166,6 +186,7 @@ final class AuthenticationConfigBuilder {
 		createBasicFilter(authenticationManager);
 		createFormLoginFilter(sessionStrategy, authenticationManager);
 		createOAuth2LoginFilter(sessionStrategy, authenticationManager);
+		createOAuth2ClientFilter(requestCache, authenticationManager);
 		createOpenIDLoginFilter(sessionStrategy, authenticationManager);
 		createX509Filter(authenticationManager);
 		createJeeFilter(authenticationManager);
@@ -283,6 +304,36 @@ final class AuthenticationConfigBuilder {
 		oauth2LoginOidcAuthenticationProviderRef = new RuntimeBeanReference(oauth2LoginOidcAuthProviderId);
 	}
 
+	void createOAuth2ClientFilter(BeanReference requestCache, BeanReference authenticationManager) {
+		Element oauth2ClientElt = DomUtils.getChildElementByTagName(this.httpElt, Elements.OAUTH2_CLIENT);
+		if (oauth2ClientElt == null) {
+			return;
+		}
+
+		OAuth2ClientBeanDefinitionParser parser = new OAuth2ClientBeanDefinitionParser(
+				requestCache, authenticationManager);
+		parser.parse(oauth2ClientElt, this.pc);
+
+		this.authorizationRequestRedirectFilter = parser.getAuthorizationRequestRedirectFilter();
+		String authorizationRequestRedirectFilterId = pc.getReaderContext()
+				.generateBeanName(this.authorizationRequestRedirectFilter);
+		this.pc.registerBeanComponent(new BeanComponentDefinition(
+				this.authorizationRequestRedirectFilter, authorizationRequestRedirectFilterId));
+
+		this.authorizationCodeGrantFilter = parser.getAuthorizationCodeGrantFilter();
+		String authorizationCodeGrantFilterId = pc.getReaderContext()
+				.generateBeanName(this.authorizationCodeGrantFilter);
+		this.pc.registerBeanComponent(new BeanComponentDefinition(
+				this.authorizationCodeGrantFilter, authorizationCodeGrantFilterId));
+
+		BeanDefinition authorizationCodeAuthenticationProvider = parser.getAuthorizationCodeAuthenticationProvider();
+		String authorizationCodeAuthenticationProviderId = pc.getReaderContext()
+				.generateBeanName(authorizationCodeAuthenticationProvider);
+		this.pc.registerBeanComponent(new BeanComponentDefinition(
+				authorizationCodeAuthenticationProvider, authorizationCodeAuthenticationProviderId));
+		this.authorizationCodeAuthenticationProviderRef = new RuntimeBeanReference(authorizationCodeAuthenticationProviderId);
+	}
+
 	void createOpenIDLoginFilter(BeanReference sessionStrategy, BeanReference authManager) {
 		Element openIDLoginElt = DomUtils.getChildElementByTagName(httpElt,
 				Elements.OPENID_LOGIN);
@@ -884,6 +935,11 @@ final class AuthenticationConfigBuilder {
 			filters.add(new OrderDecorator(basicFilter, BASIC_AUTH_FILTER));
 		}
 
+		if (authorizationCodeGrantFilter != null) {
+			filters.add(new OrderDecorator(authorizationRequestRedirectFilter, OAUTH2_AUTHORIZATION_REQUEST_FILTER));
+			filters.add(new OrderDecorator(authorizationCodeGrantFilter, OAUTH2_AUTHORIZATION_CODE_GRANT_FILTER));
+		}
+
 		filters.add(new OrderDecorator(etf, EXCEPTION_TRANSLATION_FILTER));
 
 		return filters;
@@ -920,6 +976,10 @@ final class AuthenticationConfigBuilder {
 			providers.add(oauth2LoginOidcAuthenticationProviderRef);
 		}
 
+		if (authorizationCodeAuthenticationProviderRef != null) {
+			providers.add(authorizationCodeAuthenticationProviderRef);
+		}
+
 		return providers;
 	}
 

+ 172 - 0
config/src/main/java/org/springframework/security/config/http/OAuth2ClientBeanDefinitionParser.java

@@ -0,0 +1,172 @@
+/*
+ * Copyright 2002-2020 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
+ *
+ *      https://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.config.http;
+
+import org.springframework.beans.BeanMetadataElement;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.BeanReference;
+import org.springframework.beans.factory.config.RuntimeBeanReference;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.xml.BeanDefinitionParser;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationProvider;
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
+import org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter;
+import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
+import org.springframework.util.StringUtils;
+import org.springframework.util.xml.DomUtils;
+import org.w3c.dom.Element;
+
+/**
+ * @author Joe Grandja
+ * @since 5.3
+ */
+final class OAuth2ClientBeanDefinitionParser implements BeanDefinitionParser {
+	private static final String ELT_AUTHORIZATION_CODE_GRANT = "authorization-code-grant";
+	private static final String ATT_CLIENT_REGISTRATION_REPOSITORY_REF = "client-registration-repository-ref";
+	private static final String ATT_AUTHORIZED_CLIENT_REPOSITORY_REF = "authorized-client-repository-ref";
+	private static final String ATT_AUTHORIZED_CLIENT_SERVICE_REF = "authorized-client-service-ref";
+	private static final String ATT_AUTHORIZATION_REQUEST_REPOSITORY_REF = "authorization-request-repository-ref";
+	private static final String ATT_AUTHORIZATION_REQUEST_RESOLVER_REF = "authorization-request-resolver-ref";
+	private static final String ATT_ACCESS_TOKEN_RESPONSE_CLIENT_REF = "access-token-response-client-ref";
+	private final BeanReference requestCache;
+	private final BeanReference authenticationManager;
+	private BeanDefinition authorizationRequestRedirectFilter;
+	private BeanDefinition authorizationCodeGrantFilter;
+	private BeanDefinition authorizationCodeAuthenticationProvider;
+
+	OAuth2ClientBeanDefinitionParser(BeanReference requestCache, BeanReference authenticationManager) {
+		this.requestCache = requestCache;
+		this.authenticationManager = authenticationManager;
+	}
+
+	@Override
+	public BeanDefinition parse(Element element, ParserContext parserContext) {
+		Element authorizationCodeGrantElt = DomUtils.getChildElementByTagName(element, ELT_AUTHORIZATION_CODE_GRANT);
+
+		BeanMetadataElement clientRegistrationRepository = getClientRegistrationRepository(element);
+		BeanMetadataElement authorizedClientRepository = getAuthorizedClientRepository(
+				element, clientRegistrationRepository);
+		BeanMetadataElement authorizationRequestRepository = getAuthorizationRequestRepository(
+				authorizationCodeGrantElt);
+
+		BeanDefinitionBuilder authorizationRequestRedirectFilterBuilder = BeanDefinitionBuilder
+				.rootBeanDefinition(OAuth2AuthorizationRequestRedirectFilter.class);
+		String authorizationRequestResolverRef = authorizationCodeGrantElt != null ?
+				authorizationCodeGrantElt.getAttribute(ATT_AUTHORIZATION_REQUEST_RESOLVER_REF) : null;
+		if (!StringUtils.isEmpty(authorizationRequestResolverRef)) {
+			authorizationRequestRedirectFilterBuilder.addConstructorArgReference(authorizationRequestResolverRef);
+		} else {
+			authorizationRequestRedirectFilterBuilder.addConstructorArgValue(clientRegistrationRepository);
+		}
+		this.authorizationRequestRedirectFilter = authorizationRequestRedirectFilterBuilder
+				.addPropertyValue("authorizationRequestRepository", authorizationRequestRepository)
+				.addPropertyValue("requestCache", this.requestCache)
+				.getBeanDefinition();
+
+		this.authorizationCodeGrantFilter = BeanDefinitionBuilder
+				.rootBeanDefinition(OAuth2AuthorizationCodeGrantFilter.class)
+				.addConstructorArgValue(clientRegistrationRepository)
+				.addConstructorArgValue(authorizedClientRepository)
+				.addConstructorArgValue(this.authenticationManager)
+				.addPropertyValue("authorizationRequestRepository", authorizationRequestRepository)
+				.getBeanDefinition();
+
+		BeanMetadataElement accessTokenResponseClient = getAccessTokenResponseClient(authorizationCodeGrantElt);
+
+		this.authorizationCodeAuthenticationProvider = BeanDefinitionBuilder
+				.rootBeanDefinition(OAuth2AuthorizationCodeAuthenticationProvider.class)
+				.addConstructorArgValue(accessTokenResponseClient)
+				.getBeanDefinition();
+
+		return null;
+	}
+
+	private BeanMetadataElement getClientRegistrationRepository(Element element) {
+		BeanMetadataElement clientRegistrationRepository;
+		String clientRegistrationRepositoryRef = element.getAttribute(ATT_CLIENT_REGISTRATION_REPOSITORY_REF);
+		if (!StringUtils.isEmpty(clientRegistrationRepositoryRef)) {
+			clientRegistrationRepository = new RuntimeBeanReference(clientRegistrationRepositoryRef);
+		} else {
+			clientRegistrationRepository = new RuntimeBeanReference(ClientRegistrationRepository.class);
+		}
+		return clientRegistrationRepository;
+	}
+
+	private BeanMetadataElement getAuthorizedClientRepository(Element element,
+			BeanMetadataElement clientRegistrationRepository) {
+		BeanMetadataElement authorizedClientRepository;
+		String authorizedClientRepositoryRef = element.getAttribute(ATT_AUTHORIZED_CLIENT_REPOSITORY_REF);
+		if (!StringUtils.isEmpty(authorizedClientRepositoryRef)) {
+			authorizedClientRepository = new RuntimeBeanReference(authorizedClientRepositoryRef);
+		} else {
+			BeanMetadataElement authorizedClientService;
+			String authorizedClientServiceRef = element.getAttribute(ATT_AUTHORIZED_CLIENT_SERVICE_REF);
+			if (!StringUtils.isEmpty(authorizedClientServiceRef)) {
+				authorizedClientService = new RuntimeBeanReference(authorizedClientServiceRef);
+			} else {
+				authorizedClientService = BeanDefinitionBuilder
+						.rootBeanDefinition(
+								"org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService")
+						.addConstructorArgValue(clientRegistrationRepository).getBeanDefinition();
+			}
+			authorizedClientRepository = BeanDefinitionBuilder.rootBeanDefinition(
+					"org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository")
+					.addConstructorArgValue(authorizedClientService).getBeanDefinition();
+		}
+		return authorizedClientRepository;
+	}
+
+	private BeanMetadataElement getAuthorizationRequestRepository(Element element) {
+		BeanMetadataElement authorizationRequestRepository;
+		String authorizationRequestRepositoryRef = element != null ?
+				element.getAttribute(ATT_AUTHORIZATION_REQUEST_REPOSITORY_REF) : null;
+		if (!StringUtils.isEmpty(authorizationRequestRepositoryRef)) {
+			authorizationRequestRepository = new RuntimeBeanReference(authorizationRequestRepositoryRef);
+		} else {
+			authorizationRequestRepository = BeanDefinitionBuilder.rootBeanDefinition(
+					"org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository")
+					.getBeanDefinition();
+		}
+		return authorizationRequestRepository;
+	}
+
+	private BeanMetadataElement getAccessTokenResponseClient(Element element) {
+		BeanMetadataElement accessTokenResponseClient;
+		String accessTokenResponseClientRef = element != null ?
+				element.getAttribute(ATT_ACCESS_TOKEN_RESPONSE_CLIENT_REF) : null;
+		if (!StringUtils.isEmpty(accessTokenResponseClientRef)) {
+			accessTokenResponseClient = new RuntimeBeanReference(accessTokenResponseClientRef);
+		} else {
+			accessTokenResponseClient = BeanDefinitionBuilder.rootBeanDefinition(
+					"org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient")
+					.getBeanDefinition();
+		}
+		return accessTokenResponseClient;
+	}
+
+	BeanDefinition getAuthorizationRequestRedirectFilter() {
+		return this.authorizationRequestRedirectFilter;
+	}
+
+	BeanDefinition getAuthorizationCodeGrantFilter() {
+		return this.authorizationCodeGrantFilter;
+	}
+
+	BeanDefinition getAuthorizationCodeAuthenticationProvider() {
+		return this.authorizationCodeAuthenticationProvider;
+	}
+}

+ 1 - 0
config/src/main/java/org/springframework/security/config/http/SecurityFilters.java

@@ -51,6 +51,7 @@ enum SecurityFilters {
 	JAAS_API_SUPPORT_FILTER,
 	REMEMBER_ME_FILTER,
 	ANONYMOUS_FILTER,
+	OAUTH2_AUTHORIZATION_CODE_GRANT_FILTER,
 	SESSION_MANAGEMENT_FILTER,
 	EXCEPTION_TRANSLATION_FILTER,
 	FILTER_SECURITY_INTERCEPTOR,

+ 28 - 2
config/src/main/resources/org/springframework/security/config/spring-security-5.3.rnc

@@ -296,7 +296,7 @@ http-firewall =
 
 http =
 	## Container element for HTTP security configuration. Multiple elements can now be defined, each with a specific pattern to which the enclosed security configuration applies. A pattern can also be configured to bypass Spring Security's filters completely by setting the "security" attribute to "none".
-	element http {http.attlist, (intercept-url* & access-denied-handler? & form-login? & oauth2-login? & openid-login? & x509? & jee? & http-basic? & logout? & session-management & remember-me? & anonymous? & port-mappings & custom-filter* & request-cache? & expression-handler? & headers? & csrf? & cors?) }
+	element http {http.attlist, (intercept-url* & access-denied-handler? & form-login? & oauth2-login? & oauth2-client? & openid-login? & x509? & jee? & http-basic? & logout? & session-management & remember-me? & anonymous? & port-mappings & custom-filter* & request-cache? & expression-handler? & headers? & csrf? & cors?) }
 http.attlist &=
 	## The request URL pattern which will be mapped to the filter chain created by this <http> element. If omitted, the filter chain will match all requests.
 	attribute pattern {xsd:token}?
@@ -483,6 +483,32 @@ oauth2-login.attlist &=
 	## Reference to the JwtDecoderFactory used by OidcAuthorizationCodeAuthenticationProvider
 	attribute jwt-decoder-factory-ref {xsd:token}?
 
+oauth2-client =
+	## Configures OAuth 2.0 Client support.
+	element oauth2-client {oauth2-client.attlist, (authorization-code-grant?) }
+oauth2-client.attlist &=
+	## Reference to the ClientRegistrationRepository
+	attribute client-registration-repository-ref {xsd:token}?
+oauth2-client.attlist &=
+	## Reference to the OAuth2AuthorizedClientRepository
+	attribute authorized-client-repository-ref {xsd:token}?
+oauth2-client.attlist &=
+	## Reference to the OAuth2AuthorizedClientService
+	attribute authorized-client-service-ref {xsd:token}?
+
+authorization-code-grant =
+	## Configures OAuth 2.0 Authorization Code Grant.
+	element authorization-code-grant {authorization-code-grant.attlist, empty}
+authorization-code-grant.attlist &=
+	## Reference to the AuthorizationRequestRepository
+	attribute authorization-request-repository-ref {xsd:token}?
+authorization-code-grant.attlist &=
+	## Reference to the OAuth2AuthorizationRequestResolver
+	attribute authorization-request-resolver-ref {xsd:token}?
+authorization-code-grant.attlist &=
+	## Reference to the OAuth2AccessTokenResponseClient
+	attribute access-token-response-client-ref {xsd:token}?
+
 client-registrations =
 	## Container element for client(s) registered with an OAuth 2.0 or OpenID Connect 1.0 Provider.
 	element client-registrations {client-registration+, provider*}
@@ -1024,4 +1050,4 @@ position =
 	## The explicit position at which the custom-filter should be placed in the chain. Use if you are replacing a standard filter.
 	attribute position {named-security-filter}
 
-named-security-filter = "FIRST" | "CHANNEL_FILTER" | "SECURITY_CONTEXT_FILTER" | "CONCURRENT_SESSION_FILTER" | "WEB_ASYNC_MANAGER_FILTER" | "HEADERS_FILTER" | "CORS_FILTER" | "CSRF_FILTER" | "LOGOUT_FILTER" | "OAUTH2_AUTHORIZATION_REQUEST_FILTER" | "X509_FILTER" | "PRE_AUTH_FILTER" | "CAS_FILTER" | "OAUTH2_LOGIN_FILTER" | "FORM_LOGIN_FILTER" | "OPENID_FILTER" | "LOGIN_PAGE_FILTER" |"LOGOUT_PAGE_FILTER" | "DIGEST_AUTH_FILTER" | "BEARER_TOKEN_AUTH_FILTER" | "BASIC_AUTH_FILTER" | "REQUEST_CACHE_FILTER" | "SERVLET_API_SUPPORT_FILTER" | "JAAS_API_SUPPORT_FILTER" | "REMEMBER_ME_FILTER" | "ANONYMOUS_FILTER" | "SESSION_MANAGEMENT_FILTER" | "EXCEPTION_TRANSLATION_FILTER" | "FILTER_SECURITY_INTERCEPTOR" | "SWITCH_USER_FILTER" | "LAST"
+named-security-filter = "FIRST" | "CHANNEL_FILTER" | "SECURITY_CONTEXT_FILTER" | "CONCURRENT_SESSION_FILTER" | "WEB_ASYNC_MANAGER_FILTER" | "HEADERS_FILTER" | "CORS_FILTER" | "CSRF_FILTER" | "LOGOUT_FILTER" | "OAUTH2_AUTHORIZATION_REQUEST_FILTER" | "X509_FILTER" | "PRE_AUTH_FILTER" | "CAS_FILTER" | "OAUTH2_LOGIN_FILTER" | "FORM_LOGIN_FILTER" | "OPENID_FILTER" | "LOGIN_PAGE_FILTER" |"LOGOUT_PAGE_FILTER" | "DIGEST_AUTH_FILTER" | "BEARER_TOKEN_AUTH_FILTER" | "BASIC_AUTH_FILTER" | "REQUEST_CACHE_FILTER" | "SERVLET_API_SUPPORT_FILTER" | "JAAS_API_SUPPORT_FILTER" | "REMEMBER_ME_FILTER" | "ANONYMOUS_FILTER" | "OAUTH2_AUTHORIZATION_CODE_GRANT_FILTER" | "SESSION_MANAGEMENT_FILTER" | "EXCEPTION_TRANSLATION_FILTER" | "FILTER_SECURITY_INTERCEPTOR" | "SWITCH_USER_FILTER" | "LAST"

+ 63 - 0
config/src/main/resources/org/springframework/security/config/spring-security-5.3.xsd

@@ -956,6 +956,7 @@
                </xs:complexType>
             </xs:element>
             <xs:element ref="security:oauth2-login"/>
+            <xs:element ref="security:oauth2-client"/>
             <xs:element name="openid-login">
                <xs:annotation>
                   <xs:documentation>Sets up form login for authentication with an Open ID identity
@@ -1546,6 +1547,67 @@
          </xs:annotation>
       </xs:attribute>
   </xs:attributeGroup>
+  <xs:element name="oauth2-client">
+      <xs:annotation>
+         <xs:documentation>Configures OAuth 2.0 Client support.
+                </xs:documentation>
+      </xs:annotation>
+      <xs:complexType>
+         <xs:sequence>
+            <xs:element minOccurs="0" ref="security:authorization-code-grant"/>
+         </xs:sequence>
+         <xs:attributeGroup ref="security:oauth2-client.attlist"/>
+      </xs:complexType>
+   </xs:element>
+  <xs:attributeGroup name="oauth2-client.attlist">
+      <xs:attribute name="client-registration-repository-ref" type="xs:token">
+         <xs:annotation>
+            <xs:documentation>Reference to the ClientRegistrationRepository
+                </xs:documentation>
+         </xs:annotation>
+      </xs:attribute>
+      <xs:attribute name="authorized-client-repository-ref" type="xs:token">
+         <xs:annotation>
+            <xs:documentation>Reference to the OAuth2AuthorizedClientRepository
+                </xs:documentation>
+         </xs:annotation>
+      </xs:attribute>
+      <xs:attribute name="authorized-client-service-ref" type="xs:token">
+         <xs:annotation>
+            <xs:documentation>Reference to the OAuth2AuthorizedClientService
+                </xs:documentation>
+         </xs:annotation>
+      </xs:attribute>
+  </xs:attributeGroup>
+  <xs:element name="authorization-code-grant">
+      <xs:annotation>
+         <xs:documentation>Configures OAuth 2.0 Authorization Code Grant.
+                </xs:documentation>
+      </xs:annotation>
+      <xs:complexType>
+         <xs:attributeGroup ref="security:authorization-code-grant.attlist"/>
+      </xs:complexType>
+   </xs:element>
+  <xs:attributeGroup name="authorization-code-grant.attlist">
+      <xs:attribute name="authorization-request-repository-ref" type="xs:token">
+         <xs:annotation>
+            <xs:documentation>Reference to the AuthorizationRequestRepository
+                </xs:documentation>
+         </xs:annotation>
+      </xs:attribute>
+      <xs:attribute name="authorization-request-resolver-ref" type="xs:token">
+         <xs:annotation>
+            <xs:documentation>Reference to the OAuth2AuthorizationRequestResolver
+                </xs:documentation>
+         </xs:annotation>
+      </xs:attribute>
+      <xs:attribute name="access-token-response-client-ref" type="xs:token">
+         <xs:annotation>
+            <xs:documentation>Reference to the OAuth2AccessTokenResponseClient
+                </xs:documentation>
+         </xs:annotation>
+      </xs:attribute>
+  </xs:attributeGroup>
   <xs:element name="client-registrations">
       <xs:annotation>
          <xs:documentation>Container element for client(s) registered with an OAuth 2.0 or OpenID Connect 1.0
@@ -3006,6 +3068,7 @@
          <xs:enumeration value="JAAS_API_SUPPORT_FILTER"/>
          <xs:enumeration value="REMEMBER_ME_FILTER"/>
          <xs:enumeration value="ANONYMOUS_FILTER"/>
+         <xs:enumeration value="OAUTH2_AUTHORIZATION_CODE_GRANT_FILTER"/>
          <xs:enumeration value="SESSION_MANAGEMENT_FILTER"/>
          <xs:enumeration value="EXCEPTION_TRANSLATION_FILTER"/>
          <xs:enumeration value="FILTER_SECURITY_INTERCEPTOR"/>

+ 219 - 0
config/src/test/java/org/springframework/security/config/http/OAuth2ClientBeanDefinitionParserTests.java

@@ -0,0 +1,219 @@
+/*
+ * Copyright 2002-2020 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
+ *
+ *      https://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.config.http;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.config.oauth2.client.CommonOAuth2Provider;
+import org.springframework.security.config.test.SpringTestRule;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
+import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
+import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
+import org.springframework.security.oauth2.client.registration.ClientRegistration;
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
+import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
+import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
+import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses.accessTokenResponse;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+/**
+ * Tests for {@link OAuth2ClientBeanDefinitionParser}.
+ *
+ * @author Joe Grandja
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@SecurityTestExecutionListeners
+public class OAuth2ClientBeanDefinitionParserTests {
+	private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/OAuth2ClientBeanDefinitionParserTests";
+
+	@Rule
+	public final SpringTestRule spring = new SpringTestRule();
+
+	@Autowired
+	private ClientRegistrationRepository clientRegistrationRepository;
+
+	@Autowired(required = false)
+	private OAuth2AuthorizedClientRepository authorizedClientRepository;
+
+	@Autowired(required = false)
+	private OAuth2AuthorizedClientService authorizedClientService;
+
+	@Autowired(required = false)
+	private AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository;
+
+	@Autowired(required = false)
+	private OAuth2AuthorizationRequestResolver authorizationRequestResolver;
+
+	@Autowired(required = false)
+	private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
+
+	@Autowired
+	private MockMvc mvc;
+
+	@Test
+	public void requestWhenAuthorizeThenRedirect() throws Exception {
+		this.spring.configLocations(xml("Minimal")).autowire();
+
+		MvcResult result = this.mvc.perform(get("/oauth2/authorization/google"))
+				.andExpect(status().is3xxRedirection())
+				.andReturn();
+		assertThat(result.getResponse().getRedirectedUrl()).matches(
+				"https://accounts.google.com/o/oauth2/v2/auth\\?" +
+						"response_type=code&client_id=google-client-id&" +
+						"scope=scope1%20scope2&state=.{15,}&redirect_uri=http://localhost/callback/google");
+	}
+
+	@Test
+	public void requestWhenCustomClientRegistrationRepositoryThenCalled() throws Exception {
+		this.spring.configLocations(xml("CustomClientRegistrationRepository")).autowire();
+
+		ClientRegistration clientRegistration = CommonOAuth2Provider.GOOGLE.getBuilder("google")
+				.clientId("google-client-id")
+				.clientSecret("google-client-secret")
+				.redirectUriTemplate("http://localhost/callback/google")
+				.scope("scope1", "scope2")
+				.build();
+		when(this.clientRegistrationRepository.findByRegistrationId(any())).thenReturn(clientRegistration);
+
+		MvcResult result = this.mvc.perform(get("/oauth2/authorization/google"))
+				.andExpect(status().is3xxRedirection())
+				.andReturn();
+		assertThat(result.getResponse().getRedirectedUrl()).matches(
+				"https://accounts.google.com/o/oauth2/v2/auth\\?" +
+						"response_type=code&client_id=google-client-id&" +
+						"scope=scope1%20scope2&state=.{15,}&redirect_uri=http://localhost/callback/google");
+
+		verify(this.clientRegistrationRepository).findByRegistrationId(any());
+	}
+
+	@Test
+	public void requestWhenCustomAuthorizationRequestResolverThenCalled() throws Exception {
+		this.spring.configLocations(xml("CustomConfiguration")).autowire();
+
+		ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google");
+
+		OAuth2AuthorizationRequest authorizationRequest = createAuthorizationRequest(clientRegistration);
+		when(this.authorizationRequestResolver.resolve(any())).thenReturn(authorizationRequest);
+
+		this.mvc.perform(get("/oauth2/authorization/google"))
+				.andExpect(status().is3xxRedirection())
+				.andExpect(redirectedUrl(
+						"https://accounts.google.com/o/oauth2/v2/auth?" +
+								"response_type=code&client_id=google-client-id&" +
+								"scope=scope1%20scope2&state=state&redirect_uri=http://localhost/callback/google"));
+
+		verify(this.authorizationRequestResolver).resolve(any());
+	}
+
+	@Test
+	public void requestWhenAuthorizationResponseMatchThenProcess() throws Exception {
+		this.spring.configLocations(xml("CustomConfiguration")).autowire();
+
+		ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google");
+
+		OAuth2AuthorizationRequest authorizationRequest = createAuthorizationRequest(clientRegistration);
+		when(this.authorizationRequestRepository.loadAuthorizationRequest(any()))
+				.thenReturn(authorizationRequest);
+		when(this.authorizationRequestRepository.removeAuthorizationRequest(any(), any()))
+				.thenReturn(authorizationRequest);
+
+		OAuth2AccessTokenResponse accessTokenResponse = accessTokenResponse().build();
+		when(this.accessTokenResponseClient.getTokenResponse(any())).thenReturn(accessTokenResponse);
+
+		MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
+		params.add("code", "code123");
+		params.add("state", authorizationRequest.getState());
+		this.mvc.perform(get(authorizationRequest.getRedirectUri()).params(params))
+				.andExpect(status().is3xxRedirection())
+				.andExpect(redirectedUrl(authorizationRequest.getRedirectUri()));
+
+		ArgumentCaptor<OAuth2AuthorizedClient> authorizedClientCaptor =
+				ArgumentCaptor.forClass(OAuth2AuthorizedClient.class);
+		verify(this.authorizedClientRepository).saveAuthorizedClient(
+				authorizedClientCaptor.capture(), any(), any(), any());
+		OAuth2AuthorizedClient authorizedClient = authorizedClientCaptor.getValue();
+		assertThat(authorizedClient.getClientRegistration()).isEqualTo(clientRegistration);
+		assertThat(authorizedClient.getAccessToken()).isEqualTo(accessTokenResponse.getAccessToken());
+	}
+
+	@WithMockUser
+	@Test
+	public void requestWhenCustomAuthorizedClientServiceThenCalled() throws Exception {
+		this.spring.configLocations(xml("CustomAuthorizedClientService")).autowire();
+
+		ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google");
+
+		OAuth2AuthorizationRequest authorizationRequest = createAuthorizationRequest(clientRegistration);
+		when(this.authorizationRequestRepository.loadAuthorizationRequest(any()))
+				.thenReturn(authorizationRequest);
+		when(this.authorizationRequestRepository.removeAuthorizationRequest(any(), any()))
+				.thenReturn(authorizationRequest);
+
+		OAuth2AccessTokenResponse accessTokenResponse = accessTokenResponse().build();
+		when(this.accessTokenResponseClient.getTokenResponse(any())).thenReturn(accessTokenResponse);
+
+		MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
+		params.add("code", "code123");
+		params.add("state", authorizationRequest.getState());
+		this.mvc.perform(get(authorizationRequest.getRedirectUri()).params(params))
+				.andExpect(status().is3xxRedirection())
+				.andExpect(redirectedUrl(authorizationRequest.getRedirectUri()));
+
+		verify(this.authorizedClientService).saveAuthorizedClient(any(), any());
+	}
+
+	private static OAuth2AuthorizationRequest createAuthorizationRequest(ClientRegistration clientRegistration) {
+		Map<String, Object> attributes = new HashMap<>();
+		attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId());
+		return OAuth2AuthorizationRequest.authorizationCode()
+				.authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri())
+				.clientId(clientRegistration.getClientId())
+				.redirectUri(clientRegistration.getRedirectUriTemplate())
+				.scopes(clientRegistration.getScopes())
+				.state("state")
+				.attributes(attributes)
+				.build();
+	}
+
+	private static String xml(String configName) {
+		return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml";
+	}
+}

+ 60 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ClientBeanDefinitionParserTests-CustomAuthorizedClientService.xml

@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2002-2020 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
+  ~
+  ~       https://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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		xmlns="http://www.springframework.org/schema/security"
+		xsi:schemaLocation="
+			http://www.springframework.org/schema/security
+			https://www.springframework.org/schema/security/spring-security.xsd
+			http://www.springframework.org/schema/beans
+			https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<http auto-config="true">
+		<oauth2-client
+				authorized-client-service-ref="authorizedClientService">
+			<authorization-code-grant
+					authorization-request-repository-ref="authorizationRequestRepository"
+					authorization-request-resolver-ref="authorizationRequestResolver"
+					access-token-response-client-ref="accessTokenResponseClient"/>
+		</oauth2-client>
+	</http>
+
+	<client-registrations>
+		<client-registration registration-id="google"
+							 client-id="google-client-id"
+							 client-secret="google-client-secret"
+							 redirect-uri="http://localhost/callback/google"
+							 scope="scope1,scope2"
+							 provider-id="google"/>
+	</client-registrations>
+
+	<b:bean id="authorizedClientService" class="org.mockito.Mockito" factory-method="mock">
+		<b:constructor-arg value="org.springframework.security.oauth2.client.OAuth2AuthorizedClientService"/>
+	</b:bean>
+	<b:bean id="authorizationRequestRepository" class="org.mockito.Mockito" factory-method="mock">
+		<b:constructor-arg value="org.springframework.security.oauth2.client.web.AuthorizationRequestRepository"/>
+	</b:bean>
+	<b:bean id="authorizationRequestResolver" class="org.mockito.Mockito" factory-method="mock">
+		<b:constructor-arg value="org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver"/>
+	</b:bean>
+	<b:bean id="accessTokenResponseClient" class="org.mockito.Mockito" factory-method="mock">
+		<b:constructor-arg value="org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient"/>
+	</b:bean>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

+ 36 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ClientBeanDefinitionParserTests-CustomClientRegistrationRepository.xml

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2002-2020 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
+  ~
+  ~       https://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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		xmlns="http://www.springframework.org/schema/security"
+		xsi:schemaLocation="
+			http://www.springframework.org/schema/security
+			https://www.springframework.org/schema/security/spring-security.xsd
+			http://www.springframework.org/schema/beans
+			https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<http auto-config="true">
+		<oauth2-client client-registration-repository-ref="clientRegistrationRepository"/>
+	</http>
+
+	<b:bean id="clientRegistrationRepository" class="org.mockito.Mockito" factory-method="mock">
+		<b:constructor-arg value="org.springframework.security.oauth2.client.registration.ClientRegistrationRepository"/>
+	</b:bean>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

+ 60 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ClientBeanDefinitionParserTests-CustomConfiguration.xml

@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2002-2020 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
+  ~
+  ~       https://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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		xmlns="http://www.springframework.org/schema/security"
+		xsi:schemaLocation="
+			http://www.springframework.org/schema/security
+			https://www.springframework.org/schema/security/spring-security.xsd
+			http://www.springframework.org/schema/beans
+			https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<http auto-config="true">
+		<oauth2-client
+				authorized-client-repository-ref="authorizedClientRepository">
+			<authorization-code-grant
+					authorization-request-repository-ref="authorizationRequestRepository"
+					authorization-request-resolver-ref="authorizationRequestResolver"
+					access-token-response-client-ref="accessTokenResponseClient"/>
+		</oauth2-client>
+	</http>
+
+	<client-registrations>
+		<client-registration registration-id="google"
+							 client-id="google-client-id"
+							 client-secret="google-client-secret"
+							 redirect-uri="http://localhost/callback/google"
+							 scope="scope1,scope2"
+							 provider-id="google"/>
+	</client-registrations>
+
+	<b:bean id="authorizedClientRepository" class="org.mockito.Mockito" factory-method="mock">
+		<b:constructor-arg value="org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository"/>
+	</b:bean>
+	<b:bean id="authorizationRequestRepository" class="org.mockito.Mockito" factory-method="mock">
+		<b:constructor-arg value="org.springframework.security.oauth2.client.web.AuthorizationRequestRepository"/>
+	</b:bean>
+	<b:bean id="authorizationRequestResolver" class="org.mockito.Mockito" factory-method="mock">
+		<b:constructor-arg value="org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver"/>
+	</b:bean>
+	<b:bean id="accessTokenResponseClient" class="org.mockito.Mockito" factory-method="mock">
+		<b:constructor-arg value="org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient"/>
+	</b:bean>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

+ 41 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2ClientBeanDefinitionParserTests-Minimal.xml

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2002-2020 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
+  ~
+  ~       https://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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		xmlns="http://www.springframework.org/schema/security"
+		xsi:schemaLocation="
+			http://www.springframework.org/schema/security
+			https://www.springframework.org/schema/security/spring-security.xsd
+			http://www.springframework.org/schema/beans
+			https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<http auto-config="true">
+		<oauth2-client/>
+	</http>
+
+	<client-registrations>
+		<client-registration registration-id="google"
+							 client-id="google-client-id"
+							 client-secret="google-client-secret"
+							 redirect-uri="http://localhost/callback/google"
+							 scope="scope1,scope2"
+							 provider-id="google"/>
+	</client-registrations>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

+ 66 - 0
docs/manual/src/docs/asciidoc/_includes/servlet/appendix/namespace.adoc

@@ -164,6 +164,7 @@ The default value is true.
 * <<nsa-intercept-url,intercept-url>>
 * <<nsa-jee,jee>>
 * <<nsa-logout,logout>>
+* <<nsa-oauth2-client,oauth2-client>>
 * <<nsa-oauth2-login,oauth2-login>>
 * <<nsa-openid-login,openid-login>>
 * <<nsa-port-mappings,port-mappings>>
@@ -965,6 +966,71 @@ Reference to the `AuthenticationFailureHandler`.
 Reference to the `JwtDecoderFactory` used by `OidcAuthorizationCodeAuthenticationProvider`.
 
 
+[[nsa-oauth2-client]]
+==== <oauth2-client>
+Configures <<oauth2client,OAuth 2.0 Client>> support.
+
+
+[[nsa-oauth2-client-parents]]
+===== Parent Elements of <oauth2-client>
+
+* <<nsa-http,http>>
+
+[[nsa-oauth2-client-attributes]]
+===== <oauth2-client> Attributes
+
+
+[[nsa-oauth2-client-client-registration-repository-ref]]
+* **client-registration-repository-ref**
+Reference to the `ClientRegistrationRepository`.
+
+
+[[nsa-oauth2-client-authorized-client-repository-ref]]
+* **authorized-client-repository-ref**
+Reference to the `OAuth2AuthorizedClientRepository`.
+
+
+[[nsa-oauth2-client-authorized-client-service-ref]]
+* **authorized-client-service-ref**
+Reference to the `OAuth2AuthorizedClientService`.
+
+
+[[nsa-oauth2-client-children]]
+===== Child Elements of <oauth2-client>
+
+* <<nsa-authorization-code-grant,authorization-code-grant>>
+
+
+[[nsa-authorization-code-grant]]
+==== <authorization-code-grant>
+Configures <<oauth2Client-auth-grant-support,OAuth 2.0 Authorization Code Grant>>.
+
+
+[[nsa-authorization-code-grant-parents]]
+===== Parent Elements of <authorization-code-grant>
+
+* <<nsa-oauth2-client,oauth2-client>>
+
+
+[[nsa-authorization-code-grant-attributes]]
+===== <authorization-code-grant> Attributes
+
+
+[[nsa-authorization-code-grant-authorization-request-repository-ref]]
+* **authorization-request-repository-ref**
+Reference to the `AuthorizationRequestRepository`.
+
+
+[[nsa-authorization-code-grant-authorization-request-resolver-ref]]
+* **authorization-request-resolver-ref**
+Reference to the `OAuth2AuthorizationRequestResolver`.
+
+
+[[nsa-authorization-code-grant-access-token-response-client-ref]]
+* **access-token-response-client-ref**
+Reference to the `OAuth2AccessTokenResponseClient`.
+
+
 [[nsa-client-registrations]]
 ==== <client-registrations>
 A container element for client(s) registered (<<oauth2Client-client-registration,ClientRegistration>>) with an OAuth 2.0 or OpenID Connect 1.0 Provider.