Use AuthorizationManager in <http>

Closes gh-11305
This commit is contained in:
Josh Cummings
2022-05-31 15:10:00 -06:00
parent e125a76687
commit 2afa9313eb
24 changed files with 1013 additions and 1 deletions

View File

@@ -0,0 +1,180 @@
/*
* Copyright 2002-2016 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 java.util.List;
import java.util.Map;
import jakarta.servlet.http.HttpServletRequest;
import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.beans.factory.xml.XmlReaderContext;
import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.config.Elements;
import org.springframework.security.web.access.expression.DefaultHttpSecurityExpressionHandler;
import org.springframework.security.web.access.expression.WebExpressionAuthorizationManager;
import org.springframework.security.web.access.intercept.AuthorizationFilter;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
import org.springframework.security.web.access.intercept.RequestMatcherDelegatingAuthorizationManager;
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
class AuthorizationFilterParser implements BeanDefinitionParser {
private static final String ATT_USE_EXPRESSIONS = "use-expressions";
private static final String ATT_HTTP_METHOD = "method";
private static final String ATT_PATTERN = "pattern";
private static final String ATT_ACCESS = "access";
private static final String ATT_SERVLET_PATH = "servlet-path";
private String authorizationManagerRef;
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
if (!isUseExpressions(element)) {
parserContext.getReaderContext().error("AuthorizationManager must be used with `use-expressions=\"true\"",
element);
return null;
}
this.authorizationManagerRef = createAuthorizationManager(element, parserContext);
BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder.rootBeanDefinition(AuthorizationFilter.class);
filterBuilder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
BeanDefinition filter = filterBuilder.addConstructorArgReference(this.authorizationManagerRef)
.getBeanDefinition();
String id = element.getAttribute(AbstractBeanDefinitionParser.ID_ATTRIBUTE);
if (StringUtils.hasText(id)) {
parserContext.registerComponent(new BeanComponentDefinition(filter, id));
parserContext.getRegistry().registerBeanDefinition(id, filter);
}
return filter;
}
String getAuthorizationManagerRef() {
return this.authorizationManagerRef;
}
private String createAuthorizationManager(Element element, ParserContext parserContext) {
XmlReaderContext context = parserContext.getReaderContext();
String authorizationManagerRef = element.getAttribute("authorization-manager-ref");
if (StringUtils.hasText(authorizationManagerRef)) {
return authorizationManagerRef;
}
Element expressionHandlerElt = DomUtils.getChildElementByTagName(element, Elements.EXPRESSION_HANDLER);
String expressionHandlerRef = (expressionHandlerElt != null) ? expressionHandlerElt.getAttribute("ref") : null;
if (expressionHandlerRef == null) {
expressionHandlerRef = registerDefaultExpressionHandler(parserContext);
}
MatcherType matcherType = MatcherType.fromElement(element);
ManagedMap<BeanMetadataElement, BeanDefinition> matcherToExpression = new ManagedMap<>();
List<Element> interceptMessages = DomUtils.getChildElementsByTagName(element, Elements.INTERCEPT_URL);
for (Element interceptMessage : interceptMessages) {
String accessExpression = interceptMessage.getAttribute(ATT_ACCESS);
BeanDefinitionBuilder authorizationManager = BeanDefinitionBuilder
.rootBeanDefinition(WebExpressionAuthorizationManager.class);
authorizationManager.addPropertyReference("expressionHandler", expressionHandlerRef);
authorizationManager.addConstructorArgValue(accessExpression);
BeanMetadataElement matcher = createMatcher(matcherType, interceptMessage, parserContext);
matcherToExpression.put(matcher, authorizationManager.getBeanDefinition());
}
BeanDefinitionBuilder mds = BeanDefinitionBuilder
.rootBeanDefinition(RequestMatcherDelegatingAuthorizationManagerFactory.class);
mds.setFactoryMethod("createRequestMatcherDelegatingAuthorizationManager");
mds.addConstructorArgValue(matcherToExpression);
return context.registerWithGeneratedName(mds.getBeanDefinition());
}
private BeanMetadataElement createMatcher(MatcherType matcherType, Element urlElt, ParserContext parserContext) {
String path = urlElt.getAttribute(ATT_PATTERN);
String matcherRef = urlElt.getAttribute(HttpSecurityBeanDefinitionParser.ATT_REQUEST_MATCHER_REF);
boolean hasMatcherRef = StringUtils.hasText(matcherRef);
if (!hasMatcherRef && !StringUtils.hasText(path)) {
parserContext.getReaderContext().error("path attribute cannot be empty or null", urlElt);
}
String method = urlElt.getAttribute(ATT_HTTP_METHOD);
if (!StringUtils.hasText(method)) {
method = null;
}
String servletPath = urlElt.getAttribute(ATT_SERVLET_PATH);
if (!StringUtils.hasText(servletPath)) {
servletPath = null;
}
else if (!MatcherType.mvc.equals(matcherType)) {
parserContext.getReaderContext().error(
ATT_SERVLET_PATH + " is not applicable for request-matcher: '" + matcherType.name() + "'", urlElt);
}
return hasMatcherRef ? new RuntimeBeanReference(matcherRef)
: matcherType.createMatcher(parserContext, path, method, servletPath);
}
String registerDefaultExpressionHandler(ParserContext pc) {
BeanDefinition expressionHandler = GrantedAuthorityDefaultsParserUtils.registerWithDefaultRolePrefix(pc,
DefaultWebSecurityExpressionHandlerBeanFactory.class);
String expressionHandlerRef = pc.getReaderContext().generateBeanName(expressionHandler);
pc.registerBeanComponent(new BeanComponentDefinition(expressionHandler, expressionHandlerRef));
return expressionHandlerRef;
}
boolean isUseExpressions(Element elt) {
String useExpressions = elt.getAttribute(ATT_USE_EXPRESSIONS);
return !StringUtils.hasText(useExpressions) || "true".equals(useExpressions);
}
private static class RequestMatcherDelegatingAuthorizationManagerFactory {
private static AuthorizationManager<HttpServletRequest> createRequestMatcherDelegatingAuthorizationManager(
Map<RequestMatcher, AuthorizationManager<RequestAuthorizationContext>> beans) {
RequestMatcherDelegatingAuthorizationManager.Builder builder = RequestMatcherDelegatingAuthorizationManager
.builder();
for (Map.Entry<RequestMatcher, AuthorizationManager<RequestAuthorizationContext>> entry : beans
.entrySet()) {
builder.add(entry.getKey(), entry.getValue());
}
return builder.add(AnyRequestMatcher.INSTANCE, AuthenticatedAuthorizationManager.authenticated()).build();
}
}
static class DefaultWebSecurityExpressionHandlerBeanFactory
extends GrantedAuthorityDefaultsParserUtils.AbstractGrantedAuthorityDefaultsBeanFactory {
private DefaultHttpSecurityExpressionHandler handler = new DefaultHttpSecurityExpressionHandler();
@Override
public DefaultHttpSecurityExpressionHandler getBean() {
this.handler.setDefaultRolePrefix(this.rolePrefix);
return this.handler;
}
}
}

View File

@@ -40,6 +40,7 @@ import org.springframework.security.access.vote.RoleVoter;
import org.springframework.security.config.Elements;
import org.springframework.security.config.http.GrantedAuthorityDefaultsParserUtils.AbstractGrantedAuthorityDefaultsBeanFactory;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.access.AuthorizationManagerWebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.channel.ChannelDecisionManagerImpl;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
@@ -112,6 +113,10 @@ class HttpConfigurationBuilder {
private static final String ATT_DISABLE_URL_REWRITING = "disable-url-rewriting";
private static final String ATT_USE_AUTHORIZATION_MGR = "use-authorization-manager";
private static final String ATT_AUTHORIZATION_MGR = "authorization-manager-ref";
private static final String ATT_ACCESS_MGR = "access-decision-manager-ref";
private static final String ATT_ONCE_PER_REQUEST = "once-per-request";
@@ -219,7 +224,7 @@ class HttpConfigurationBuilder {
createServletApiFilter(authenticationManager);
createJaasApiFilter();
createChannelProcessingFilter();
createFilterSecurityInterceptor(authenticationManager);
createFilterSecurity(authenticationManager);
createAddHeadersFilter();
createCorsFilter();
createWellKnownChangePasswordRedirectFilter();
@@ -674,6 +679,35 @@ class HttpConfigurationBuilder {
this.requestCacheAwareFilter.getConstructorArgumentValues().addGenericArgumentValue(this.requestCache);
}
private void createFilterSecurity(BeanReference authManager) {
boolean useAuthorizationManager = Boolean.parseBoolean(this.httpElt.getAttribute(ATT_USE_AUTHORIZATION_MGR));
if (useAuthorizationManager) {
createAuthorizationFilter();
return;
}
if (StringUtils.hasText(this.httpElt.getAttribute(ATT_AUTHORIZATION_MGR))) {
createAuthorizationFilter();
return;
}
createFilterSecurityInterceptor(authManager);
}
private void createAuthorizationFilter() {
AuthorizationFilterParser authorizationFilterParser = new AuthorizationFilterParser();
BeanDefinition fsiBean = authorizationFilterParser.parse(this.httpElt, this.pc);
String fsiId = this.pc.getReaderContext().generateBeanName(fsiBean);
this.pc.registerBeanComponent(new BeanComponentDefinition(fsiBean, fsiId));
// Create and register a AuthorizationManagerWebInvocationPrivilegeEvaluator for
// use with
// taglibs etc.
BeanDefinition wipe = BeanDefinitionBuilder
.rootBeanDefinition(AuthorizationManagerWebInvocationPrivilegeEvaluator.class)
.addConstructorArgReference(authorizationFilterParser.getAuthorizationManagerRef()).getBeanDefinition();
this.pc.registerBeanComponent(
new BeanComponentDefinition(wipe, this.pc.getReaderContext().generateBeanName(wipe)));
this.fsi = new RuntimeBeanReference(fsiId);
}
private void createFilterSecurityInterceptor(BeanReference authManager) {
boolean useExpressions = FilterInvocationSecurityMetadataSourceParser.isUseExpressions(this.httpElt);
RootBeanDefinition securityMds = FilterInvocationSecurityMetadataSourceParser

View File

@@ -350,6 +350,12 @@ http.attlist &=
http.attlist &=
## If available, runs the request as the Subject acquired from the JaasAuthenticationToken. Defaults to "false".
attribute jaas-api-provision {xsd:boolean}?
http.attlist &=
## Use AuthorizationManager API instead of SecurityMetadataSource
attribute use-authorization-manager {xsd:boolean}?
http.attlist &=
## Use this AuthorizationManager instead of deriving one from <intercept-url> elements
attribute authorization-manager-ref {xsd:token}?
http.attlist &=
## Optional attribute specifying the ID of the AccessDecisionManager implementation which should be used for authorizing HTTP requests.
attribute access-decision-manager-ref {xsd:token}?

View File

@@ -1287,6 +1287,18 @@
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="use-authorization-manager" type="xs:boolean">
<xs:annotation>
<xs:documentation>Use AuthorizationManager API instead of SecurityMetadataSource
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="authorization-manager-ref" type="xs:token">
<xs:annotation>
<xs:documentation>Use this AuthorizationManager instead of deriving one from &lt;intercept-url&gt; elements
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="access-decision-manager-ref" type="xs:token">
<xs:annotation>
<xs:documentation>Optional attribute specifying the ID of the AccessDecisionManager implementation which

View File

@@ -350,6 +350,12 @@ http.attlist &=
http.attlist &=
## If available, runs the request as the Subject acquired from the JaasAuthenticationToken. Defaults to "false".
attribute jaas-api-provision {xsd:boolean}?
http.attlist &=
## Use AuthorizationManager API instead of SecurityMetadataSource
attribute use-authorization-manager {xsd:boolean}?
http.attlist &=
## Use this AuthorizationManager instead of deriving one from <intercept-url> elements
attribute authorization-manager-ref {xsd:token}?
http.attlist &=
## Optional attribute specifying the ID of the AccessDecisionManager implementation which should be used for authorizing HTTP requests.
attribute access-decision-manager-ref {xsd:token}?

View File

@@ -1265,6 +1265,18 @@
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="use-authorization-manager" type="xs:boolean">
<xs:annotation>
<xs:documentation>Use AuthorizationManager API instead of SecurityMetadataSource
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="authorization-manager-ref" type="xs:token">
<xs:annotation>
<xs:documentation>Use this AuthorizationManager instead of deriving one from &lt;intercept-url&gt; elements
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="access-decision-manager-ref" type="xs:token">
<xs:annotation>
<xs:documentation>Optional attribute specifying the ID of the AccessDecisionManager implementation which