Skip to content

Commit 8a4ff44

Browse files
committed
Add XML namespace support for oauth2-client
Fixes gh-5184
1 parent c5cb695 commit 8a4ff44

File tree

12 files changed

+810
-5
lines changed

12 files changed

+810
-5
lines changed

config/src/main/java/org/springframework/security/config/Elements.java

+1
Original file line numberDiff line numberDiff line change
@@ -74,5 +74,6 @@ public abstract class Elements {
7474
public static final String INTERCEPT_MESSAGE = "intercept-message";
7575

7676
public static final String OAUTH2_LOGIN = "oauth2-login";
77+
public static final String OAUTH2_CLIENT = "oauth2-client";
7778
public static final String CLIENT_REGISTRATIONS = "client-registrations";
7879
}

config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java

+63-3
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
*/
1616
package org.springframework.security.config.http;
1717

18-
import static org.springframework.security.config.http.SecurityFilters.*;
19-
2018
import org.apache.commons.logging.Log;
2119
import org.apache.commons.logging.LogFactory;
2220
import org.springframework.beans.BeanMetadataElement;
@@ -59,9 +57,27 @@
5957

6058
import javax.servlet.http.HttpServletRequest;
6159
import java.security.SecureRandom;
62-
import java.util.*;
60+
import java.util.ArrayList;
61+
import java.util.Collections;
62+
import java.util.List;
63+
import java.util.Map;
6364
import java.util.function.Function;
6465

66+
import static org.springframework.security.config.http.SecurityFilters.ANONYMOUS_FILTER;
67+
import static org.springframework.security.config.http.SecurityFilters.BASIC_AUTH_FILTER;
68+
import static org.springframework.security.config.http.SecurityFilters.EXCEPTION_TRANSLATION_FILTER;
69+
import static org.springframework.security.config.http.SecurityFilters.FORM_LOGIN_FILTER;
70+
import static org.springframework.security.config.http.SecurityFilters.LOGIN_PAGE_FILTER;
71+
import static org.springframework.security.config.http.SecurityFilters.LOGOUT_FILTER;
72+
import static org.springframework.security.config.http.SecurityFilters.LOGOUT_PAGE_FILTER;
73+
import static org.springframework.security.config.http.SecurityFilters.OAUTH2_AUTHORIZATION_CODE_GRANT_FILTER;
74+
import static org.springframework.security.config.http.SecurityFilters.OAUTH2_AUTHORIZATION_REQUEST_FILTER;
75+
import static org.springframework.security.config.http.SecurityFilters.OAUTH2_LOGIN_FILTER;
76+
import static org.springframework.security.config.http.SecurityFilters.OPENID_FILTER;
77+
import static org.springframework.security.config.http.SecurityFilters.PRE_AUTH_FILTER;
78+
import static org.springframework.security.config.http.SecurityFilters.REMEMBER_ME_FILTER;
79+
import static org.springframework.security.config.http.SecurityFilters.X509_FILTER;
80+
6581
/**
6682
* Handles creation of authentication mechanism filters and related beans for <http>
6783
* parsing.
@@ -145,6 +161,10 @@ final class AuthenticationConfigBuilder {
145161
private BeanReference oauth2LoginOidcAuthenticationProviderRef;
146162
private BeanDefinition oauth2LoginLinks;
147163

164+
private BeanDefinition authorizationRequestRedirectFilter;
165+
private BeanDefinition authorizationCodeGrantFilter;
166+
private BeanReference authorizationCodeAuthenticationProviderRef;
167+
148168
AuthenticationConfigBuilder(Element element, boolean forceAutoConfig,
149169
ParserContext pc, SessionCreationPolicy sessionPolicy,
150170
BeanReference requestCache, BeanReference authenticationManager,
@@ -166,6 +186,7 @@ final class AuthenticationConfigBuilder {
166186
createBasicFilter(authenticationManager);
167187
createFormLoginFilter(sessionStrategy, authenticationManager);
168188
createOAuth2LoginFilter(sessionStrategy, authenticationManager);
189+
createOAuth2ClientFilter(requestCache, authenticationManager);
169190
createOpenIDLoginFilter(sessionStrategy, authenticationManager);
170191
createX509Filter(authenticationManager);
171192
createJeeFilter(authenticationManager);
@@ -283,6 +304,36 @@ void createOAuth2LoginFilter(BeanReference sessionStrategy, BeanReference authMa
283304
oauth2LoginOidcAuthenticationProviderRef = new RuntimeBeanReference(oauth2LoginOidcAuthProviderId);
284305
}
285306

307+
void createOAuth2ClientFilter(BeanReference requestCache, BeanReference authenticationManager) {
308+
Element oauth2ClientElt = DomUtils.getChildElementByTagName(this.httpElt, Elements.OAUTH2_CLIENT);
309+
if (oauth2ClientElt == null) {
310+
return;
311+
}
312+
313+
OAuth2ClientBeanDefinitionParser parser = new OAuth2ClientBeanDefinitionParser(
314+
requestCache, authenticationManager);
315+
parser.parse(oauth2ClientElt, this.pc);
316+
317+
this.authorizationRequestRedirectFilter = parser.getAuthorizationRequestRedirectFilter();
318+
String authorizationRequestRedirectFilterId = pc.getReaderContext()
319+
.generateBeanName(this.authorizationRequestRedirectFilter);
320+
this.pc.registerBeanComponent(new BeanComponentDefinition(
321+
this.authorizationRequestRedirectFilter, authorizationRequestRedirectFilterId));
322+
323+
this.authorizationCodeGrantFilter = parser.getAuthorizationCodeGrantFilter();
324+
String authorizationCodeGrantFilterId = pc.getReaderContext()
325+
.generateBeanName(this.authorizationCodeGrantFilter);
326+
this.pc.registerBeanComponent(new BeanComponentDefinition(
327+
this.authorizationCodeGrantFilter, authorizationCodeGrantFilterId));
328+
329+
BeanDefinition authorizationCodeAuthenticationProvider = parser.getAuthorizationCodeAuthenticationProvider();
330+
String authorizationCodeAuthenticationProviderId = pc.getReaderContext()
331+
.generateBeanName(authorizationCodeAuthenticationProvider);
332+
this.pc.registerBeanComponent(new BeanComponentDefinition(
333+
authorizationCodeAuthenticationProvider, authorizationCodeAuthenticationProviderId));
334+
this.authorizationCodeAuthenticationProviderRef = new RuntimeBeanReference(authorizationCodeAuthenticationProviderId);
335+
}
336+
286337
void createOpenIDLoginFilter(BeanReference sessionStrategy, BeanReference authManager) {
287338
Element openIDLoginElt = DomUtils.getChildElementByTagName(httpElt,
288339
Elements.OPENID_LOGIN);
@@ -884,6 +935,11 @@ List<OrderDecorator> getFilters() {
884935
filters.add(new OrderDecorator(basicFilter, BASIC_AUTH_FILTER));
885936
}
886937

938+
if (authorizationCodeGrantFilter != null) {
939+
filters.add(new OrderDecorator(authorizationRequestRedirectFilter, OAUTH2_AUTHORIZATION_REQUEST_FILTER));
940+
filters.add(new OrderDecorator(authorizationCodeGrantFilter, OAUTH2_AUTHORIZATION_CODE_GRANT_FILTER));
941+
}
942+
887943
filters.add(new OrderDecorator(etf, EXCEPTION_TRANSLATION_FILTER));
888944

889945
return filters;
@@ -920,6 +976,10 @@ List<BeanReference> getProviders() {
920976
providers.add(oauth2LoginOidcAuthenticationProviderRef);
921977
}
922978

979+
if (authorizationCodeAuthenticationProviderRef != null) {
980+
providers.add(authorizationCodeAuthenticationProviderRef);
981+
}
982+
923983
return providers;
924984
}
925985

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
/*
2+
* Copyright 2002-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.security.config.http;
17+
18+
import org.springframework.beans.BeanMetadataElement;
19+
import org.springframework.beans.factory.config.BeanDefinition;
20+
import org.springframework.beans.factory.config.BeanReference;
21+
import org.springframework.beans.factory.config.RuntimeBeanReference;
22+
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
23+
import org.springframework.beans.factory.xml.BeanDefinitionParser;
24+
import org.springframework.beans.factory.xml.ParserContext;
25+
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationProvider;
26+
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
27+
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter;
28+
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
29+
import org.springframework.util.StringUtils;
30+
import org.springframework.util.xml.DomUtils;
31+
import org.w3c.dom.Element;
32+
33+
/**
34+
* @author Joe Grandja
35+
* @since 5.3
36+
*/
37+
final class OAuth2ClientBeanDefinitionParser implements BeanDefinitionParser {
38+
private static final String ELT_AUTHORIZATION_CODE_GRANT = "authorization-code-grant";
39+
private static final String ATT_CLIENT_REGISTRATION_REPOSITORY_REF = "client-registration-repository-ref";
40+
private static final String ATT_AUTHORIZED_CLIENT_REPOSITORY_REF = "authorized-client-repository-ref";
41+
private static final String ATT_AUTHORIZED_CLIENT_SERVICE_REF = "authorized-client-service-ref";
42+
private static final String ATT_AUTHORIZATION_REQUEST_REPOSITORY_REF = "authorization-request-repository-ref";
43+
private static final String ATT_AUTHORIZATION_REQUEST_RESOLVER_REF = "authorization-request-resolver-ref";
44+
private static final String ATT_ACCESS_TOKEN_RESPONSE_CLIENT_REF = "access-token-response-client-ref";
45+
private final BeanReference requestCache;
46+
private final BeanReference authenticationManager;
47+
private BeanDefinition authorizationRequestRedirectFilter;
48+
private BeanDefinition authorizationCodeGrantFilter;
49+
private BeanDefinition authorizationCodeAuthenticationProvider;
50+
51+
OAuth2ClientBeanDefinitionParser(BeanReference requestCache, BeanReference authenticationManager) {
52+
this.requestCache = requestCache;
53+
this.authenticationManager = authenticationManager;
54+
}
55+
56+
@Override
57+
public BeanDefinition parse(Element element, ParserContext parserContext) {
58+
Element authorizationCodeGrantElt = DomUtils.getChildElementByTagName(element, ELT_AUTHORIZATION_CODE_GRANT);
59+
60+
BeanMetadataElement clientRegistrationRepository = getClientRegistrationRepository(element);
61+
BeanMetadataElement authorizedClientRepository = getAuthorizedClientRepository(
62+
element, clientRegistrationRepository);
63+
BeanMetadataElement authorizationRequestRepository = getAuthorizationRequestRepository(
64+
authorizationCodeGrantElt);
65+
66+
BeanDefinitionBuilder authorizationRequestRedirectFilterBuilder = BeanDefinitionBuilder
67+
.rootBeanDefinition(OAuth2AuthorizationRequestRedirectFilter.class);
68+
String authorizationRequestResolverRef = authorizationCodeGrantElt != null ?
69+
authorizationCodeGrantElt.getAttribute(ATT_AUTHORIZATION_REQUEST_RESOLVER_REF) : null;
70+
if (!StringUtils.isEmpty(authorizationRequestResolverRef)) {
71+
authorizationRequestRedirectFilterBuilder.addConstructorArgReference(authorizationRequestResolverRef);
72+
} else {
73+
authorizationRequestRedirectFilterBuilder.addConstructorArgValue(clientRegistrationRepository);
74+
}
75+
this.authorizationRequestRedirectFilter = authorizationRequestRedirectFilterBuilder
76+
.addPropertyValue("authorizationRequestRepository", authorizationRequestRepository)
77+
.addPropertyValue("requestCache", this.requestCache)
78+
.getBeanDefinition();
79+
80+
this.authorizationCodeGrantFilter = BeanDefinitionBuilder
81+
.rootBeanDefinition(OAuth2AuthorizationCodeGrantFilter.class)
82+
.addConstructorArgValue(clientRegistrationRepository)
83+
.addConstructorArgValue(authorizedClientRepository)
84+
.addConstructorArgValue(this.authenticationManager)
85+
.addPropertyValue("authorizationRequestRepository", authorizationRequestRepository)
86+
.getBeanDefinition();
87+
88+
BeanMetadataElement accessTokenResponseClient = getAccessTokenResponseClient(authorizationCodeGrantElt);
89+
90+
this.authorizationCodeAuthenticationProvider = BeanDefinitionBuilder
91+
.rootBeanDefinition(OAuth2AuthorizationCodeAuthenticationProvider.class)
92+
.addConstructorArgValue(accessTokenResponseClient)
93+
.getBeanDefinition();
94+
95+
return null;
96+
}
97+
98+
private BeanMetadataElement getClientRegistrationRepository(Element element) {
99+
BeanMetadataElement clientRegistrationRepository;
100+
String clientRegistrationRepositoryRef = element.getAttribute(ATT_CLIENT_REGISTRATION_REPOSITORY_REF);
101+
if (!StringUtils.isEmpty(clientRegistrationRepositoryRef)) {
102+
clientRegistrationRepository = new RuntimeBeanReference(clientRegistrationRepositoryRef);
103+
} else {
104+
clientRegistrationRepository = new RuntimeBeanReference(ClientRegistrationRepository.class);
105+
}
106+
return clientRegistrationRepository;
107+
}
108+
109+
private BeanMetadataElement getAuthorizedClientRepository(Element element,
110+
BeanMetadataElement clientRegistrationRepository) {
111+
BeanMetadataElement authorizedClientRepository;
112+
String authorizedClientRepositoryRef = element.getAttribute(ATT_AUTHORIZED_CLIENT_REPOSITORY_REF);
113+
if (!StringUtils.isEmpty(authorizedClientRepositoryRef)) {
114+
authorizedClientRepository = new RuntimeBeanReference(authorizedClientRepositoryRef);
115+
} else {
116+
BeanMetadataElement authorizedClientService;
117+
String authorizedClientServiceRef = element.getAttribute(ATT_AUTHORIZED_CLIENT_SERVICE_REF);
118+
if (!StringUtils.isEmpty(authorizedClientServiceRef)) {
119+
authorizedClientService = new RuntimeBeanReference(authorizedClientServiceRef);
120+
} else {
121+
authorizedClientService = BeanDefinitionBuilder
122+
.rootBeanDefinition(
123+
"org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService")
124+
.addConstructorArgValue(clientRegistrationRepository).getBeanDefinition();
125+
}
126+
authorizedClientRepository = BeanDefinitionBuilder.rootBeanDefinition(
127+
"org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository")
128+
.addConstructorArgValue(authorizedClientService).getBeanDefinition();
129+
}
130+
return authorizedClientRepository;
131+
}
132+
133+
private BeanMetadataElement getAuthorizationRequestRepository(Element element) {
134+
BeanMetadataElement authorizationRequestRepository;
135+
String authorizationRequestRepositoryRef = element != null ?
136+
element.getAttribute(ATT_AUTHORIZATION_REQUEST_REPOSITORY_REF) : null;
137+
if (!StringUtils.isEmpty(authorizationRequestRepositoryRef)) {
138+
authorizationRequestRepository = new RuntimeBeanReference(authorizationRequestRepositoryRef);
139+
} else {
140+
authorizationRequestRepository = BeanDefinitionBuilder.rootBeanDefinition(
141+
"org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository")
142+
.getBeanDefinition();
143+
}
144+
return authorizationRequestRepository;
145+
}
146+
147+
private BeanMetadataElement getAccessTokenResponseClient(Element element) {
148+
BeanMetadataElement accessTokenResponseClient;
149+
String accessTokenResponseClientRef = element != null ?
150+
element.getAttribute(ATT_ACCESS_TOKEN_RESPONSE_CLIENT_REF) : null;
151+
if (!StringUtils.isEmpty(accessTokenResponseClientRef)) {
152+
accessTokenResponseClient = new RuntimeBeanReference(accessTokenResponseClientRef);
153+
} else {
154+
accessTokenResponseClient = BeanDefinitionBuilder.rootBeanDefinition(
155+
"org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient")
156+
.getBeanDefinition();
157+
}
158+
return accessTokenResponseClient;
159+
}
160+
161+
BeanDefinition getAuthorizationRequestRedirectFilter() {
162+
return this.authorizationRequestRedirectFilter;
163+
}
164+
165+
BeanDefinition getAuthorizationCodeGrantFilter() {
166+
return this.authorizationCodeGrantFilter;
167+
}
168+
169+
BeanDefinition getAuthorizationCodeAuthenticationProvider() {
170+
return this.authorizationCodeAuthenticationProvider;
171+
}
172+
}

config/src/main/java/org/springframework/security/config/http/SecurityFilters.java

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ enum SecurityFilters {
5151
JAAS_API_SUPPORT_FILTER,
5252
REMEMBER_ME_FILTER,
5353
ANONYMOUS_FILTER,
54+
OAUTH2_AUTHORIZATION_CODE_GRANT_FILTER,
5455
SESSION_MANAGEMENT_FILTER,
5556
EXCEPTION_TRANSLATION_FILTER,
5657
FILTER_SECURITY_INTERCEPTOR,

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

+28-2
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ http-firewall =
296296

297297
http =
298298
## 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".
299-
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?) }
299+
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?) }
300300
http.attlist &=
301301
## 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.
302302
attribute pattern {xsd:token}?
@@ -483,6 +483,32 @@ oauth2-login.attlist &=
483483
## Reference to the JwtDecoderFactory used by OidcAuthorizationCodeAuthenticationProvider
484484
attribute jwt-decoder-factory-ref {xsd:token}?
485485

486+
oauth2-client =
487+
## Configures OAuth 2.0 Client support.
488+
element oauth2-client {oauth2-client.attlist, (authorization-code-grant?) }
489+
oauth2-client.attlist &=
490+
## Reference to the ClientRegistrationRepository
491+
attribute client-registration-repository-ref {xsd:token}?
492+
oauth2-client.attlist &=
493+
## Reference to the OAuth2AuthorizedClientRepository
494+
attribute authorized-client-repository-ref {xsd:token}?
495+
oauth2-client.attlist &=
496+
## Reference to the OAuth2AuthorizedClientService
497+
attribute authorized-client-service-ref {xsd:token}?
498+
499+
authorization-code-grant =
500+
## Configures OAuth 2.0 Authorization Code Grant.
501+
element authorization-code-grant {authorization-code-grant.attlist, empty}
502+
authorization-code-grant.attlist &=
503+
## Reference to the AuthorizationRequestRepository
504+
attribute authorization-request-repository-ref {xsd:token}?
505+
authorization-code-grant.attlist &=
506+
## Reference to the OAuth2AuthorizationRequestResolver
507+
attribute authorization-request-resolver-ref {xsd:token}?
508+
authorization-code-grant.attlist &=
509+
## Reference to the OAuth2AccessTokenResponseClient
510+
attribute access-token-response-client-ref {xsd:token}?
511+
486512
client-registrations =
487513
## Container element for client(s) registered with an OAuth 2.0 or OpenID Connect 1.0 Provider.
488514
element client-registrations {client-registration+, provider*}
@@ -1024,4 +1050,4 @@ position =
10241050
## The explicit position at which the custom-filter should be placed in the chain. Use if you are replacing a standard filter.
10251051
attribute position {named-security-filter}
10261052

1027-
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"
1053+
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"

0 commit comments

Comments
 (0)