From 9dbd1f3e259f196b17be32020e8059a007efb1f3 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 | 188 ++++++++++++++++++ .../config/http/HttpConfigurationBuilder.java | 36 +++- .../security/config/spring-security-5.8.rnc | 6 + .../security/config/spring-security-5.8.xsd | 72 ++++--- .../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 + 22 files changed, 1034 insertions(+), 30 deletions(-) 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..0bb17a56f9 --- /dev/null +++ b/config/src/main/java/org/springframework/security/config/http/AuthorizationFilterParser.java @@ -0,0 +1,188 @@ +/* + * 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 javax.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.FilterInvocationSecurityMetadataSource; +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; + +/** + * Allows for convenient creation of a {@link FilterInvocationSecurityMetadataSource} bean + * for use with a FilterSecurityInterceptor. + * + * @author Luke Taylor + */ +public 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; + } + + public 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 2e1a4c4a48..e7539558b5 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 @@ -41,6 +41,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; @@ -113,6 +114,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"; @@ -220,7 +225,7 @@ class HttpConfigurationBuilder { createServletApiFilter(authenticationManager); createJaasApiFilter(); createChannelProcessingFilter(); - createFilterSecurityInterceptor(authenticationManager); + createFilterSecurity(authenticationManager); createAddHeadersFilter(); createCorsFilter(); createWellKnownChangePasswordRedirectFilter(); @@ -675,6 +680,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..2d3538a09d 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 @@ -124,7 +124,7 @@ - + @@ -408,7 +408,7 @@ - + @@ -488,7 +488,7 @@ - + @@ -541,7 +541,7 @@ - + @@ -842,13 +842,13 @@ - - - - - - - + + + + + + + @@ -1287,6 +1287,20 @@ + + + Optional attribute specifying the ID of the AccessDecisionManager implementation which + should be used for authorizing HTTP requests. + + + + + + Optional attribute specifying the ID of the AccessDecisionManager implementation which + should be used for authorizing HTTP requests. + + + Optional attribute specifying the ID of the AccessDecisionManager implementation which @@ -1335,7 +1349,7 @@ - + @@ -1360,7 +1374,7 @@ - + @@ -1417,7 +1431,7 @@ - + @@ -1464,7 +1478,7 @@ - + @@ -1979,7 +1993,7 @@ - + Sets up an attribute exchange configuration to request specified attributes from the @@ -2046,7 +2060,7 @@ - + @@ -2103,7 +2117,7 @@ - + @@ -2556,7 +2570,7 @@ - + @@ -2589,7 +2603,7 @@ - + @@ -2645,7 +2659,7 @@ - + @@ -2692,7 +2706,7 @@ - + @@ -2790,7 +2804,7 @@ - + @@ -2823,8 +2837,8 @@ - - + + @@ -2841,7 +2855,7 @@ - + @@ -2978,7 +2992,7 @@ - + @@ -3030,7 +3044,7 @@ - + @@ -3773,4 +3787,4 @@ - \ No newline at end of file + 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 990a648221..c0fa5f46b3 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 javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; @@ -26,12 +27,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; @@ -60,6 +66,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 2ebd408395..41e8d20fbb 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 @@ -28,8 +28,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; @@ -37,6 +39,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; @@ -75,6 +78,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 */ @@ -91,6 +111,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(); @@ -102,6 +141,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 */ @@ -118,6 +171,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 */ @@ -134,6 +207,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 */ @@ -148,6 +241,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(); @@ -156,6 +267,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(); @@ -169,6 +291,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(); @@ -185,30 +324,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 5008922169..1f26ad0f45 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**