diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractRequestMatcherBuilderRegistry.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractRequestMatcherBuilderRegistry.java deleted file mode 100644 index 57bd420c3b..0000000000 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractRequestMatcherBuilderRegistry.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2002-2023 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.annotation.web.configurers; - -import org.springframework.context.ApplicationContext; -import org.springframework.http.HttpMethod; -import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry; -import org.springframework.security.web.util.matcher.RequestMatcher; - -abstract class AbstractRequestMatcherBuilderRegistry extends AbstractRequestMatcherRegistry { - - private final RequestMatcherBuilder builder; - - AbstractRequestMatcherBuilderRegistry(ApplicationContext context) { - this(context, RequestMatcherBuilders.createDefault(context)); - } - - AbstractRequestMatcherBuilderRegistry(ApplicationContext context, RequestMatcherBuilder builder) { - setApplicationContext(context); - this.builder = builder; - } - - @Override - public final C requestMatchers(String... patterns) { - return requestMatchers(null, patterns); - } - - @Override - public final C requestMatchers(HttpMethod method, String... patterns) { - return requestMatchers(this.builder.matchers(method, patterns).toArray(RequestMatcher[]::new)); - } - - @Override - public final C requestMatchers(HttpMethod method) { - return requestMatchers(method, "/**"); - } - -} diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AntPathRequestMatcherBuilder.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AntPathRequestMatcherBuilder.java deleted file mode 100644 index 4026fa2d2f..0000000000 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AntPathRequestMatcherBuilder.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2002-2023 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.annotation.web.configurers; - -import org.springframework.http.HttpMethod; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; - -final class AntPathRequestMatcherBuilder implements RequestMatcherBuilder { - - private final String servletPath; - - private AntPathRequestMatcherBuilder(String servletPath) { - this.servletPath = servletPath; - } - - static AntPathRequestMatcherBuilder absolute() { - return new AntPathRequestMatcherBuilder(null); - } - - static AntPathRequestMatcherBuilder relativeTo(String path) { - return new AntPathRequestMatcherBuilder(path); - } - - @Override - public AntPathRequestMatcher matcher(String pattern) { - return matcher((String) null, pattern); - } - - @Override - public AntPathRequestMatcher matcher(HttpMethod method, String pattern) { - return matcher((method != null) ? method.name() : null, pattern); - } - - private AntPathRequestMatcher matcher(String method, String pattern) { - return new AntPathRequestMatcher(prependServletPath(pattern), method); - } - - private String prependServletPath(String pattern) { - if (this.servletPath == null) { - return pattern; - } - return this.servletPath + pattern; - } - -} diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java index f67ba9ad4c..1de4750a49 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java @@ -16,14 +16,10 @@ package org.springframework.security.config.annotation.web.configurers; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; -import java.util.function.Function; import java.util.function.Supplier; import io.micrometer.observation.ObservationRegistry; -import jakarta.servlet.http.HttpServletMapping; import jakarta.servlet.http.HttpServletRequest; import org.springframework.context.ApplicationContext; @@ -36,22 +32,17 @@ import org.springframework.security.authorization.AuthorizationEventPublisher; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.authorization.ObservationAuthorizationManager; import org.springframework.security.authorization.SpringAuthorizationEventPublisher; -import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry; import org.springframework.security.config.annotation.web.HttpSecurityBuilder; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.core.GrantedAuthorityDefaults; 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.servlet.util.matcher.MvcRequestMatcher; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcherEntry; import org.springframework.util.Assert; import org.springframework.util.function.SingletonSupplier; -import org.springframework.web.servlet.DispatcherServlet; /** * Adds a URL based authorization using {@link AuthorizationManager}. @@ -146,62 +137,41 @@ public final class AuthorizeHttpRequestsConfigurer { + extends AbstractRequestMatcherRegistry { private final RequestMatcherDelegatingAuthorizationManager.Builder managerBuilder = RequestMatcherDelegatingAuthorizationManager .builder(); - List unmappedMatchers; + private List unmappedMatchers; private int mappingCount; private boolean shouldFilterAllDispatcherTypes = true; - private final Map servletPattern = new LinkedHashMap<>(); - - AuthorizationManagerRequestMatcherRegistry(ApplicationContext context) { - super(context); + private AuthorizationManagerRequestMatcherRegistry(ApplicationContext context) { + setApplicationContext(context); } private void addMapping(RequestMatcher matcher, AuthorizationManager manager) { - Assert.isTrue(this.servletPattern.isEmpty(), - "Since you have used forServletPattern, all request matchers must be configured using forServletPattern; alternatively, you can use requestMatchers(RequestMatcher) for all requests."); this.unmappedMatchers = null; this.managerBuilder.add(matcher, manager); this.mappingCount++; } private void addFirst(RequestMatcher matcher, AuthorizationManager manager) { - Assert.isTrue(this.servletPattern.isEmpty(), - "Since you have used forServletPattern, all request matchers must be configured using forServletPattern; alternatively, you can use requestMatchers(RequestMatcher) for all requests."); this.unmappedMatchers = null; this.managerBuilder.mappings((m) -> m.add(0, new RequestMatcherEntry<>(matcher, manager))); this.mappingCount++; } - private AuthorizationManager servletAuthorizationManager() { - for (Map.Entry entry : this.servletPattern - .entrySet()) { - AuthorizationManagerServletRequestMatcherRegistry registry = entry.getValue(); - this.managerBuilder.add(new ServletPatternRequestMatcher(entry.getKey()), - registry.authorizationManager()); - } - return postProcess(this.managerBuilder.build()); - } - - private AuthorizationManager authorizationManager() { + private AuthorizationManager createAuthorizationManager() { Assert.state(this.unmappedMatchers == null, () -> "An incomplete mapping was found for " + this.unmappedMatchers + ". Try completing it with something like requestUrls()..hasRole('USER')"); Assert.state(this.mappingCount > 0, "At least one mapping is required (for example, authorizeHttpRequests().anyRequest().authenticated())"); - return postProcess(this.managerBuilder.build()); - } - - private AuthorizationManager createAuthorizationManager() { - AuthorizationManager manager = (this.servletPattern.isEmpty()) ? authorizationManager() - : servletAuthorizationManager(); ObservationRegistry registry = getObservationRegistry(); + RequestMatcherDelegatingAuthorizationManager manager = postProcess(this.managerBuilder.build()); if (registry.isNoop()) { return manager; } @@ -211,74 +181,7 @@ public final class AuthorizeHttpRequestsConfigurer requestMatchers) { this.unmappedMatchers = requestMatchers; - return new AuthorizedUrl( - (manager) -> AuthorizeHttpRequestsConfigurer.this.addMapping(requestMatchers, manager)); - } - - /** - * Begin registering {@link RequestMatcher}s based on the type of the servlet - * mapped to {@code pattern}. Each registered request matcher will additionally - * check {@link HttpServletMapping#getPattern} against the provided - * {@code pattern}. - * - *

- * If the corresponding servlet is of type {@link DispatcherServlet}, then use a - * {@link AuthorizationManagerServletRequestMatcherRegistry} that registers - * {@link MvcRequestMatcher}s. - * - *

- * Otherwise, use a configurer that registers {@link AntPathRequestMatcher}s. - * - *

- * When doing a path-based pattern, like `/path/*`, registered URIs should leave - * out the matching path. For example, if the target URI is `/path/resource/3`, - * then the configuration should look like this: - * .forServletPattern("/path/*", (path) -> path - * .requestMatchers("/resource/3").hasAuthority(...) - * ) - * - * - *

- * Or, if the pattern is `/path/subpath/*`, and the URI is - * `/path/subpath/resource/3`, then the configuration should look like this: - * - * .forServletPattern("/path/subpath/*", (path) -> path - * .requestMatchers("/resource/3").hasAuthority(...) - * ) - * - * - *

- * For all other patterns, please supply the URI in absolute terms. For example, - * if the target URI is `/js/**` and it matches to the default servlet, then the - * configuration should look like this: - * .forServletPattern("/", (root) -> root - * .requestMatchers("/js/**").hasAuthority(...) - * ) - * - * - *

- * Or, if the target URI is `/views/**`, and it matches to a `*.jsp` extension - * servlet, then the configuration should look like this: - * .forServletPattern("*.jsp", (jsp) -> jsp - * .requestMatchers("/views/**").hasAuthority(...) - * ) - * - * @param customizer a customizer that uses a - * {@link AuthorizationManagerServletRequestMatcherRegistry} for URIs mapped to - * the provided servlet - * @return an {@link AuthorizationManagerServletRequestMatcherRegistry} for - * further configurations - * @since 6.2 - */ - public AuthorizationManagerRequestMatcherRegistry forServletPattern(String pattern, - Customizer customizer) { - ApplicationContext context = getApplicationContext(); - RequestMatcherBuilder builder = RequestMatcherBuilders.createForServletPattern(context, pattern); - AuthorizationManagerServletRequestMatcherRegistry registry = new AuthorizationManagerServletRequestMatcherRegistry( - builder); - customizer.customize(registry); - this.servletPattern.put(pattern, registry); - return this; + return new AuthorizedUrl(requestMatchers); } /** @@ -334,265 +237,6 @@ public final class AuthorizeHttpRequestsConfigurer - * This class is designed primarily for use with the {@link HttpSecurity} DSL. For - * that reason, please use {@link HttpSecurity#authorizeHttpRequests} instead as - * it exposes this class fluently alongside related DSL configurations. - * - *

- * NOTE: In many cases, which kind of request matcher is needed is apparent by the - * servlet configuration, and so you should generally use the methods found in - * {@link AbstractRequestMatcherRegistry} instead of this these. Use this class - * when you want or need to indicate which request matcher URIs belong to which - * servlet. - * - *

- * In all cases, though, you may arrange your request matchers by servlet pattern - * with the {@link AuthorizationManagerRequestMatcherRegistry#forServletPattern} - * method in the {@link HttpSecurity#authorizeHttpRequests} DSL. - * - *

- * Consider, for example, the circumstance where you have Spring MVC configured - * and also Spring Boot H2 Console. Spring MVC registers a servlet of type - * {@link DispatcherServlet} as the default servlet and Spring Boot registers a - * servlet of its own as well at `/h2-console/*`. - * - *

- * Such might have a configuration like this in Spring Security: - * http - * .authorizeHttpRequests((authorize) -> authorize - * .requestMatchers("/js/**", "/css/**").permitAll() - * .requestMatchers("/my/controller/**").hasAuthority("CONTROLLER") - * .requestMatchers("/h2-console/**").hasAuthority("H2") - * ) - * // ... - * - * - *

- * Spring Security by default addresses the above configuration on its own. - * - *

- * However, consider the same situation, but where {@link DispatcherServlet} is - * mapped to a path like `/mvc/*`. In this case, the above configuration is - * ambiguous, and you should use this class to clarify the rest of each MVC URI - * like so: - * http - * .authorizeHttpRequests((authorize) -> authorize - * .forServletPattern("/", (root) -> root - * .requestMatchers("/js/**", "/css/**").permitAll() - * ) - * .forServletPattern("/mvc/*", (mvc) -> mvc - * .requestMatchers("/my/controller/**").hasAuthority("CONTROLLER") - * ) - * .forServletPattern("/h2-console/*", (h2) -> h2 - * .anyRequest().hasAuthority("OTHER") - * ) - * ) - * // ... - * - * - *

- * In the above configuration, it's now clear to Spring Security that the - * following matchers map to these corresponding URIs: - * - *

    - *
  • <default> + `/js/**` ==> `/js/**`
  • - *
  • <default> + `/css/**` ==> `/css/**`
  • - *
  • `/mvc` + `/my/controller/**` ==> - * `/mvc/my/controller/**`
  • - *
  • `/h2-console` + <any request> ==> - * `/h2-console/**`
  • - *
- * - * @author Josh Cummings - * @since 6.2 - * @see AbstractRequestMatcherRegistry - * @see AuthorizeHttpRequestsConfigurer - */ - public final class AuthorizationManagerServletRequestMatcherRegistry - extends AbstractRequestMatcherBuilderRegistry { - - private final RequestMatcherDelegatingAuthorizationManager.Builder managerBuilder = RequestMatcherDelegatingAuthorizationManager - .builder(); - - private List unmappedMatchers; - - AuthorizationManagerServletRequestMatcherRegistry(RequestMatcherBuilder builder) { - super(AuthorizationManagerRequestMatcherRegistry.this.getApplicationContext(), builder); - } - - AuthorizationManager authorizationManager() { - Assert.state(this.unmappedMatchers == null, - () -> "An incomplete mapping was found for " + this.unmappedMatchers - + ". Try completing it with something like requestUrls()..hasRole('USER')"); - AuthorizationManager request = this.managerBuilder.build(); - return (authentication, context) -> request.check(authentication, context.getRequest()); - } - - @Override - protected ServletAuthorizedUrl chainRequestMatchers(List requestMatchers) { - this.unmappedMatchers = requestMatchers; - return new ServletAuthorizedUrl((manager) -> addMapping(requestMatchers, manager)); - } - - private AuthorizationManagerServletRequestMatcherRegistry addMapping(List matchers, - AuthorizationManager manager) { - this.unmappedMatchers = null; - for (RequestMatcher matcher : matchers) { - this.managerBuilder.add(matcher, manager); - } - return this; - } - - } - - /** - * An object that allows configuring the {@link AuthorizationManager} for - * {@link RequestMatcher}s. - * - * @author Josh Cummings - * @since 6.2 - */ - public final class ServletAuthorizedUrl { - - private final Function, AuthorizationManagerServletRequestMatcherRegistry> registrar; - - ServletAuthorizedUrl( - Function, AuthorizationManagerServletRequestMatcherRegistry> registrar) { - this.registrar = registrar; - } - - /** - * Specify that URLs are allowed by anyone. - * @return the {@link AuthorizationManagerRequestMatcherRegistry} for further - * customizations - */ - public AuthorizationManagerServletRequestMatcherRegistry permitAll() { - return access(permitAllAuthorizationManager); - } - - /** - * Specify that URLs are not allowed by anyone. - * @return the {@link AuthorizationManagerRequestMatcherRegistry} for further - * customizations - */ - public AuthorizationManagerServletRequestMatcherRegistry denyAll() { - return access((a, o) -> new AuthorizationDecision(false)); - } - - /** - * Specifies a user requires a role. - * @param role the role that should be required which is prepended with ROLE_ - * automatically (i.e. USER, ADMIN, etc). It should not start with ROLE_ - * @return {@link AuthorizationManagerRequestMatcherRegistry} for further - * customizations - */ - public AuthorizationManagerServletRequestMatcherRegistry hasRole(String role) { - return access(withRoleHierarchy(AuthorityAuthorizationManager - .hasAnyRole(AuthorizeHttpRequestsConfigurer.this.rolePrefix, new String[] { role }))); - } - - /** - * Specifies that a user requires one of many roles. - * @param roles the roles that the user should have at least one of (i.e. - * ADMIN, USER, etc). Each role should not start with ROLE_ since it is - * automatically prepended already - * @return the {@link AuthorizationManagerRequestMatcherRegistry} for further - * customizations - */ - public AuthorizationManagerServletRequestMatcherRegistry hasAnyRole(String... roles) { - return access(withRoleHierarchy(AuthorityAuthorizationManager - .hasAnyRole(AuthorizeHttpRequestsConfigurer.this.rolePrefix, roles))); - } - - /** - * Specifies a user requires an authority. - * @param authority the authority that should be required - * @return the {@link AuthorizationManagerRequestMatcherRegistry} for further - * customizations - */ - public AuthorizationManagerServletRequestMatcherRegistry hasAuthority(String authority) { - return access(withRoleHierarchy(AuthorityAuthorizationManager.hasAuthority(authority))); - } - - /** - * Specifies that a user requires one of many authorities. - * @param authorities the authorities that the user should have at least one - * of (i.e. ROLE_USER, ROLE_ADMIN, etc) - * @return the {@link AuthorizationManagerRequestMatcherRegistry} for further - * customizations - */ - public AuthorizationManagerServletRequestMatcherRegistry hasAnyAuthority(String... authorities) { - return access(withRoleHierarchy(AuthorityAuthorizationManager.hasAnyAuthority(authorities))); - } - - private AuthorityAuthorizationManager withRoleHierarchy( - AuthorityAuthorizationManager manager) { - manager.setRoleHierarchy(AuthorizeHttpRequestsConfigurer.this.roleHierarchy.get()); - return manager; - } - - /** - * Specify that URLs are allowed by any authenticated user. - * @return the {@link AuthorizationManagerRequestMatcherRegistry} for further - * customizations - */ - public AuthorizationManagerServletRequestMatcherRegistry authenticated() { - return access(AuthenticatedAuthorizationManager.authenticated()); - } - - /** - * Specify that URLs are allowed by users who have authenticated and were not - * "remembered". - * @return the {@link AuthorizationManagerRequestMatcherRegistry} for further - * customization - * @see RememberMeConfigurer - */ - public AuthorizationManagerServletRequestMatcherRegistry fullyAuthenticated() { - return access(AuthenticatedAuthorizationManager.fullyAuthenticated()); - } - - /** - * Specify that URLs are allowed by users that have been remembered. - * @return the {@link AuthorizationManagerRequestMatcherRegistry} for further - * customization - * @since 5.8 - * @see RememberMeConfigurer - */ - public AuthorizationManagerServletRequestMatcherRegistry rememberMe() { - return access(AuthenticatedAuthorizationManager.rememberMe()); - } - - /** - * Specify that URLs are allowed by anonymous users. - * @return the {@link AuthorizationManagerRequestMatcherRegistry} for further - * customization - * @since 5.8 - */ - public AuthorizationManagerServletRequestMatcherRegistry anonymous() { - return access(AuthenticatedAuthorizationManager.anonymous()); - } - - /** - * Allows specifying a custom {@link AuthorizationManager}. - * @param manager the {@link AuthorizationManager} to use - * @return the {@link AuthorizationManagerRequestMatcherRegistry} for further - * customizations - */ - public AuthorizationManagerServletRequestMatcherRegistry access( - AuthorizationManager manager) { - Assert.notNull(manager, "manager cannot be null"); - return this.registrar.apply(manager); - } - - } - } /** @@ -603,11 +247,18 @@ public final class AuthorizeHttpRequestsConfigurer, AuthorizationManagerRequestMatcherRegistry> registrar; + private final List matchers; + + /** + * Creates an instance. + * @param matchers the {@link RequestMatcher} instances to map + */ + AuthorizedUrl(List matchers) { + this.matchers = matchers; + } - AuthorizedUrl( - Function, AuthorizationManagerRequestMatcherRegistry> registrar) { - this.registrar = registrar; + protected List getMatchers() { + return this.matchers; } /** @@ -731,7 +382,7 @@ public final class AuthorizeHttpRequestsConfigurer manager) { Assert.notNull(manager, "manager cannot be null"); - return this.registrar.apply(manager); + return AuthorizeHttpRequestsConfigurer.this.addMapping(this.matchers, manager); } } diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/DispatcherServletDelegatingRequestMatcherBuilder.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/DispatcherServletDelegatingRequestMatcherBuilder.java deleted file mode 100644 index bb300d5703..0000000000 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/DispatcherServletDelegatingRequestMatcherBuilder.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2002-2023 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.annotation.web.configurers; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.http.HttpMethod; -import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; - -final class DispatcherServletDelegatingRequestMatcherBuilder implements RequestMatcherBuilder { - - final MvcRequestMatcherBuilder mvc; - - final AntPathRequestMatcherBuilder ant; - - final ServletRegistrationCollection registrations; - - DispatcherServletDelegatingRequestMatcherBuilder(MvcRequestMatcherBuilder mvc, AntPathRequestMatcherBuilder ant, - ServletRegistrationCollection registrations) { - this.mvc = mvc; - this.ant = ant; - this.registrations = registrations; - } - - @Override - public RequestMatcher matcher(String pattern) { - MvcRequestMatcher mvc = this.mvc.matcher(pattern); - AntPathRequestMatcher ant = this.ant.matcher(pattern); - return new DispatcherServletDelegatingRequestMatcher(mvc, ant, this.registrations); - } - - @Override - public RequestMatcher matcher(HttpMethod method, String pattern) { - MvcRequestMatcher mvc = this.mvc.matcher(method, pattern); - AntPathRequestMatcher ant = this.ant.matcher(method, pattern); - return new DispatcherServletDelegatingRequestMatcher(mvc, ant, this.registrations); - } - - static final class DispatcherServletDelegatingRequestMatcher implements RequestMatcher { - - private final MvcRequestMatcher mvc; - - private final AntPathRequestMatcher ant; - - private final ServletRegistrationCollection registrations; - - private DispatcherServletDelegatingRequestMatcher(MvcRequestMatcher mvc, AntPathRequestMatcher ant, - ServletRegistrationCollection registrations) { - this.mvc = mvc; - this.ant = ant; - this.registrations = registrations; - } - - @Override - public boolean matches(HttpServletRequest request) { - String name = request.getHttpServletMapping().getServletName(); - ServletRegistrationCollection.Registration registration = this.registrations.registrationByName(name); - Assert.notNull(registration, - String.format("Could not find %s in servlet configuration %s", name, this.registrations)); - if (registration.isDispatcherServlet()) { - return this.mvc.matches(request); - } - return this.ant.matches(request); - } - - @Override - public MatchResult matcher(HttpServletRequest request) { - String name = request.getHttpServletMapping().getServletName(); - ServletRegistrationCollection.Registration registration = this.registrations.registrationByName(name); - Assert.notNull(registration, - String.format("Could not find %s in servlet configuration %s", name, this.registrations)); - if (registration.isDispatcherServlet()) { - return this.mvc.matcher(request); - } - return this.ant.matcher(request); - } - - @Override - public String toString() { - return String.format("DispatcherServlet [mvc=[%s], ant=[%s], servlet=[%s]]", this.mvc, this.ant, - this.registrations); - } - - } - -} diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/MvcRequestMatcherBuilder.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/MvcRequestMatcherBuilder.java deleted file mode 100644 index 60122c35c5..0000000000 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/MvcRequestMatcherBuilder.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2002-2023 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.annotation.web.configurers; - -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.context.ApplicationContext; -import org.springframework.http.HttpMethod; -import org.springframework.security.config.annotation.ObjectPostProcessor; -import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher; -import org.springframework.web.servlet.handler.HandlerMappingIntrospector; - -final class MvcRequestMatcherBuilder implements RequestMatcherBuilder { - - private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector"; - - private final HandlerMappingIntrospector introspector; - - private final ObjectPostProcessor objectPostProcessor; - - private final String servletPath; - - private MvcRequestMatcherBuilder(ApplicationContext context, String servletPath) { - if (!context.containsBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) { - throw new NoSuchBeanDefinitionException("A Bean named " + HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME - + " of type " + HandlerMappingIntrospector.class.getName() - + " is required to use MvcRequestMatcher. Please ensure Spring Security & Spring MVC are configured in a shared ApplicationContext."); - } - this.introspector = context.getBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME, HandlerMappingIntrospector.class); - this.objectPostProcessor = context.getBean(ObjectPostProcessor.class); - this.servletPath = servletPath; - } - - static MvcRequestMatcherBuilder absolute(ApplicationContext context) { - return new MvcRequestMatcherBuilder(context, null); - } - - static MvcRequestMatcherBuilder relativeTo(ApplicationContext context, String path) { - return new MvcRequestMatcherBuilder(context, path); - } - - @Override - public MvcRequestMatcher matcher(String pattern) { - MvcRequestMatcher matcher = new MvcRequestMatcher(this.introspector, pattern); - this.objectPostProcessor.postProcess(matcher); - if (this.servletPath != null) { - matcher.setServletPath(this.servletPath); - } - return matcher; - } - - @Override - public MvcRequestMatcher matcher(HttpMethod method, String pattern) { - MvcRequestMatcher matcher = new MvcRequestMatcher(this.introspector, pattern); - this.objectPostProcessor.postProcess(matcher); - matcher.setMethod(method); - if (this.servletPath != null) { - matcher.setServletPath(this.servletPath); - } - return matcher; - } - -} diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/RequestMatcherBuilder.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/RequestMatcherBuilder.java deleted file mode 100644 index 6b95bf8111..0000000000 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/RequestMatcherBuilder.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2002-2023 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.annotation.web.configurers; - -import java.util.ArrayList; -import java.util.List; - -import org.springframework.http.HttpMethod; -import org.springframework.security.web.util.matcher.AnyRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; - -/** - * An interface that abstracts how matchers are created - * - * @author Josh Cummings - * @since 6.2 - */ -interface RequestMatcherBuilder { - - /** - * Create a request matcher for the given pattern. - * - *

- * For example, you might do something like the following: - * builder.matcher("/controller/**") - * - * @param pattern the pattern to use, typically an Ant path - * @return a {@link RequestMatcher} that matches on the given {@code pattern} - */ - RequestMatcher matcher(String pattern); - - /** - * Create a request matcher for the given pattern. - * - *

- * For example, you might do something like the following: - * builder.matcher(HttpMethod.GET, "/controller/**") - * - * @param method the HTTP method to use - * @param pattern the pattern to use, typically an Ant path - * @return a {@link RequestMatcher} that matches on the given HTTP {@code method} and - * {@code pattern} - */ - RequestMatcher matcher(HttpMethod method, String pattern); - - /** - * Create a request matcher that matches any request - * @return a {@link RequestMatcher} that matches any request - */ - default RequestMatcher any() { - return AnyRequestMatcher.INSTANCE; - } - - /** - * Create an array request matchers, one for each of the given patterns. - * - *

- * For example, you might do something like the following: - * builder.matcher("/controller-one/**", "/controller-two/**") - * - * @param patterns the patterns to use, typically Ant paths - * @return a list of {@link RequestMatcher} that match on the given {@code pattern} - */ - default List matchers(String... patterns) { - List matchers = new ArrayList<>(); - for (String pattern : patterns) { - matchers.add(matcher(pattern)); - } - return matchers; - } - - /** - * Create an array request matchers, one for each of the given patterns. - * - *

- * For example, you might do something like the following: - * builder.matcher(HttpMethod.POST, "/controller-one/**", "/controller-two/**") - * - * @param method the HTTP method to use - * @param patterns the patterns to use, typically Ant paths - * @return a list of {@link RequestMatcher} that match on the given HTTP - * {@code method} and {@code pattern} - */ - default List matchers(HttpMethod method, String... patterns) { - List matchers = new ArrayList<>(); - for (String pattern : patterns) { - matchers.add(matcher(method, pattern)); - } - return matchers; - } - -} diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/RequestMatcherBuilders.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/RequestMatcherBuilders.java deleted file mode 100644 index f34793320e..0000000000 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/RequestMatcherBuilders.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright 2002-2023 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.annotation.web.configurers; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.context.ApplicationContext; -import org.springframework.http.HttpMethod; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.web.servlet.DispatcherServlet; - -/** - * A factory for constructing {@link RequestMatcherBuilder} instances - * - * @author Josh Cummings - * @since 6.2 - */ -final class RequestMatcherBuilders { - - private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector"; - - private static final String HANDLER_MAPPING_INTROSPECTOR = "org.springframework.web.servlet.handler.HandlerMappingIntrospector"; - - private static final boolean mvcPresent; - - static { - mvcPresent = ClassUtils.isPresent(HANDLER_MAPPING_INTROSPECTOR, RequestMatcherBuilders.class.getClassLoader()); - } - - private static final Log logger = LogFactory.getLog(RequestMatcherBuilders.class); - - private RequestMatcherBuilders() { - - } - - /** - * Create the default {@link RequestMatcherBuilder} for use by Spring Security DSLs. - * - *

- * If Spring MVC is not present on the classpath or if there is no - * {@link DispatcherServlet}, this method will return an Ant-based builder. - * - *

- * If the servlet configuration has only {@link DispatcherServlet} with a single - * mapping (for example `/` or `/path/*`), then this method will return an MVC-based - * builder. - * - *

- * If the servlet configuration maps {@link DispatcherServlet} to a path and also has - * other servlets, this will throw an exception. In that case, an application should - * instead use the {@link RequestMatcherBuilders#createForServletPattern} ideally with - * the associated - * {@link org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer} - * to create builders by servlet path. - * - *

- * Otherwise, (namely if {@link DispatcherServlet} is root), this method will return a - * builder that delegates to an Ant or Mvc builder at runtime. - * @param context the application context - * @return the appropriate {@link RequestMatcherBuilder} based on application - * configuration - */ - static RequestMatcherBuilder createDefault(ApplicationContext context) { - if (!mvcPresent) { - logger.trace("Defaulting to Ant matching since Spring MVC is not on the classpath"); - return AntPathRequestMatcherBuilder.absolute(); - } - if (!context.containsBean(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) { - logger.trace("Defaulting to Ant matching since Spring MVC is not fully configured"); - return AntPathRequestMatcherBuilder.absolute(); - } - ServletRegistrationCollection registrations = ServletRegistrationCollection.registrations(context); - if (registrations.isEmpty()) { - logger.trace("Defaulting to MVC matching since Spring MVC is on the class path and no servlet " - + "information is available"); - return AntPathRequestMatcherBuilder.absolute(); - } - ServletRegistrationCollection dispatcherServlets = registrations.dispatcherServlets(); - if (dispatcherServlets.isEmpty()) { - logger.trace("Defaulting to Ant matching since there is no DispatcherServlet configured"); - return AntPathRequestMatcherBuilder.absolute(); - } - ServletRegistrationCollection.ServletPath servletPath = registrations.deduceOneServletPath(); - if (servletPath != null) { - String message = "Defaulting to MVC matching since DispatcherServlet [%s] is the only servlet mapping"; - logger.trace(String.format(message, servletPath.path())); - return MvcRequestMatcherBuilder.relativeTo(context, servletPath.path()); - } - servletPath = dispatcherServlets.deduceOneServletPath(); - if (servletPath == null) { - logger.trace("Did not choose a default since there is more than one DispatcherServlet mapping"); - String message = String.format(""" - This method cannot decide whether these patterns are Spring MVC patterns or not - since your servlet configuration has multiple Spring MVC servlet mappings. - - For your reference, here is your servlet configuration: %s - - To address this, you need to specify the servlet path for each endpoint. - You can use .forServletPattern in conjunction with requestMatchers do to this - like so: - - @Bean - SecurityFilterChain appSecurity(HttpSecurity http) throws Exception { - http - .authorizeHttpRequests((authorize) -> authorize - .forServletPattern("/mvc-one/*", (one) -> one - .requestMatchers("/controller/**", "/endpoints/**" - )... - .forServletPattern("/mvc-two/*", (two) -> two - .requestMatchers("/other/**", "/controllers/**")... - ) - .forServletPattern("/h2-console/*", (h2) -> h2 - .requestMatchers("/**")... - ) - ) - // ... - return http.build(); - } - """, registrations); - return new ErrorRequestMatcherBuilder(message); - } - if (servletPath.path() != null) { - logger.trace("Did not choose a default since there is a non-root DispatcherServlet mapping"); - String message = String.format(""" - This method cannot decide whether these patterns are Spring MVC patterns or not - since your Spring MVC mapping is mapped to a path and you have other servlet mappings. - - For your reference, here is your servlet configuration: %s - - To address this, you need to specify the servlet path for each endpoint. - You can use .forServletPattern in conjunction with requestMatchers do to this - like so: - - @Bean - SecurityFilterChain appSecurity(HttpSecurity http) throws Exception { - http - .authorizeHttpRequests((authorize) -> authorize - .forServletPattern("/mvc/*", (mvc) -> mvc - .requestMatchers("/controller/**", "/endpoints/**")... - ) - .forServletPattern("/h2-console/*", (h2) -> h2 - .requestMatchers("/**")... - ) - ) - // ... - return http.build(); - } - """, registrations); - return new ErrorRequestMatcherBuilder(message); - } - logger.trace("Defaulting to request-time checker since DispatcherServlet is mapped to root, but there are also " - + "other servlet mappings"); - return new DispatcherServletDelegatingRequestMatcherBuilder(MvcRequestMatcherBuilder.absolute(context), - AntPathRequestMatcherBuilder.absolute(), registrations); - } - - static RequestMatcherBuilder createForServletPattern(ApplicationContext context, String pattern) { - Assert.notNull(pattern, "pattern cannot be null"); - ServletRegistrationCollection registrations = ServletRegistrationCollection.registrations(context); - ServletRegistrationCollection.Registration registration = registrations.registrationByMapping(pattern); - Assert.notNull(registration, () -> String - .format("The given pattern %s doesn't seem to match any configured servlets: %s", pattern, registrations)); - boolean isPathPattern = pattern.startsWith("/") && pattern.endsWith("/*"); - if (isPathPattern) { - String path = pattern.substring(0, pattern.length() - 2); - return (registration.isDispatcherServlet()) ? MvcRequestMatcherBuilder.relativeTo(context, path) - : AntPathRequestMatcherBuilder.relativeTo(path); - } - return (registration.isDispatcherServlet()) ? MvcRequestMatcherBuilder.absolute(context) - : AntPathRequestMatcherBuilder.absolute(); - } - - private static class ErrorRequestMatcherBuilder implements RequestMatcherBuilder { - - private final String errorMessage; - - ErrorRequestMatcherBuilder(String errorMessage) { - this.errorMessage = errorMessage; - } - - @Override - public RequestMatcher matcher(String pattern) { - throw new IllegalArgumentException(this.errorMessage); - } - - @Override - public RequestMatcher matcher(HttpMethod method, String pattern) { - throw new IllegalArgumentException(this.errorMessage); - } - - @Override - public RequestMatcher any() { - throw new IllegalArgumentException(this.errorMessage); - } - - } - -} diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ServletPatternRequestMatcher.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ServletPatternRequestMatcher.java deleted file mode 100644 index 989429a050..0000000000 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ServletPatternRequestMatcher.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2002-2023 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.annotation.web.configurers; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; - -final class ServletPatternRequestMatcher implements RequestMatcher { - - final String pattern; - - ServletPatternRequestMatcher(String pattern) { - Assert.notNull(pattern, "pattern cannot be null"); - this.pattern = pattern; - } - - @Override - public boolean matches(HttpServletRequest request) { - return this.pattern.equals(request.getHttpServletMapping().getPattern()); - } - - @Override - public String toString() { - return String.format("ServletPattern [pattern='%s']", this.pattern); - } - -} diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ServletRegistrationCollection.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ServletRegistrationCollection.java deleted file mode 100644 index 560050c0ca..0000000000 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/ServletRegistrationCollection.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright 2002-2023 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.annotation.web.configurers; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import jakarta.servlet.ServletContext; -import jakarta.servlet.ServletRegistration; - -import org.springframework.context.ApplicationContext; -import org.springframework.util.ClassUtils; -import org.springframework.util.CollectionUtils; -import org.springframework.web.context.WebApplicationContext; - -final class ServletRegistrationCollection { - - private List registrations; - - private ServletRegistrationCollection() { - this.registrations = Collections.emptyList(); - } - - private ServletRegistrationCollection(List registrations) { - this.registrations = registrations; - } - - static ServletRegistrationCollection registrations(ApplicationContext context) { - if (!(context instanceof WebApplicationContext web)) { - return new ServletRegistrationCollection(); - } - ServletContext servletContext = web.getServletContext(); - if (servletContext == null) { - return new ServletRegistrationCollection(); - } - Map registrations = servletContext.getServletRegistrations(); - if (registrations == null) { - return new ServletRegistrationCollection(); - } - List filtered = new ArrayList<>(); - for (ServletRegistration registration : registrations.values()) { - Collection mappings = registration.getMappings(); - if (!CollectionUtils.isEmpty(mappings)) { - filtered.add(new Registration(registration)); - } - } - return new ServletRegistrationCollection(filtered); - } - - boolean isEmpty() { - return this.registrations.isEmpty(); - } - - Registration registrationByName(String name) { - for (Registration registration : this.registrations) { - if (registration.registration().getName().equals(name)) { - return registration; - } - } - return null; - } - - Registration registrationByMapping(String target) { - for (Registration registration : this.registrations) { - for (String mapping : registration.registration().getMappings()) { - if (target.equals(mapping)) { - return registration; - } - } - } - return null; - } - - ServletRegistrationCollection dispatcherServlets() { - List dispatcherServlets = new ArrayList<>(); - for (Registration registration : this.registrations) { - if (registration.isDispatcherServlet()) { - dispatcherServlets.add(registration); - } - } - return new ServletRegistrationCollection(dispatcherServlets); - } - - ServletPath deduceOneServletPath() { - if (this.registrations.size() > 1) { - return null; - } - ServletRegistration registration = this.registrations.iterator().next().registration(); - if (registration.getMappings().size() > 1) { - return null; - } - String mapping = registration.getMappings().iterator().next(); - if ("/".equals(mapping)) { - return new ServletPath(); - } - if (mapping.endsWith("/*")) { - return new ServletPath(mapping.substring(0, mapping.length() - 2)); - } - return null; - } - - @Override - public String toString() { - Map> mappings = new LinkedHashMap<>(); - for (Registration registration : this.registrations) { - mappings.put(registration.registration().getClassName(), registration.registration().getMappings()); - } - return mappings.toString(); - } - - record Registration(ServletRegistration registration) { - boolean isDispatcherServlet() { - Class dispatcherServlet = ClassUtils - .resolveClassName("org.springframework.web.servlet.DispatcherServlet", null); - try { - Class clazz = Class.forName(this.registration.getClassName()); - if (dispatcherServlet.isAssignableFrom(clazz)) { - return true; - } - } - catch (ClassNotFoundException ex) { - return false; - } - return false; - } - } - - record ServletPath(String path) { - ServletPath() { - this(null); - } - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryNoMvcTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryNoMvcTests.java index ec39518324..4d7c9a18ff 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryNoMvcTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryNoMvcTests.java @@ -25,12 +25,8 @@ import org.springframework.http.HttpMethod; import org.springframework.security.test.support.ClassPathExclusions; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.web.context.WebApplicationContext; 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.mock; /** * Tests for {@link AbstractRequestMatcherRegistry} with no Spring MVC in the classpath @@ -45,9 +41,6 @@ public class AbstractRequestMatcherRegistryNoMvcTests { @BeforeEach public void setUp() { this.matcherRegistry = new TestRequestMatcherRegistry(); - WebApplicationContext context = mock(WebApplicationContext.class); - given(context.getBeanNamesForType((Class) any())).willReturn(new String[0]); - this.matcherRegistry.setApplicationContext(context); } @Test diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java index 4971bc814e..f648954617 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java @@ -173,6 +173,12 @@ public class AbstractRequestMatcherRegistryTests { assertThat(requestMatchers).isNotEmpty(); assertThat(requestMatchers).hasSize(1); assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class); + servletContext.addServlet("servletOne", Servlet.class); + servletContext.addServlet("servletTwo", Servlet.class); + requestMatchers = this.matcherRegistry.requestMatchers("/**"); + assertThat(requestMatchers).isNotEmpty(); + assertThat(requestMatchers).hasSize(1); + assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class); } @Test diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AbstractRequestMatcherBuilderRegistryTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AbstractRequestMatcherBuilderRegistryTests.java deleted file mode 100644 index ef29d6a86c..0000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AbstractRequestMatcherBuilderRegistryTests.java +++ /dev/null @@ -1,349 +0,0 @@ -/* - * Copyright 2002-2023 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.annotation.web.configurers; - -import java.util.List; -import java.util.function.Consumer; - -import jakarta.servlet.Servlet; -import jakarta.servlet.ServletContext; -import org.assertj.core.api.AbstractObjectAssert; -import org.assertj.core.api.ObjectAssert; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.context.ApplicationContext; -import org.springframework.http.HttpMethod; -import org.springframework.security.config.MockServletContext; -import org.springframework.security.config.annotation.ObjectPostProcessor; -import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher; -import org.springframework.security.web.util.matcher.AndRequestMatcher; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.web.context.support.GenericWebApplicationContext; -import org.springframework.web.servlet.DispatcherServlet; -import org.springframework.web.servlet.handler.HandlerMappingIntrospector; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link AbstractRequestMatcherBuilderRegistry} - */ -class AbstractRequestMatcherBuilderRegistryTests { - - @Test - void defaultServletMatchersWhenDefaultDispatcherServletThenMvc() { - MockServletContext servletContext = MockServletContext.mvc(); - List matchers = defaultServlet(servletContext).requestMatchers("/mvc").matchers; - assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class); - assertThatMvc(matchers).servletPath().isNull(); - assertThatMvc(matchers).pattern().isEqualTo("/mvc"); - assertThatMvc(matchers).method().isNull(); - } - - @Test - void defaultServletHttpMethodMatchersWhenDefaultDispatcherServletThenMvc() { - MockServletContext servletContext = MockServletContext.mvc(); - List matchers = defaultServlet(servletContext).requestMatchers(HttpMethod.GET, "/mvc").matchers; - assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class); - assertThatMvc(matchers).servletPath().isNull(); - assertThatMvc(matchers).pattern().isEqualTo("/mvc"); - assertThatMvc(matchers).method().isEqualTo(HttpMethod.GET); - } - - @Test - void servletMatchersWhenPathDispatcherServletThenMvc() { - MockServletContext servletContext = new MockServletContext(); - servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*"); - List matchers = servletPattern(servletContext, "/mvc/*") - .requestMatchers("/controller").matchers; - assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class); - assertThatMvc(matchers).servletPath().isEqualTo("/mvc"); - assertThatMvc(matchers).pattern().isEqualTo("/controller"); - } - - @Test - void servletMatchersWhenAlsoExtraServletContainerMappingsThenMvc() { - MockServletContext servletContext = new MockServletContext(); - servletContext.addServlet("default", Servlet.class); - servletContext.addServlet("jspServlet", Servlet.class).addMapping("*.jsp", "*.jspx"); - servletContext.addServlet("facesServlet", Servlet.class).addMapping("/faces/", "*.jsf", "*.faces", "*.xhtml"); - servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*"); - List matchers = servletPattern(servletContext, "/mvc/*") - .requestMatchers("/controller").matchers; - assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class); - assertThatMvc(matchers).servletPath().isEqualTo("/mvc"); - assertThatMvc(matchers).pattern().isEqualTo("/controller"); - } - - @Test - void defaultServletMatchersWhenOnlyDefaultServletThenAnt() { - MockServletContext servletContext = new MockServletContext(); - servletContext.addServlet("default", Servlet.class).addMapping("/"); - List matchers = defaultServlet(servletContext).requestMatchers("/controller").matchers; - assertThat(matchers).hasSize(1).hasOnlyElementsOfType(AntPathRequestMatcher.class); - assertThatAnt(matchers).pattern().isEqualTo("/controller"); - } - - @Test - void defaultDispatcherServletMatchersWhenNoHandlerMappingIntrospectorThenException() { - MockServletContext servletContext = MockServletContext.mvc(); - assertThatExceptionOfType(NoSuchBeanDefinitionException.class) - .isThrownBy(() -> defaultServlet(servletContext, (context) -> { - })); - } - - @Test - void dispatcherServletMatchersWhenNoHandlerMappingIntrospectorThenException() { - MockServletContext servletContext = new MockServletContext(); - servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*"); - assertThatExceptionOfType(NoSuchBeanDefinitionException.class) - .isThrownBy(() -> servletPattern(servletContext, (context) -> { - }, "/mvc/*")); - } - - @Test - void matchersWhenNoDispatchServletThenAnt() { - MockServletContext servletContext = new MockServletContext(); - servletContext.addServlet("default", Servlet.class).addMapping("/"); - servletContext.addServlet("messageDispatcherServlet", Servlet.class).addMapping("/services/*"); - List matchers = defaultServlet(servletContext).requestMatchers("/services/endpoint").matchers; - assertThat(matchers).hasSize(1).hasOnlyElementsOfType(AntPathRequestMatcher.class); - assertThatAnt(matchers).pattern().isEqualTo("/services/endpoint"); - } - - @Test - void servletMatchersWhenMixedServletsThenDeterminesByServletPath() { - MockServletContext servletContext = MockServletContext.mvc(); - servletContext.addServlet("messageDispatcherServlet", Servlet.class).addMapping("/services/*"); - List matchers = servletPattern(servletContext, "/services/*") - .requestMatchers("/endpoint").matchers; - assertThat(matchers).hasSize(1).hasOnlyElementsOfType(AntPathRequestMatcher.class); - assertThatAnt(matchers).pattern().isEqualTo("/services/endpoint"); - matchers = defaultServlet(servletContext).requestMatchers("/controller").matchers; - assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class); - assertThatMvc(matchers).servletPath().isNull(); - assertThatMvc(matchers).pattern().isEqualTo("/controller"); - } - - @Test - void servletMatchersWhenDispatcherServletNotDefaultThenDeterminesByServletPath() { - MockServletContext servletContext = new MockServletContext(); - servletContext.addServlet("default", Servlet.class).addMapping("/"); - servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*"); - List matchers = servletPattern(servletContext, "/mvc/*") - .requestMatchers("/controller").matchers; - assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class); - assertThatMvc(matchers).servletPath().isEqualTo("/mvc"); - assertThatMvc(matchers).pattern().isEqualTo("/controller"); - matchers = defaultServlet(servletContext).requestMatchers("/endpoint").matchers; - assertThat(matchers).hasSize(1).hasOnlyElementsOfType(AntPathRequestMatcher.class); - assertThatAnt(matchers).pattern().isEqualTo("/endpoint"); - } - - @Test - void servletHttpMatchersWhenDispatcherServletNotDefaultThenDeterminesByServletPath() { - MockServletContext servletContext = new MockServletContext(); - servletContext.addServlet("default", Servlet.class).addMapping("/"); - servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*"); - List matchers = servletPattern(servletContext, "/mvc/*").requestMatchers(HttpMethod.GET, - "/controller").matchers; - assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class); - assertThatMvc(matchers).method().isEqualTo(HttpMethod.GET); - assertThatMvc(matchers).servletPath().isEqualTo("/mvc"); - assertThatMvc(matchers).pattern().isEqualTo("/controller"); - matchers = defaultServlet(servletContext).requestMatchers(HttpMethod.GET, "/endpoint").matchers; - assertThat(matchers).hasSize(1).hasOnlyElementsOfType(AntPathRequestMatcher.class); - assertThatAnt(matchers).method().isEqualTo(HttpMethod.GET); - assertThatAnt(matchers).pattern().isEqualTo("/endpoint"); - } - - @Test - void servletMatchersWhenTwoDispatcherServletsThenDeterminesByServletPath() { - MockServletContext servletContext = MockServletContext.mvc(); - servletContext.addServlet("two", DispatcherServlet.class).addMapping("/other/*"); - List matchers = defaultServlet(servletContext).requestMatchers("/controller").matchers; - assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class); - assertThatMvc(matchers).servletPath().isNull(); - assertThatMvc(matchers).pattern().isEqualTo("/controller"); - matchers = servletPattern(servletContext, "/other/*").requestMatchers("/endpoint").matchers; - assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class); - assertThatMvc(matchers).servletPath().isEqualTo("/other"); - assertThatMvc(matchers).pattern().isEqualTo("/endpoint"); - } - - @Test - void servletMatchersWhenMoreThanOneMappingThenDeterminesByServletPath() { - MockServletContext servletContext = new MockServletContext(); - servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/", "/two/*"); - List matchers = defaultServlet(servletContext).requestMatchers("/controller").matchers; - assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class); - assertThatMvc(matchers).servletPath().isNull(); - assertThatMvc(matchers).pattern().isEqualTo("/controller"); - matchers = servletPattern(servletContext, "/two/*").requestMatchers("/endpoint").matchers; - assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class); - assertThatMvc(matchers).servletPath().isEqualTo("/two"); - assertThatMvc(matchers).pattern().isEqualTo("/endpoint"); - } - - @Test - void servletMatchersWhenMoreThanOneMappingAndDefaultServletsThenDeterminesByServletPath() { - MockServletContext servletContext = new MockServletContext(); - servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/", "/two/*"); - servletContext.addServlet("jspServlet", Servlet.class).addMapping("*.jsp", "*.jspx"); - List matchers = defaultServlet(servletContext).requestMatchers("/controller").matchers; - assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class); - assertThatMvc(matchers).servletPath().isNull(); - assertThatMvc(matchers).pattern().isEqualTo("/controller"); - matchers = servletPattern(servletContext, "/two/*").requestMatchers("/endpoint").matchers; - assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class); - assertThatMvc(matchers).servletPath().isEqualTo("/two"); - assertThatMvc(matchers).pattern().isEqualTo("/endpoint"); - } - - @Test - void defaultServletWhenDispatcherServletThenMvc() { - MockServletContext servletContext = MockServletContext.mvc(); - servletContext.addServlet("messageDispatcherServlet", Servlet.class).addMapping("/services/*"); - List matchers = defaultServlet(servletContext).requestMatchers("/controller").matchers; - assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class); - assertThatMvc(matchers).servletPath().isNull(); - assertThatMvc(matchers).pattern().isEqualTo("/controller"); - matchers = servletPattern(servletContext, "/services/*").requestMatchers("/endpoint").matchers; - assertThat(matchers).hasSize(1).hasOnlyElementsOfType(AntPathRequestMatcher.class); - assertThatAnt(matchers).pattern().isEqualTo("/services/endpoint"); - } - - @Test - void defaultServletWhenNoDefaultServletThenException() { - MockServletContext servletContext = new MockServletContext(); - servletContext.addServlet("messageDispatcherServlet", Servlet.class).addMapping("/services/*"); - assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> defaultServlet(servletContext)); - } - - @Test - void servletPathWhenNoMatchingServletThenException() { - MockServletContext servletContext = MockServletContext.mvc(); - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> servletPattern(servletContext, "/wrong/*")); - } - - TestServletRequestMatcherRegistry defaultServlet(ServletContext servletContext) { - return servletPattern(servletContext, "/"); - } - - TestServletRequestMatcherRegistry defaultServlet(ServletContext servletContext, - Consumer consumer) { - return servletPattern(servletContext, consumer, "/"); - } - - TestServletRequestMatcherRegistry servletPattern(ServletContext servletContext, String pattern) { - return servletPattern(servletContext, (context) -> { - context.registerBean("mvcHandlerMappingIntrospector", HandlerMappingIntrospector.class); - context.registerBean(ObjectPostProcessor.class, () -> mock(ObjectPostProcessor.class)); - }, pattern); - } - - TestServletRequestMatcherRegistry servletPattern(ServletContext servletContext, - Consumer consumer, String pattern) { - GenericWebApplicationContext context = new GenericWebApplicationContext(servletContext); - consumer.accept(context); - context.refresh(); - return new TestServletRequestMatcherRegistry(context, pattern); - } - - static MvcRequestMatcherAssert assertThatMvc(List matchers) { - RequestMatcher matcher = matchers.get(0); - if (matcher instanceof AndRequestMatcher matching) { - List and = (List) ReflectionTestUtils.getField(matching, "requestMatchers"); - assertThat(and).hasSize(2); - assertThat(and.get(1)).isInstanceOf(MvcRequestMatcher.class); - return new MvcRequestMatcherAssert((MvcRequestMatcher) and.get(1)); - } - assertThat(matcher).isInstanceOf(MvcRequestMatcher.class); - return new MvcRequestMatcherAssert((MvcRequestMatcher) matcher); - } - - static AntPathRequestMatcherAssert assertThatAnt(List matchers) { - RequestMatcher matcher = matchers.get(0); - if (matcher instanceof AndRequestMatcher matching) { - List and = (List) ReflectionTestUtils.getField(matching, "requestMatchers"); - assertThat(and).hasSize(2); - assertThat(and.get(1)).isInstanceOf(AntPathRequestMatcher.class); - return new AntPathRequestMatcherAssert((AntPathRequestMatcher) and.get(1)); - } - assertThat(matcher).isInstanceOf(AntPathRequestMatcher.class); - return new AntPathRequestMatcherAssert((AntPathRequestMatcher) matcher); - } - - static final class TestServletRequestMatcherRegistry - extends AbstractRequestMatcherBuilderRegistry { - - List matchers; - - TestServletRequestMatcherRegistry(ApplicationContext context, String pattern) { - super(context, RequestMatcherBuilders.createForServletPattern(context, pattern)); - } - - @Override - protected TestServletRequestMatcherRegistry chainRequestMatchers(List requestMatchers) { - this.matchers = requestMatchers; - return this; - } - - } - - static final class MvcRequestMatcherAssert extends ObjectAssert { - - private MvcRequestMatcherAssert(MvcRequestMatcher matcher) { - super(matcher); - } - - AbstractObjectAssert servletPath() { - return extracting("servletPath"); - } - - AbstractObjectAssert pattern() { - return extracting("pattern"); - } - - AbstractObjectAssert method() { - return extracting("method"); - } - - } - - static final class AntPathRequestMatcherAssert extends ObjectAssert { - - private AntPathRequestMatcherAssert(AntPathRequestMatcher matcher) { - super(matcher); - } - - AbstractObjectAssert pattern() { - return extracting("pattern"); - } - - AbstractObjectAssert method() { - return extracting("httpMethod"); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java index 19d5de6f24..c2d99042b0 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java @@ -18,7 +18,6 @@ package org.springframework.security.config.annotation.web.configurers; import java.util.function.Supplier; -import jakarta.servlet.Servlet; import jakarta.servlet.http.HttpServletRequest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -27,7 +26,6 @@ import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpMethod; import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; import org.springframework.security.authentication.RememberMeAuthenticationToken; @@ -35,8 +33,6 @@ import org.springframework.security.authentication.TestAuthentication; import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.AuthorizationEventPublisher; import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.config.MockServletContext; -import org.springframework.security.config.TestMockHttpServletMappings; import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -62,7 +58,6 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.handler.HandlerMappingIntrospector; @@ -76,7 +71,6 @@ import static org.springframework.security.test.web.servlet.request.SecurityMock import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.head; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -127,7 +121,7 @@ public class AuthorizeHttpRequestsConfigurerTests { public void configureWhenMvcMatcherAfterAnyRequestThenException() { assertThatExceptionOfType(BeanCreationException.class) .isThrownBy(() -> this.spring.register(AfterAnyRequestConfig.class).autowire()) - .withMessageContaining("Can't configure requestMatchers after anyRequest"); + .withMessageContaining("Can't configure mvcMatchers after anyRequest"); } @Test @@ -368,7 +362,7 @@ public class AuthorizeHttpRequestsConfigurerTests { @Test public void getWhenServletPathRoleAdminConfiguredAndRoleIsUserThenRespondsWithForbidden() throws Exception { - this.spring.register(MvcServletPathConfig.class, BasicController.class).autowire(); + this.spring.register(ServletPathConfig.class, BasicController.class).autowire(); // @formatter:off MockHttpServletRequestBuilder requestWithUser = get("/spring/") .servletPath("/spring") @@ -381,7 +375,7 @@ public class AuthorizeHttpRequestsConfigurerTests { @Test public void getWhenServletPathRoleAdminConfiguredAndRoleIsUserAndWithoutServletPathThenRespondsWithForbidden() throws Exception { - this.spring.register(MvcServletPathConfig.class, BasicController.class).autowire(); + this.spring.register(ServletPathConfig.class, BasicController.class).autowire(); // @formatter:off MockHttpServletRequestBuilder requestWithUser = get("/") .with(user("user") @@ -392,7 +386,7 @@ public class AuthorizeHttpRequestsConfigurerTests { @Test public void getWhenServletPathRoleAdminConfiguredAndRoleIsAdminThenRespondsWithOk() throws Exception { - this.spring.register(MvcServletPathConfig.class, BasicController.class).autowire(); + this.spring.register(ServletPathConfig.class, BasicController.class).autowire(); // @formatter:off MockHttpServletRequestBuilder requestWithAdmin = get("/spring/") .servletPath("/spring") @@ -602,200 +596,6 @@ public class AuthorizeHttpRequestsConfigurerTests { this.mvc.perform(requestWithUser).andExpect(status().isForbidden()); } - @Test - public void configureWhenNoDispatcherServletThenSucceeds() throws Exception { - MockServletContext servletContext = new MockServletContext(); - servletContext.addServlet("default", Servlet.class).addMapping("/"); - this.spring.register(AuthorizeHttpRequestsConfig.class) - .postProcessor((context) -> context.setServletContext(servletContext)) - .autowire(); - this.mvc.perform(get("/path")).andExpect(status().isNotFound()); - } - - @Test - public void configureWhenOnlyDispatcherServletThenSucceeds() throws Exception { - MockServletContext servletContext = new MockServletContext(); - servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*"); - this.spring.register(AuthorizeHttpRequestsConfig.class) - .postProcessor((context) -> context.setServletContext(servletContext)) - .autowire(); - this.mvc.perform(get("/mvc/path").servletPath("/mvc")).andExpect(status().isNotFound()); - this.mvc.perform(get("/mvc")).andExpect(status().isUnauthorized()); - } - - @Test - public void configureWhenMultipleServletsThenSucceeds() throws Exception { - MockServletContext servletContext = MockServletContext.mvc(); - servletContext.addServlet("path", Servlet.class).addMapping("/path/*"); - this.spring.register(AuthorizeHttpRequestsConfig.class) - .postProcessor((context) -> context.setServletContext(servletContext)) - .autowire(); - this.mvc.perform(get("/path").with(servletPath("/path"))).andExpect(status().isNotFound()); - } - - @Test - public void configureWhenAmbiguousServletsThenWiringException() { - MockServletContext servletContext = new MockServletContext(); - servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*"); - servletContext.addServlet("path", Servlet.class).addMapping("/path/*"); - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.register(AuthorizeHttpRequestsConfig.class) - .postProcessor((context) -> context.setServletContext(servletContext)) - .autowire()); - } - - @Test - void defaultServletMatchersWhenDefaultServletThenPermits() throws Exception { - this.spring.register(DefaultServletConfig.class) - .postProcessor((context) -> context.setServletContext(MockServletContext.mvc())) - .autowire(); - this.mvc.perform(get("/path/path").with(defaultServlet())).andExpect(status().isNotFound()); - this.mvc.perform(get("/path/path").with(servletPath("/path"))).andExpect(status().isUnauthorized()); - } - - @Test - void defaultServletHttpMethodMatchersWhenDefaultServletThenPermits() throws Exception { - this.spring.register(DefaultServletConfig.class) - .postProcessor((context) -> context.setServletContext(MockServletContext.mvc())) - .autowire(); - this.mvc.perform(get("/path/method").with(defaultServlet())).andExpect(status().isNotFound()); - this.mvc.perform(head("/path/method").with(defaultServlet())).andExpect(status().isUnauthorized()); - this.mvc.perform(get("/path/method").with(servletPath("/path"))).andExpect(status().isUnauthorized()); - } - - @Test - void defaultServletWhenNoDefaultServletThenWiringException() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.register(DefaultServletConfig.class) - .postProcessor((context) -> context.setServletContext(new MockServletContext())) - .autowire()); - } - - @Test - void servletPathMatchersWhenMatchingServletThenPermits() throws Exception { - MockServletContext servletContext = MockServletContext.mvc(); - servletContext.addServlet("path", Servlet.class).addMapping("/path/*"); - this.spring.register(ServletPathConfig.class) - .postProcessor((context) -> context.setServletContext(servletContext)) - .autowire(); - this.mvc.perform(get("/path/path").with(servletPath("/path"))).andExpect(status().isNotFound()); - this.mvc.perform(get("/path/path").with(defaultServlet())).andExpect(status().isUnauthorized()); - } - - @Test - void servletPathHttpMethodMatchersWhenMatchingServletThenPermits() throws Exception { - MockServletContext servletContext = MockServletContext.mvc(); - servletContext.addServlet("path", Servlet.class).addMapping("/path/*"); - this.spring.register(ServletPathConfig.class) - .postProcessor((context) -> context.setServletContext(servletContext)) - .autowire(); - this.mvc.perform(get("/path/method").with(servletPath("/path"))).andExpect(status().isNotFound()); - this.mvc.perform(head("/path/method").with(servletPath("/path"))).andExpect(status().isUnauthorized()); - this.mvc.perform(get("/path/method").with(defaultServlet())).andExpect(status().isUnauthorized()); - } - - @Test - void servletPathWhenNoMatchingPathThenWiringException() { - MockServletContext servletContext = MockServletContext.mvc(); - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.register(ServletPathConfig.class) - .postProcessor((context) -> context.setServletContext(servletContext)) - .autowire()); - } - - @Test - void servletMappingMatchersWhenMatchingServletThenPermits() throws Exception { - MockServletContext servletContext = MockServletContext.mvc(); - servletContext.addServlet("jsp", Servlet.class).addMapping("*.jsp"); - this.spring.register(ServletMappingConfig.class) - .postProcessor((context) -> context.setServletContext(servletContext)) - .autowire(); - this.mvc.perform(get("/path/file.jsp").with(servletExtension(".jsp"))).andExpect(status().isNotFound()); - this.mvc.perform(get("/path/file.jsp").with(defaultServlet())).andExpect(status().isUnauthorized()); - } - - @Test - void servletMappingHttpMethodMatchersWhenMatchingServletThenPermits() throws Exception { - MockServletContext servletContext = MockServletContext.mvc(); - servletContext.addServlet("jsp", Servlet.class).addMapping("*.jsp"); - this.spring.register(ServletMappingConfig.class) - .postProcessor((context) -> context.setServletContext(servletContext)) - .autowire(); - this.mvc.perform(get("/method/file.jsp").with(servletExtension(".jsp"))).andExpect(status().isNotFound()); - this.mvc.perform(head("/method/file.jsp").with(servletExtension(".jsp"))).andExpect(status().isUnauthorized()); - this.mvc.perform(get("/method/file.jsp").with(defaultServlet())).andExpect(status().isUnauthorized()); - } - - @Test - void servletMappingWhenNoMatchingExtensionThenWiringException() { - MockServletContext servletContext = MockServletContext.mvc(); - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.register(ServletMappingConfig.class) - .postProcessor((context) -> context.setServletContext(servletContext)) - .autowire()); - } - - @Test - void anyRequestWhenUsedWithDefaultServletThenDoesNotWire() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.register(MixedServletEndpointConfig.class).autowire()) - .withMessageContaining("forServletPattern"); - } - - @Test - void servletWhenNoMatchingPathThenDenies() throws Exception { - MockServletContext servletContext = new MockServletContext(); - servletContext.addServlet("default", Servlet.class).addMapping("/"); - servletContext.addServlet("jspServlet", Servlet.class).addMapping("*.jsp"); - servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*"); - this.spring.register(DefaultServletAndServletPathConfig.class) - .postProcessor((context) -> context.setServletContext(servletContext)) - .autowire(); - this.mvc.perform(get("/js/color.js").with(servletPath("/js"))).andExpect(status().isUnauthorized()); - this.mvc.perform(get("/mvc/controller").with(defaultServlet())).andExpect(status().isUnauthorized()); - this.mvc.perform(get("/js/color.js").with(defaultServlet())).andExpect(status().isNotFound()); - this.mvc.perform(get("/mvc/controller").with(servletPath("/mvc"))).andExpect(status().isUnauthorized()); - this.mvc.perform(get("/mvc/controller").with(user("user")).with(servletPath("/mvc"))) - .andExpect(status().isNotFound()); - } - - @Test - void permitAllWhenDefaultServletThenDoesNotWire() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.register(MixedServletPermitAllConfig.class).autowire()) - .withMessageContaining("forServletPattern"); - } - - static RequestPostProcessor defaultServlet() { - return (request) -> { - String uri = request.getRequestURI(); - request.setHttpServletMapping(TestMockHttpServletMappings.defaultMapping()); - request.setServletPath(uri); - request.setPathInfo(""); - return request; - }; - } - - static RequestPostProcessor servletPath(String path) { - return (request) -> { - String uri = request.getRequestURI(); - request.setHttpServletMapping(TestMockHttpServletMappings.path(request, path)); - request.setServletPath(path); - request.setPathInfo(uri.substring(path.length())); - return request; - }; - } - - static RequestPostProcessor servletExtension(String extension) { - return (request) -> { - String uri = request.getRequestURI(); - request.setHttpServletMapping(TestMockHttpServletMappings.extension(request, extension)); - request.setServletPath(uri); - request.setPathInfo(""); - return request; - }; - } - @Configuration @EnableWebSecurity static class GrantedAuthorityDefaultHasRoleConfig { @@ -893,7 +693,6 @@ public class AuthorizeHttpRequestsConfigurerTests { @Configuration @EnableWebSecurity - @EnableWebMvc static class AfterAnyRequestConfig { @Bean @@ -1155,7 +954,7 @@ public class AuthorizeHttpRequestsConfigurerTests { @Configuration @EnableWebMvc @EnableWebSecurity - static class MvcServletPathConfig { + static class ServletPathConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http, HandlerMappingIntrospector introspector) throws Exception { @@ -1337,163 +1136,6 @@ public class AuthorizeHttpRequestsConfigurerTests { } - @Configuration - @EnableWebSecurity - @EnableWebMvc - static class AuthorizeHttpRequestsConfig { - - @Bean - SecurityFilterChain chain(HttpSecurity http) throws Exception { - // @formatter:off - http - .httpBasic(withDefaults()) - .authorizeHttpRequests((requests) -> requests - .requestMatchers("/path/**").permitAll() - .anyRequest().authenticated() - ); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - static class DefaultServletConfig { - - @Bean - SecurityFilterChain chain(HttpSecurity http) throws Exception { - // @formatter:off - http - .httpBasic(withDefaults()) - .authorizeHttpRequests((requests) -> requests - .forServletPattern("/", (root) -> root - .requestMatchers(HttpMethod.GET, "/path/method/**").permitAll() - .requestMatchers("/path/path/**").permitAll() - .anyRequest().authenticated() - ) - ); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - static class ServletPathConfig { - - @Bean - SecurityFilterChain chain(HttpSecurity http) throws Exception { - // @formatter:off - http - .httpBasic(withDefaults()) - .authorizeHttpRequests((requests) -> requests - .forServletPattern("/path/*", (root) -> root - .requestMatchers(HttpMethod.GET, "/method/**").permitAll() - .requestMatchers("/path/**").permitAll() - .anyRequest().authenticated() - ) - ); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - static class ServletMappingConfig { - - @Bean - SecurityFilterChain chain(HttpSecurity http) throws Exception { - // @formatter:off - http - .httpBasic(withDefaults()) - .authorizeHttpRequests((requests) -> requests - .forServletPattern("*.jsp", (jsp) -> jsp - .requestMatchers(HttpMethod.GET, "/method/**").permitAll() - .requestMatchers("/path/**").permitAll() - .anyRequest().authenticated() - ) - ); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - static class MixedServletEndpointConfig { - - @Bean - SecurityFilterChain chain(HttpSecurity http) throws Exception { - // @formatter:off - http - .httpBasic(withDefaults()) - .authorizeHttpRequests((requests) -> requests - .forServletPattern("/", (root) -> root.anyRequest().permitAll()) - .anyRequest().authenticated() - ); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - static class MixedServletPermitAllConfig { - - @Bean - SecurityFilterChain chain(HttpSecurity http) throws Exception { - // @formatter:off - http - .formLogin((form) -> form.loginPage("/page").permitAll()) - .authorizeHttpRequests((requests) -> requests - .forServletPattern("/", (root) -> root - .anyRequest().authenticated() - ) - ); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - static class DefaultServletAndServletPathConfig { - - @Bean - SecurityFilterChain chain(HttpSecurity http) throws Exception { - // @formatter:off - http - .httpBasic(withDefaults()) - .authorizeHttpRequests((requests) -> requests - .forServletPattern("/", (root) -> root - .requestMatchers("/js/**", "/css/**").permitAll() - ) - .forServletPattern("/mvc/*", (mvc) -> mvc - .requestMatchers("/controller/**").authenticated() - ) - .forServletPattern("*.jsp", (jsp) -> jsp - .anyRequest().authenticated() - ) - ); - // @formatter:on - return http.build(); - } - - } - @Configuration static class AuthorizationEventPublisherConfig { diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestMatcherBuildersTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestMatcherBuildersTests.java deleted file mode 100644 index 61fdf6b2be..0000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestMatcherBuildersTests.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright 2002-2023 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.annotation.web.configurers; - -import java.util.List; -import java.util.function.Consumer; - -import jakarta.servlet.Servlet; -import jakarta.servlet.ServletContext; -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpMethod; -import org.springframework.security.config.MockServletContext; -import org.springframework.security.config.annotation.ObjectPostProcessor; -import org.springframework.security.config.annotation.web.configurers.DispatcherServletDelegatingRequestMatcherBuilder.DispatcherServletDelegatingRequestMatcher; -import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.web.context.support.GenericWebApplicationContext; -import org.springframework.web.servlet.DispatcherServlet; -import org.springframework.web.servlet.handler.HandlerMappingIntrospector; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.Mockito.mock; - -public class RequestMatcherBuildersTests { - - @Test - void matchersWhenDefaultDispatcherServletThenMvc() { - MockServletContext servletContext = MockServletContext.mvc(); - RequestMatcherBuilder builder = requestMatchersBuilder(servletContext); - List matchers = builder.matchers("/mvc"); - assertThat(matchers.get(0)).isInstanceOf(MvcRequestMatcher.class); - MvcRequestMatcher matcher = (MvcRequestMatcher) matchers.get(0); - assertThat(ReflectionTestUtils.getField(matcher, "servletPath")).isNull(); - assertThat(ReflectionTestUtils.getField(matcher, "pattern")).isEqualTo("/mvc"); - } - - @Test - void httpMethodMatchersWhenDefaultDispatcherServletThenMvc() { - MockServletContext servletContext = MockServletContext.mvc(); - RequestMatcherBuilder builder = requestMatchersBuilder(servletContext); - List matchers = builder.matchers(HttpMethod.GET, "/mvc"); - assertThat(matchers.get(0)).isInstanceOf(MvcRequestMatcher.class); - MvcRequestMatcher matcher = (MvcRequestMatcher) matchers.get(0); - assertThat(ReflectionTestUtils.getField(matcher, "servletPath")).isNull(); - assertThat(ReflectionTestUtils.getField(matcher, "pattern")).isEqualTo("/mvc"); - assertThat(ReflectionTestUtils.getField(matcher, "method")).isEqualTo(HttpMethod.GET); - } - - @Test - void matchersWhenPathDispatcherServletThenMvc() { - MockServletContext servletContext = new MockServletContext(); - servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*"); - RequestMatcherBuilder builder = requestMatchersBuilder(servletContext); - List matchers = builder.matchers("/controller"); - assertThat(matchers.get(0)).isInstanceOf(MvcRequestMatcher.class); - MvcRequestMatcher matcher = (MvcRequestMatcher) matchers.get(0); - assertThat(ReflectionTestUtils.getField(matcher, "servletPath")).isEqualTo("/mvc"); - assertThat(ReflectionTestUtils.getField(matcher, "pattern")).isEqualTo("/controller"); - } - - @Test - void matchersWhenAlsoExtraServletContainerMappingsThenRequiresServletPath() { - MockServletContext servletContext = new MockServletContext(); - servletContext.addServlet("default", Servlet.class).addMapping("/"); - servletContext.addServlet("jspServlet", Servlet.class).addMapping("*.jsp", "*.jspx"); - servletContext.addServlet("facesServlet", Servlet.class).addMapping("/faces/", "*.jsf", "*.faces", "*.xhtml"); - servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*"); - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> requestMatchersBuilder(servletContext).matcher("/path")) - .withMessageContaining(".forServletPattern"); - } - - @Test - void matchersWhenOnlyDefaultServletThenAnt() { - MockServletContext servletContext = new MockServletContext(); - servletContext.addServlet("default", Servlet.class).addMapping("/"); - RequestMatcherBuilder builder = requestMatchersBuilder(servletContext); - List matchers = builder.matchers("/controller"); - assertThat(matchers.get(0)).isInstanceOf(AntPathRequestMatcher.class); - AntPathRequestMatcher matcher = (AntPathRequestMatcher) matchers.get(0); - assertThat(ReflectionTestUtils.getField(matcher, "pattern")).isEqualTo("/controller"); - } - - @Test - void matchersWhenNoHandlerMappingIntrospectorThenAnt() { - MockServletContext servletContext = MockServletContext.mvc(); - RequestMatcherBuilder builder = requestMatchersBuilder(servletContext, (context) -> { - }); - List matchers = builder.matchers("/controller"); - assertThat(matchers.get(0)).isInstanceOf(AntPathRequestMatcher.class); - AntPathRequestMatcher matcher = (AntPathRequestMatcher) matchers.get(0); - assertThat(ReflectionTestUtils.getField(matcher, "pattern")).isEqualTo("/controller"); - } - - @Test - void matchersWhenNoDispatchServletThenAnt() { - MockServletContext servletContext = new MockServletContext(); - servletContext.addServlet("default", Servlet.class).addMapping("/"); - servletContext.addServlet("messageDispatcherServlet", Servlet.class).addMapping("/services/*"); - RequestMatcherBuilder builder = requestMatchersBuilder(servletContext); - List matchers = builder.matchers("/services/endpoint"); - assertThat(matchers.get(0)).isInstanceOf(AntPathRequestMatcher.class); - AntPathRequestMatcher matcher = (AntPathRequestMatcher) matchers.get(0); - assertThat(ReflectionTestUtils.getField(matcher, "pattern")).isEqualTo("/services/endpoint"); - } - - @Test - void matchersWhenMixedServletsThenServletPathDelegating() { - MockServletContext servletContext = MockServletContext.mvc(); - servletContext.addServlet("messageDispatcherServlet", Servlet.class).addMapping("/services/*"); - RequestMatcherBuilder builder = requestMatchersBuilder(servletContext); - assertThat(builder.matchers("/services/endpoint").get(0)) - .isInstanceOf(DispatcherServletDelegatingRequestMatcher.class); - } - - @Test - void matchersWhenDispatcherServletNotDefaultAndOtherServletsThenRequiresServletPath() { - MockServletContext servletContext = new MockServletContext(); - servletContext.addServlet("default", Servlet.class).addMapping("/"); - servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*"); - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> requestMatchersBuilder(servletContext).matcher("/path/**")) - .withMessageContaining(".forServletPattern"); - } - - @Test - void httpMatchersWhenDispatcherServletNotDefaultAndOtherServletsThenRequiresServletPath() { - MockServletContext servletContext = new MockServletContext(); - servletContext.addServlet("default", Servlet.class).addMapping("/"); - servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*"); - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> requestMatchersBuilder(servletContext).matcher("/pattern")) - .withMessageContaining(".forServletPattern"); - } - - @Test - void matchersWhenTwoDispatcherServletsThenException() { - MockServletContext servletContext = MockServletContext.mvc(); - servletContext.addServlet("two", DispatcherServlet.class).addMapping("/other/*"); - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> requestMatchersBuilder(servletContext).matcher("/path/**")) - .withMessageContaining(".forServletPattern"); - } - - @Test - void matchersWhenMoreThanOneMappingThenException() { - MockServletContext servletContext = new MockServletContext(); - servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/", "/two/*"); - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> requestMatchersBuilder(servletContext).matcher("/path/**")) - .withMessageContaining(".forServletPattern"); - } - - @Test - void matchersWhenMoreThanOneMappingAndDefaultServletsThenRequiresServletPath() { - MockServletContext servletContext = new MockServletContext(); - servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/", "/two/*"); - servletContext.addServlet("jspServlet", Servlet.class).addMapping("*.jsp", "*.jspx"); - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> requestMatchersBuilder(servletContext).matcher("/path/**")) - .withMessageContaining(".forServletPattern"); - } - - RequestMatcherBuilder requestMatchersBuilder(ServletContext servletContext) { - return requestMatchersBuilder(servletContext, (context) -> { - context.registerBean("mvcHandlerMappingIntrospector", HandlerMappingIntrospector.class, - () -> mock(HandlerMappingIntrospector.class)); - context.registerBean(ObjectPostProcessor.class, () -> mock(ObjectPostProcessor.class)); - }); - } - - RequestMatcherBuilder requestMatchersBuilder(ServletContext servletContext, - Consumer consumer) { - GenericWebApplicationContext context = new GenericWebApplicationContext(servletContext); - consumer.accept(context); - context.refresh(); - return RequestMatcherBuilders.createDefault(context); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ServletPatternRequestMatcherTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ServletPatternRequestMatcherTests.java deleted file mode 100644 index 98a371bbc7..0000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ServletPatternRequestMatcherTests.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2002-2023 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.annotation.web.configurers; - -import org.junit.jupiter.api.Test; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.config.TestMockHttpServletMappings; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link ServletPatternRequestMatcher} - */ -class ServletPatternRequestMatcherTests { - - ServletPatternRequestMatcher matcher = new ServletPatternRequestMatcher("*.jsp"); - - @Test - void matchesWhenDefaultServletThenTrue() { - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/a/uri.jsp"); - request.setHttpServletMapping(TestMockHttpServletMappings.extension(request, ".jsp")); - assertThat(this.matcher.matches(request)).isTrue(); - } - - @Test - void matchesWhenNotDefaultServletThenFalse() { - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/a/uri.jsp"); - request.setHttpServletMapping(TestMockHttpServletMappings.path(request, "/a")); - request.setServletPath("/a/uri.jsp"); - assertThat(this.matcher.matches(request)).isFalse(); - } - - @Test - void matcherWhenDefaultServletThenTrue() { - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/a/uri.jsp"); - request.setHttpServletMapping(TestMockHttpServletMappings.extension(request, ".jsp")); - request.setServletPath("/a/uri.jsp"); - assertThat(this.matcher.matcher(request).isMatch()).isTrue(); - } - - @Test - void matcherWhenNotDefaultServletThenFalse() { - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/a/uri.jsp"); - request.setHttpServletMapping(TestMockHttpServletMappings.path(request, "/a")); - request.setServletPath("/a/uri.jsp"); - assertThat(this.matcher.matcher(request).isMatch()).isFalse(); - } - -} diff --git a/docs/modules/ROOT/pages/servlet/authorization/authorize-http-requests.adoc b/docs/modules/ROOT/pages/servlet/authorization/authorize-http-requests.adoc index 7dac6eb233..298b7f8ad3 100644 --- a/docs/modules/ROOT/pages/servlet/authorization/authorize-http-requests.adoc +++ b/docs/modules/ROOT/pages/servlet/authorization/authorize-http-requests.adoc @@ -571,156 +571,70 @@ http { ---- ==== -[[match-by-servlet-path]] -[[mvc-not-default-servlet]] [[match-by-mvc]] -=== Matching by Servlet Pattern +=== Using an MvcRequestMatcher -Generally speaking, you can use `requestMatchers(String...)` and `requestMatchers(HttpMethod, String...)` as demonstrated above. +Generally speaking, you can use `requestMatchers(String)` as demonstrated above. However, if you map Spring MVC to a different servlet path, then you need to account for that in your security configuration. -For example, if Spring MVC is mapped to `/mvc` instead of `/` (the default), then you may have an endpoint like `/mvc/my/controller` that you want to authorize. +For example, if Spring MVC is mapped to `/spring-mvc` instead of `/` (the default), then you may have an endpoint like `/spring-mvc/my/controller` that you want to authorize. -If you have multiple servlets, and `DispatcherServlet` is mapped in this way, you'll see an error that's something like this: - -[source,bash] ----- -This method cannot decide whether these patterns are Spring MVC patterns or not - -... - -For your reference, here is your servlet configuration: {default=[/], dispatcherServlet=[/mvc/*]} - -To address this, you need to specify the servlet path or pattern for each endpoint. -You can use .forServletPattern in conjunction with requestMatchers do to this ----- - -You can use `.forServletPattern` (or construct your own `MvcRequestMatcher` instance) to split the servlet path and the controller path in your configuration, like so: +You need to use `MvcRequestMatcher` to split the servlet path and the controller path in your configuration like so: .Match by MvcRequestMatcher ==== .Java [source,java,role="primary"] ---- +@Bean +MvcRequestMatcher.Builder mvc(HandlerMappingIntrospector introspector) { + return new MvcRequestMatcher.Builder(introspector).servletPath("/spring-mvc"); +} + @Bean SecurityFilterChain appEndpoints(HttpSecurity http, MvcRequestMatcher.Builder mvc) { http .authorizeHttpRequests((authorize) -> authorize - .forServletPattern("/mvc/*", (mvc) -> mvc - .requestMatchers("/my/resource/**").hasAuthority("resource:read") - .anyRequest().authenticated() - ) + .requestMatchers(mvc.pattern("/my/controller/**")).hasAuthority("controller") + .anyRequest().authenticated() ); return http.build(); } ---- -==== - -where `/mvc/*` is the matching pattern in your servlet configuration listed in the error message. - -This need can arise in at least two different ways: - -* If you use the `spring.mvc.servlet.path` Boot property to change the default path (`/`) to something else -* If you register more than one Spring MVC `DispatcherServlet` (thus requiring that one of them not be the default servlet) - -Note that when either of these cases come up, all URIs need to be fully-qualified as above. - -For example, consider a more sophisticated setup where you have Spring MVC resources mapped to `/mvc/*` and Spring Boot H2 Console mapped to `/h2-console/*`. -In that case, each URI can be made absolute, listing the servlet path like so: -.Match by Servlet Path -==== -.Java -[source,java,role="primary"] +.Kotlin +[source,kotlin,role="secondary"] ---- @Bean -SecurityFilterChain appSecurity(HttpSecurity http) throws Exception { - http - .authorizeHttpRequests((authorize) -> authorize - .forServletPattern("/mvc/*", (mvc) -> mvc - .requestMatchers("/my/resource/**").hasAuthority("resource:read") - ) - .forServletPattern("/h2-console/*", (h2) -> h2 - .anyRequest().hasAuthority("h2") - ) - ) - // ... -} ----- -==== - -Alternatively, you can do one of three things to remove the need to disambiguate: - -1. Always deploy `DispatcherServlet` to `/` (the default behavior) -+ -When `DispatcherServlet` is mapped to `/`, it's clear that all the URIs supplied in `requestMatchers(String)` are absolute URIs. -Because of that, there is no ambiguity when interpreting them. -+ -2. Remove all other servlets -+ -When there is only `DispatcherServlet`, it's clear that all the URIs supplied in `requestMatchers(String)` are relative to the Spring MVC configuration. -Because of that, there is no ambiguity when interpreting them. - -At times, servlet containers add other servlets by default that you aren't actually using. -So, if these aren't needed, remove them, bringing you down to just `DispatcherServlet`. -+ -3. Create an `HttpRequestHandler` so that `DispatcherServlet` dispatches to your servlets instead of your servlet container. -+ -If you are deploying Spring MVC to a separate path to allow your container to serve static resources, consider instead {spring-framework-reference-url}web/webmvc/mvc-config/default-servlet-handler.html#page-title[notifying Spring MVC about this]. -Or, if you have a custom servlet, publishing {spring-framework-api-url}org/springframework/web/servlet/mvc/HttpRequestHandlerAdapter.html[a custom `HttpRequestHandler` bean within {spring-framework-api-url}org/springframework/web/servlet/DispatcherServlet.html[the `DispatcherServlet` configuration] instead. -+ - -=== Matching by the Default Servlet - -You can also match more generally by the matching pattern specified in your servlet configuration. - -For example, to match the default servlet (whichever servlet is mapped to `/`), use `forServletPattern` like so: +fun mvc(introspector: HandlerMappingIntrospector): MvcRequestMatcher.Builder = + MvcRequestMatcher.Builder(introspector).servletPath("/spring-mvc"); -.Match by the Default Servlet -==== -.Java -[source,java,role="primary"] ----- @Bean -SecurityFilterChain appSecurity(HttpSecurity http) throws Exception { - http - .authorizeHttpRequests((authorize) -> authorize - .forServletPattern("/", (root) -> root - .requestMatchers("/my/resource/**").hasAuthority("resource:read") - ) - ) - // ... -} +fun appEndpoints(http: HttpSecurity, mvc: MvcRequestMatcher.Builder): SecurityFilterChain = + http { + authorizeHttpRequests { + authorize(mvc.pattern("/my/controller/**"), hasAuthority("controller")) + authorize(anyRequest, authenticated) + } + } ---- -==== -Such will match on requests that the servlet container matches to your default servlet that start with the URI `/my/resource`. - -=== Matching by an Extension Servlet - -Or, to match to an extension servlet (like a servlet mapped to `*.jsp`), use `forServletPattern` as follows: - -.Match by an Extension Servlet -==== -.Java -[source,java,role="primary"] +.Xml +[source,xml,role="secondary"] ---- -@Bean -SecurityFilterChain appSecurity(HttpSecurity http) throws Exception { - http - .authorizeHttpRequests((authorize) -> authorize - .forServletPattern("*.jsp", (jsp) -> jsp - .requestMatchers("/my/resource/**").hasAuthority("resource:read") - ) - ) - // ... -} + + + + ---- ==== -Such will match on requests that the servlet container matches to your `*.jsp` servlet that start with the URI `/my/resource` (for example a request like `/my/resource/page.jsp`). +This need can arise in at least two different ways: + +* If you use the `spring.mvc.servlet.path` Boot property to change the default path (`/`) to something else +* If you register more than one Spring MVC `DispatcherServlet` (thus requiring that one of them not be the default path) [[match-by-custom]] === Using a Custom Matcher