From 2afa9313eb08b29f36b1f3a4c5b938a2a34bb809 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Tue, 31 May 2022 15:10:00 -0600 Subject: [PATCH] Use AuthorizationManager in Closes gh-11305 --- .../http/AuthorizationFilterParser.java | 180 +++++++++++++++++ .../config/http/HttpConfigurationBuilder.java | 36 +++- .../security/config/spring-security-5.8.rnc | 6 + .../security/config/spring-security-5.8.xsd | 12 ++ .../security/config/spring-security-6.0.rnc | 6 + .../security/config/spring-security-6.0.xsd | 12 ++ .../security/config/http/HttpConfigTests.java | 30 +++ .../config/http/InterceptUrlConfigTests.java | 183 ++++++++++++++++++ .../HttpConfigTests-AuthorizationManager.xml | 36 ++++ ...onfigTests-MinimalAuthorizationManager.xml | 32 +++ ...MatcherServletPathAuthorizationManager.xml | 33 ++++ ...lCasePathVariablesAuthorizationManager.xml | 36 ++++ ...MatcherServletPathAuthorizationManager.xml | 33 ++++ ...MatcherServletPathAuthorizationManager.xml | 33 ++++ ...igTests-HasAnyRoleAuthorizationManager.xml | 35 ++++ ...gTests-MvcMatchersAuthorizationManager.xml | 42 ++++ ...chersPathVariablesAuthorizationManager.xml | 41 ++++ ...atchersServletPathAuthorizationManager.xml | 42 ++++ ...gTests-PatchMethodAuthorizationManager.xml | 36 ++++ ...ests-PathVariablesAuthorizationManager.xml | 36 ++++ ...MatcherServletPathAuthorizationManager.xml | 33 ++++ ...onfigTests-Sec2256AuthorizationManager.xml | 37 ++++ ...rsionPathVariablesAuthorizationManager.xml | 37 ++++ .../servlet/appendix/namespace/http.adoc | 7 + 24 files changed, 1013 insertions(+), 1 deletion(-) create mode 100644 config/src/main/java/org/springframework/security/config/http/AuthorizationFilterParser.java create mode 100644 config/src/test/resources/org/springframework/security/config/http/HttpConfigTests-AuthorizationManager.xml create mode 100644 config/src/test/resources/org/springframework/security/config/http/HttpConfigTests-MinimalAuthorizationManager.xml create mode 100644 config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-AntMatcherServletPathAuthorizationManager.xml create mode 100644 config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-CamelCasePathVariablesAuthorizationManager.xml create mode 100644 config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-CiRegexMatcherServletPathAuthorizationManager.xml create mode 100644 config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-DefaultMatcherServletPathAuthorizationManager.xml create mode 100644 config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-HasAnyRoleAuthorizationManager.xml create mode 100644 config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchersAuthorizationManager.xml create mode 100644 config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchersPathVariablesAuthorizationManager.xml create mode 100644 config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchersServletPathAuthorizationManager.xml create mode 100644 config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-PatchMethodAuthorizationManager.xml create mode 100644 config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-PathVariablesAuthorizationManager.xml create mode 100644 config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-RegexMatcherServletPathAuthorizationManager.xml create mode 100644 config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-Sec2256AuthorizationManager.xml create mode 100644 config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-TypeConversionPathVariablesAuthorizationManager.xml diff --git a/config/src/main/java/org/springframework/security/config/http/AuthorizationFilterParser.java b/config/src/main/java/org/springframework/security/config/http/AuthorizationFilterParser.java new file mode 100644 index 0000000000..6e1c7a0e67 --- /dev/null +++ b/config/src/main/java/org/springframework/security/config/http/AuthorizationFilterParser.java @@ -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 matcherToExpression = new ManagedMap<>(); + List 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 createRequestMatcherDelegatingAuthorizationManager( + Map> beans) { + RequestMatcherDelegatingAuthorizationManager.Builder builder = RequestMatcherDelegatingAuthorizationManager + .builder(); + for (Map.Entry> 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; + } + + } + +} diff --git a/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java b/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java index a5beddf172..84d4a46e1c 100644 --- a/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java +++ b/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java @@ -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 diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-5.8.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-5.8.rnc index 86ded9594e..1db6c30464 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-5.8.rnc +++ b/config/src/main/resources/org/springframework/security/config/spring-security-5.8.rnc @@ -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 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}? diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-5.8.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-5.8.xsd index f7f9977f47..02e9139568 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-5.8.xsd +++ b/config/src/main/resources/org/springframework/security/config/spring-security-5.8.xsd @@ -1287,6 +1287,18 @@ + + + Use AuthorizationManager API instead of SecurityMetadataSource + + + + + + Use this AuthorizationManager instead of deriving one from <intercept-url> elements + + + Optional attribute specifying the ID of the AccessDecisionManager implementation which diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-6.0.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-6.0.rnc index 26d9f3bfa8..d76c32722a 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-6.0.rnc +++ b/config/src/main/resources/org/springframework/security/config/spring-security-6.0.rnc @@ -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 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}? diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-6.0.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-6.0.xsd index 510e04af2b..d2048ac5b7 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-6.0.xsd +++ b/config/src/main/resources/org/springframework/security/config/spring-security-6.0.xsd @@ -1265,6 +1265,18 @@ + + + Use AuthorizationManager API instead of SecurityMetadataSource + + + + + + Use this AuthorizationManager instead of deriving one from <intercept-url> elements + + + Optional attribute specifying the ID of the AccessDecisionManager implementation which diff --git a/config/src/test/java/org/springframework/security/config/http/HttpConfigTests.java b/config/src/test/java/org/springframework/security/config/http/HttpConfigTests.java index 423481921f..8be9526bc4 100644 --- a/config/src/test/java/org/springframework/security/config/http/HttpConfigTests.java +++ b/config/src/test/java/org/springframework/security/config/http/HttpConfigTests.java @@ -16,6 +16,7 @@ package org.springframework.security.config.http; +import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponseWrapper; import org.apache.http.HttpStatus; @@ -25,12 +26,17 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.authorization.AuthorizationDecision; +import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.web.FilterChainProxy; import org.springframework.test.web.servlet.MockMvc; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; 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; @@ -59,6 +65,30 @@ public class HttpConfigTests { // @formatter:on } + @Test + public void getWhenUsingMinimalAuthorizationManagerThenRedirectsToLogin() throws Exception { + this.spring.configLocations(this.xml("MinimalAuthorizationManager")).autowire(); + // @formatter:off + this.mvc.perform(get("/")) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("http://localhost/login")); + // @formatter:on + } + + @Test + public void getWhenUsingAuthorizationManagerThenRedirectsToLogin() throws Exception { + this.spring.configLocations(this.xml("AuthorizationManager")).autowire(); + AuthorizationManager authorizationManager = this.spring.getContext() + .getBean(AuthorizationManager.class); + given(authorizationManager.check(any(), any())).willReturn(new AuthorizationDecision(false)); + // @formatter:off + this.mvc.perform(get("/")) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("http://localhost/login")); + // @formatter:on + verify(authorizationManager).check(any(), any()); + } + @Test public void getWhenUsingMinimalConfigurationThenPreventsSessionAsUrlParameter() throws Exception { this.spring.configLocations(this.xml("Minimal")).autowire(); diff --git a/config/src/test/java/org/springframework/security/config/http/InterceptUrlConfigTests.java b/config/src/test/java/org/springframework/security/config/http/InterceptUrlConfigTests.java index 07adf58c4d..1ba39012bb 100644 --- a/config/src/test/java/org/springframework/security/config/http/InterceptUrlConfigTests.java +++ b/config/src/test/java/org/springframework/security/config/http/InterceptUrlConfigTests.java @@ -27,8 +27,10 @@ import org.mockito.stubbing.Answer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; import org.springframework.mock.web.MockServletContext; +import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; +import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.RequestPostProcessor; import org.springframework.web.bind.annotation.PathVariable; @@ -36,6 +38,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.ConfigurableWebApplicationContext; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; @@ -74,6 +77,23 @@ public class InterceptUrlConfigTests { // @formatter:on } + /** + * sec-2256 + */ + @Test + public void requestWhenMethodIsSpecifiedAndAuthorizationManagerThenItIsNotGivenPriority() throws Exception { + this.spring.configLocations(this.xml("Sec2256AuthorizationManager")).autowire(); + // @formatter:off + this.mvc.perform(post("/path").with(userCredentials())) + .andExpect(status().isOk()); + this.mvc.perform(get("/path").with(userCredentials())) + .andExpect(status().isOk()); + // @formatter:on + assertThat(this.spring.getContext().getBeanNamesForType(FilterInvocationSecurityMetadataSource.class)) + .isEmpty(); + assertThat(this.spring.getContext().getBean(AuthorizationManager.class)).isNotNull(); + } + /** * sec-2355 */ @@ -90,6 +110,25 @@ public class InterceptUrlConfigTests { // @formatter:on } + /** + * sec-2355 + */ + @Test + public void requestWhenUsingPatchAndAuthorizationManagerThenAuthorizesRequestsAccordingly() throws Exception { + this.spring.configLocations(this.xml("PatchMethodAuthorizationManager")).autowire(); + // @formatter:off + this.mvc.perform(get("/path").with(userCredentials())) + .andExpect(status().isOk()); + this.mvc.perform(patch("/path").with(userCredentials())) + .andExpect(status().isForbidden()); + this.mvc.perform(patch("/path").with(adminCredentials())) + .andExpect(status().isOk()); + // @formatter:on + assertThat(this.spring.getContext().getBeanNamesForType(FilterInvocationSecurityMetadataSource.class)) + .isEmpty(); + assertThat(this.spring.getContext().getBean(AuthorizationManager.class)).isNotNull(); + } + @Test public void requestWhenUsingHasAnyRoleThenAuthorizesRequestsAccordingly() throws Exception { this.spring.configLocations(this.xml("HasAnyRole")).autowire(); @@ -101,6 +140,20 @@ public class InterceptUrlConfigTests { // @formatter:on } + @Test + public void requestWhenUsingHasAnyRoleAndAuthorizationManagerThenAuthorizesRequestsAccordingly() throws Exception { + this.spring.configLocations(this.xml("HasAnyRoleAuthorizationManager")).autowire(); + // @formatter:off + this.mvc.perform(get("/path").with(userCredentials())) + .andExpect(status().isOk()); + this.mvc.perform(get("/path").with(adminCredentials())) + .andExpect(status().isForbidden()); + // @formatter:on + assertThat(this.spring.getContext().getBeanNamesForType(FilterInvocationSecurityMetadataSource.class)) + .isEmpty(); + assertThat(this.spring.getContext().getBean(AuthorizationManager.class)).isNotNull(); + } + /** * sec-2059 */ @@ -117,6 +170,26 @@ public class InterceptUrlConfigTests { // @formatter:on } + /** + * sec-2059 + */ + @Test + public void requestWhenUsingPathVariablesAndAuthorizationManagerThenAuthorizesRequestsAccordingly() + throws Exception { + this.spring.configLocations(this.xml("PathVariablesAuthorizationManager")).autowire(); + // @formatter:off + this.mvc.perform(get("/path/user/path").with(userCredentials())) + .andExpect(status().isOk()); + this.mvc.perform(get("/path/otheruser/path").with(userCredentials())) + .andExpect(status().isForbidden()); + this.mvc.perform(get("/path").with(userCredentials())) + .andExpect(status().isForbidden()); + // @formatter:on + assertThat(this.spring.getContext().getBeanNamesForType(FilterInvocationSecurityMetadataSource.class)) + .isEmpty(); + assertThat(this.spring.getContext().getBean(AuthorizationManager.class)).isNotNull(); + } + /** * gh-3786 */ @@ -133,6 +206,26 @@ public class InterceptUrlConfigTests { // @formatter:on } + /** + * gh-3786 + */ + @Test + public void requestWhenUsingCamelCasePathVariablesAndAuthorizationManagerThenAuthorizesRequestsAccordingly() + throws Exception { + this.spring.configLocations(this.xml("CamelCasePathVariablesAuthorizationManager")).autowire(); + // @formatter:off + this.mvc.perform(get("/path/user/path").with(userCredentials())) + .andExpect(status().isOk()); + this.mvc.perform(get("/path/otheruser/path").with(userCredentials())) + .andExpect(status().isForbidden()); + this.mvc.perform(get("/PATH/user/path").with(userCredentials())) + .andExpect(status().isForbidden()); + // @formatter:on + assertThat(this.spring.getContext().getBeanNamesForType(FilterInvocationSecurityMetadataSource.class)) + .isEmpty(); + assertThat(this.spring.getContext().getBean(AuthorizationManager.class)).isNotNull(); + } + /** * sec-2059 */ @@ -147,6 +240,24 @@ public class InterceptUrlConfigTests { // @formatter:on } + /** + * sec-2059 + */ + @Test + public void requestWhenUsingPathVariablesAndTypeConversionAndAuthorizationManagerThenAuthorizesRequestsAccordingly() + throws Exception { + this.spring.configLocations(this.xml("TypeConversionPathVariablesAuthorizationManager")).autowire(); + // @formatter:off + this.mvc.perform(get("/path/1/path").with(userCredentials())) + .andExpect(status().isOk()); + this.mvc.perform(get("/path/2/path").with(userCredentials())) + .andExpect(status().isForbidden()); + // @formatter:on + assertThat(this.spring.getContext().getBeanNamesForType(FilterInvocationSecurityMetadataSource.class)) + .isEmpty(); + assertThat(this.spring.getContext().getBean(AuthorizationManager.class)).isNotNull(); + } + @Test public void requestWhenUsingMvcMatchersThenAuthorizesRequestsAccordingly() throws Exception { this.spring.configLocations(this.xml("MvcMatchers")).autowire(); @@ -155,6 +266,17 @@ public class InterceptUrlConfigTests { this.mvc.perform(get("/path/")).andExpect(status().isUnauthorized()); } + @Test + public void requestWhenUsingMvcMatchersAndAuthorizationManagerThenAuthorizesRequestsAccordingly() throws Exception { + this.spring.configLocations(this.xml("MvcMatchersAuthorizationManager")).autowire(); + this.mvc.perform(get("/path")).andExpect(status().isUnauthorized()); + this.mvc.perform(get("/path.html")).andExpect(status().isUnauthorized()); + this.mvc.perform(get("/path/")).andExpect(status().isUnauthorized()); + assertThat(this.spring.getContext().getBeanNamesForType(FilterInvocationSecurityMetadataSource.class)) + .isEmpty(); + assertThat(this.spring.getContext().getBean(AuthorizationManager.class)).isNotNull(); + } + @Test public void requestWhenUsingMvcMatchersAndPathVariablesThenAuthorizesRequestsAccordingly() throws Exception { this.spring.configLocations(this.xml("MvcMatchersPathVariables")).autowire(); @@ -168,6 +290,23 @@ public class InterceptUrlConfigTests { // @formatter:on } + @Test + public void requestWhenUsingMvcMatchersAndPathVariablesAndAuthorizationManagerThenAuthorizesRequestsAccordingly() + throws Exception { + this.spring.configLocations(this.xml("MvcMatchersPathVariablesAuthorizationManager")).autowire(); + // @formatter:off + this.mvc.perform(get("/path/user/path").with(userCredentials())) + .andExpect(status().isOk()); + this.mvc.perform(get("/path/otheruser/path").with(userCredentials())) + .andExpect(status().isForbidden()); + this.mvc.perform(get("/PATH/user/path").with(userCredentials())) + .andExpect(status().isForbidden()); + // @formatter:on + assertThat(this.spring.getContext().getBeanNamesForType(FilterInvocationSecurityMetadataSource.class)) + .isEmpty(); + assertThat(this.spring.getContext().getBean(AuthorizationManager.class)).isNotNull(); + } + @Test public void requestWhenUsingMvcMatchersAndServletPathThenAuthorizesRequestsAccordingly() throws Exception { this.spring.configLocations(this.xml("MvcMatchersServletPath")).autowire(); @@ -184,30 +323,74 @@ public class InterceptUrlConfigTests { // @formatter:on } + @Test + public void requestWhenUsingMvcMatchersAndServletPathAndAuthorizationManagerThenAuthorizesRequestsAccordingly() + throws Exception { + this.spring.configLocations(this.xml("MvcMatchersServletPathAuthorizationManager")).autowire(); + MockServletContext servletContext = mockServletContext("/spring"); + ConfigurableWebApplicationContext context = this.spring.getContext(); + context.setServletContext(servletContext); + // @formatter:off + this.mvc.perform(get("/spring/path").servletPath("/spring")) + .andExpect(status().isUnauthorized()); + this.mvc.perform(get("/spring/path.html").servletPath("/spring")) + .andExpect(status().isUnauthorized()); + this.mvc.perform(get("/spring/path/").servletPath("/spring")) + .andExpect(status().isUnauthorized()); + // @formatter:on + assertThat(this.spring.getContext().getBeanNamesForType(FilterInvocationSecurityMetadataSource.class)) + .isEmpty(); + assertThat(this.spring.getContext().getBean(AuthorizationManager.class)).isNotNull(); + } + @Test public void configureWhenUsingAntMatcherAndServletPathThenThrowsException() { assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy(() -> this.spring.configLocations(this.xml("AntMatcherServletPath")).autowire()); } + @Test + public void configureWhenUsingAntMatcherAndServletPathAndAuthorizationManagerThenThrowsException() { + assertThatExceptionOfType(BeanDefinitionParsingException.class).isThrownBy( + () -> this.spring.configLocations(this.xml("AntMatcherServletPathAuthorizationManager")).autowire()); + } + @Test public void configureWhenUsingRegexMatcherAndServletPathThenThrowsException() { assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy(() -> this.spring.configLocations(this.xml("RegexMatcherServletPath")).autowire()); } + @Test + public void configureWhenUsingRegexMatcherAndServletPathAndAuthorizationManagerThenThrowsException() { + assertThatExceptionOfType(BeanDefinitionParsingException.class).isThrownBy( + () -> this.spring.configLocations(this.xml("RegexMatcherServletPathAuthorizationManager")).autowire()); + } + @Test public void configureWhenUsingCiRegexMatcherAndServletPathThenThrowsException() { assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy(() -> this.spring.configLocations(this.xml("CiRegexMatcherServletPath")).autowire()); } + @Test + public void configureWhenUsingCiRegexMatcherAndServletPathAndAuthorizationManagerThenThrowsException() { + assertThatExceptionOfType(BeanDefinitionParsingException.class).isThrownBy(() -> this.spring + .configLocations(this.xml("CiRegexMatcherServletPathAuthorizationManager")).autowire()); + } + @Test public void configureWhenUsingDefaultMatcherAndServletPathThenThrowsException() { assertThatExceptionOfType(BeanDefinitionParsingException.class) .isThrownBy(() -> this.spring.configLocations(this.xml("DefaultMatcherServletPath")).autowire()); } + @Test + public void configureWhenUsingDefaultMatcherAndServletPathAndAuthorizationManagerThenThrowsException() { + assertThatExceptionOfType(BeanDefinitionParsingException.class).isThrownBy(() -> this.spring + .configLocations(this.xml("DefaultMatcherServletPathAuthorizationManager")).autowire()); + } + private static RequestPostProcessor adminCredentials() { return httpBasic("admin", "password"); } diff --git a/config/src/test/resources/org/springframework/security/config/http/HttpConfigTests-AuthorizationManager.xml b/config/src/test/resources/org/springframework/security/config/http/HttpConfigTests-AuthorizationManager.xml new file mode 100644 index 0000000000..981d7b6a8c --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/HttpConfigTests-AuthorizationManager.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/HttpConfigTests-MinimalAuthorizationManager.xml b/config/src/test/resources/org/springframework/security/config/http/HttpConfigTests-MinimalAuthorizationManager.xml new file mode 100644 index 0000000000..293fbe8e96 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/HttpConfigTests-MinimalAuthorizationManager.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-AntMatcherServletPathAuthorizationManager.xml b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-AntMatcherServletPathAuthorizationManager.xml new file mode 100644 index 0000000000..856892c719 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-AntMatcherServletPathAuthorizationManager.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-CamelCasePathVariablesAuthorizationManager.xml b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-CamelCasePathVariablesAuthorizationManager.xml new file mode 100644 index 0000000000..598394585b --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-CamelCasePathVariablesAuthorizationManager.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-CiRegexMatcherServletPathAuthorizationManager.xml b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-CiRegexMatcherServletPathAuthorizationManager.xml new file mode 100644 index 0000000000..25fab70579 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-CiRegexMatcherServletPathAuthorizationManager.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-DefaultMatcherServletPathAuthorizationManager.xml b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-DefaultMatcherServletPathAuthorizationManager.xml new file mode 100644 index 0000000000..3fb406032f --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-DefaultMatcherServletPathAuthorizationManager.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-HasAnyRoleAuthorizationManager.xml b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-HasAnyRoleAuthorizationManager.xml new file mode 100644 index 0000000000..3215253b71 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-HasAnyRoleAuthorizationManager.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchersAuthorizationManager.xml b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchersAuthorizationManager.xml new file mode 100644 index 0000000000..5d1b053a66 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchersAuthorizationManager.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchersPathVariablesAuthorizationManager.xml b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchersPathVariablesAuthorizationManager.xml new file mode 100644 index 0000000000..60fc53b7cd --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchersPathVariablesAuthorizationManager.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchersServletPathAuthorizationManager.xml b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchersServletPathAuthorizationManager.xml new file mode 100644 index 0000000000..866017eeb2 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-MvcMatchersServletPathAuthorizationManager.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-PatchMethodAuthorizationManager.xml b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-PatchMethodAuthorizationManager.xml new file mode 100644 index 0000000000..d525d6f2b6 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-PatchMethodAuthorizationManager.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-PathVariablesAuthorizationManager.xml b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-PathVariablesAuthorizationManager.xml new file mode 100644 index 0000000000..43cdb2bbf3 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-PathVariablesAuthorizationManager.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-RegexMatcherServletPathAuthorizationManager.xml b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-RegexMatcherServletPathAuthorizationManager.xml new file mode 100644 index 0000000000..0d0690efd7 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-RegexMatcherServletPathAuthorizationManager.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-Sec2256AuthorizationManager.xml b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-Sec2256AuthorizationManager.xml new file mode 100644 index 0000000000..a01eba3055 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-Sec2256AuthorizationManager.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-TypeConversionPathVariablesAuthorizationManager.xml b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-TypeConversionPathVariablesAuthorizationManager.xml new file mode 100644 index 0000000000..3ca186ee72 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/InterceptUrlConfigTests-TypeConversionPathVariablesAuthorizationManager.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + diff --git a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc index 3549b9b627..b9c1b3c705 100644 --- a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc +++ b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc @@ -29,6 +29,13 @@ These are fixed and cannot be replaced with alternatives. === Attributes The attributes on the `` element control some of the properties on the core filters. +[[nsa-http-use-authorization-manager]] +* **use-authorization-manager** +Use AuthorizationManager API instead of SecurityMetadataSource + +[[nsa-http-authorization-manager-ref]] +* **access-decision-manager-ref** +Use this AuthorizationManager instead of deriving one from elements [[nsa-http-access-decision-manager-ref]] * **access-decision-manager-ref**