diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/PathRequest.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/PathRequest.java new file mode 100644 index 00000000000..a87541557f4 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/PathRequest.java @@ -0,0 +1,82 @@ +/* + * Copyright 2012-2018 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 + * + * http://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.boot.autoconfigure.security.servlet; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.boot.autoconfigure.h2.H2ConsoleProperties; +import org.springframework.boot.autoconfigure.security.StaticResourceLocation; +import org.springframework.boot.security.servlet.ApplicationContextRequestMatcher; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; + +/** + * Factory that can be used to create a {@link RequestMatcher} for commonly used paths. + * + * @author Madhura Bhave + * @author Phillip Webb + * @since 2.0.0 + */ +public final class PathRequest { + + private PathRequest() { + } + + /** + * Returns a {@link StaticResourceRequest} that can be used to create a matcher for + * {@link StaticResourceLocation Locations}. + * @return a {@link StaticResourceRequest} + */ + public static StaticResourceRequest toStaticResources() { + return StaticResourceRequest.get(); + } + + /** + * Returns a matcher that includes the H2 console location. For example:
+	 * PathRequest.toH2Console()
+	 * 
+ * @return the configured {@link RequestMatcher} + */ + public static H2ConsoleRequestMatcher toH2Console() { + return new H2ConsoleRequestMatcher(); + } + + /** + * The request matcher used to match against h2 console path. + */ + public static final class H2ConsoleRequestMatcher + extends ApplicationContextRequestMatcher { + + private RequestMatcher delegate; + + private H2ConsoleRequestMatcher() { + super(H2ConsoleProperties.class); + } + + @Override + protected void initialized(H2ConsoleProperties h2ConsoleProperties) { + this.delegate = new AntPathRequestMatcher(h2ConsoleProperties.getPath()); + } + + @Override + protected boolean matches(HttpServletRequest request, H2ConsoleProperties context) { + return this.delegate.matches(request); + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/StaticResourceRequest.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/StaticResourceRequest.java index d4ab0f2f0e3..a3954fd4d9c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/StaticResourceRequest.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/servlet/StaticResourceRequest.java @@ -34,15 +34,18 @@ import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; /** - * Factory that can be used to create a {@link RequestMatcher} for static resources in - * commonly used locations. + * Used to create a {@link RequestMatcher} for static resources in + * commonly used locations. Returned by {@link PathRequest#toStaticResources()}. * * @author Madhura Bhave * @author Phillip Webb * @since 2.0.0 + * @see PathRequest */ public final class StaticResourceRequest { + private static final StaticResourceRequest INSTANCE = new StaticResourceRequest(); + private StaticResourceRequest() { } @@ -52,41 +55,49 @@ public final class StaticResourceRequest { * {@link StaticResourceRequestMatcher#excluding(StaticResourceLocation, StaticResourceLocation...) * excluding} method can be used to remove specific locations if required. For * example:
-	 * StaticResourceRequest.toCommonLocations().excluding(StaticResourceLocation.CSS)
+	 * StaticResourceRequest.atCommonLocations().excluding(StaticResourceLocation.CSS)
 	 * 
* @return the configured {@link RequestMatcher} */ - public static StaticResourceRequestMatcher toCommonLocations() { - return to(EnumSet.allOf(StaticResourceLocation.class)); + public StaticResourceRequestMatcher atCommonLocations() { + return at(EnumSet.allOf(StaticResourceLocation.class)); } /** * Returns a matcher that includes the specified {@link StaticResourceLocation * Locations}. For example:
-	 * StaticResourceRequest.to(StaticResourceLocation.CSS, StaticResourceLocation.JAVA_SCRIPT)
+	 * StaticResourceRequest.at(StaticResourceLocation.CSS, StaticResourceLocation.JAVA_SCRIPT)
 	 * 
* @param first the first location to include * @param rest additional locations to include * @return the configured {@link RequestMatcher} */ - public static StaticResourceRequestMatcher to(StaticResourceLocation first, + public StaticResourceRequestMatcher at(StaticResourceLocation first, StaticResourceLocation... rest) { - return to(EnumSet.of(first, rest)); + return at(EnumSet.of(first, rest)); } /** * Returns a matcher that includes the specified {@link StaticResourceLocation * Locations}. For example:
-	 * StaticResourceRequest.to(locations)
+	 * StaticResourceRequest.at(locations)
 	 * 
* @param locations the locations to include * @return the configured {@link RequestMatcher} */ - public static StaticResourceRequestMatcher to(Set locations) { + public StaticResourceRequestMatcher at(Set locations) { Assert.notNull(locations, "Locations must not be null"); return new StaticResourceRequestMatcher(new LinkedHashSet<>(locations)); } + /** + * Return the static resource request. + * @return the static resource request + */ + static StaticResourceRequest get() { + return INSTANCE; + } + /** * The request matcher used to match against resource {@link StaticResourceLocation * Locations}. diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/PathRequestTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/PathRequestTests.java new file mode 100644 index 00000000000..13c6f0c0a90 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/PathRequestTests.java @@ -0,0 +1,112 @@ +/* + * Copyright 2012-2018 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 + * + * http://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.boot.autoconfigure.security.servlet; + +import javax.servlet.http.HttpServletRequest; + +import org.assertj.core.api.AssertDelegateTarget; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockServletContext; +import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.StaticWebApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link PathRequest}. + * + * @author Madhura Bhave + */ +public class PathRequestTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void toStaticResourcesShouldReturnStaticResourceRequest() { + assertThat(PathRequest.toStaticResources()).isInstanceOf(StaticResourceRequest.class); + } + + @Test + public void toH2ConsoleShouldMatchH2ConsolePath() { + RequestMatcher matcher = PathRequest.toH2Console(); + assertMatcher(matcher).matches("/h2-console"); + assertMatcher(matcher).doesNotMatch("/js/file.js"); + } + + private RequestMatcherAssert assertMatcher(RequestMatcher matcher) { + StaticWebApplicationContext context = new StaticWebApplicationContext(); + context.registerBean(ServerProperties.class); + return assertThat(new RequestMatcherAssert(context, matcher)); + } + + private static class RequestMatcherAssert implements AssertDelegateTarget { + + private final WebApplicationContext context; + + private final RequestMatcher matcher; + + RequestMatcherAssert(WebApplicationContext context, RequestMatcher matcher) { + this.context = context; + this.matcher = matcher; + } + + public void matches(String path) { + matches(mockRequest(path)); + } + + private void matches(HttpServletRequest request) { + assertThat(this.matcher.matches(request)) + .as("Matches " + getRequestPath(request)).isTrue(); + } + + public void doesNotMatch(String path) { + doesNotMatch(mockRequest(path)); + } + + private void doesNotMatch(HttpServletRequest request) { + assertThat(this.matcher.matches(request)) + .as("Does not match " + getRequestPath(request)).isFalse(); + } + + private MockHttpServletRequest mockRequest(String path) { + MockServletContext servletContext = new MockServletContext(); + servletContext.setAttribute( + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, + this.context); + MockHttpServletRequest request = new MockHttpServletRequest(servletContext); + request.setPathInfo(path); + return request; + } + + private String getRequestPath(HttpServletRequest request) { + String url = request.getServletPath(); + if (request.getPathInfo() != null) { + url += request.getPathInfo(); + } + return url; + } + + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/StaticResourceRequestTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/StaticResourceRequestTests.java index c41c13ed9c7..50654bbec10 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/StaticResourceRequestTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/servlet/StaticResourceRequestTests.java @@ -16,8 +16,6 @@ package org.springframework.boot.autoconfigure.security.servlet; -import java.util.Set; - import javax.servlet.http.HttpServletRequest; import org.assertj.core.api.AssertDelegateTarget; @@ -43,12 +41,14 @@ import static org.assertj.core.api.Assertions.assertThat; */ public class StaticResourceRequestTests { + private StaticResourceRequest resourceRequest = StaticResourceRequest.get(); + @Rule public ExpectedException thrown = ExpectedException.none(); @Test - public void toCommonLocationsShouldMatchCommonLocations() { - RequestMatcher matcher = StaticResourceRequest.toCommonLocations(); + public void atCommonLocationsShouldMatchCommonLocations() { + RequestMatcher matcher = this.resourceRequest.atCommonLocations(); assertMatcher(matcher).matches("/css/file.css"); assertMatcher(matcher).matches("/js/file.js"); assertMatcher(matcher).matches("/images/file.css"); @@ -58,42 +58,42 @@ public class StaticResourceRequestTests { } @Test - public void toCommonLocationsWithExcludeShouldNotMatchExcluded() { - RequestMatcher matcher = StaticResourceRequest.toCommonLocations() + public void atCommonLocationsWithExcludeShouldNotMatchExcluded() { + RequestMatcher matcher = this.resourceRequest.atCommonLocations() .excluding(StaticResourceLocation.CSS); assertMatcher(matcher).doesNotMatch("/css/file.css"); assertMatcher(matcher).matches("/js/file.js"); } @Test - public void toLocationShouldMatchLocation() { - RequestMatcher matcher = StaticResourceRequest.to(StaticResourceLocation.CSS); + public void atLocationShouldMatchLocation() { + RequestMatcher matcher = this.resourceRequest.at(StaticResourceLocation.CSS); assertMatcher(matcher).matches("/css/file.css"); assertMatcher(matcher).doesNotMatch("/js/file.js"); } @Test - public void toLocationWhenHasServletPathShouldMatchLocation() { + public void atLocationWhenHasServletPathShouldMatchLocation() { ServerProperties serverProperties = new ServerProperties(); serverProperties.getServlet().setPath("/foo"); - RequestMatcher matcher = StaticResourceRequest.to(StaticResourceLocation.CSS); + RequestMatcher matcher = this.resourceRequest.at(StaticResourceLocation.CSS); assertMatcher(matcher, serverProperties).matches("/foo", "/css/file.css"); assertMatcher(matcher, serverProperties).doesNotMatch("/foo", "/js/file.js"); } @Test - public void toLocationsFromSetWhenSetIsNullShouldThrowException() { + public void atLocationsFromSetWhenSetIsNullShouldThrowException() { this.thrown.expect(IllegalArgumentException.class); this.thrown.expectMessage("Locations must not be null"); - StaticResourceRequest.to((Set) null); + this.resourceRequest.at(null); } @Test public void excludeFromSetWhenSetIsNullShouldThrowException() { this.thrown.expect(IllegalArgumentException.class); this.thrown.expectMessage("Locations must not be null"); - StaticResourceRequest.toCommonLocations() - .excluding((Set) null); + this.resourceRequest.atCommonLocations() + .excluding(null); } private RequestMatcherAssert assertMatcher(RequestMatcher matcher) { diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index b5541089b9c..a14c1b3d5dc 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -3013,7 +3013,7 @@ Access rules can be overridden by adding a custom `WebSecurityConfigurerAdapter` Boot provides convenience methods that can be used to override access rules for actuator endpoints and static resources. `EndpointRequest` can be used to create a `RequestMatcher` that is based on the `management.endpoints.web.base-path` property. -`StaticResourceRequest` can be used to create a `RequestMatcher` for static resources in +`PathRequest` can be used to create a `RequestMatcher` for resources in commonly used locations. diff --git a/spring-boot-samples/spring-boot-sample-actuator-custom-security/src/main/java/sample/actuator/customsecurity/SecurityConfiguration.java b/spring-boot-samples/spring-boot-sample-actuator-custom-security/src/main/java/sample/actuator/customsecurity/SecurityConfiguration.java index f722f449033..53967fd1ab8 100644 --- a/spring-boot-samples/spring-boot-sample-actuator-custom-security/src/main/java/sample/actuator/customsecurity/SecurityConfiguration.java +++ b/spring-boot-samples/spring-boot-sample-actuator-custom-security/src/main/java/sample/actuator/customsecurity/SecurityConfiguration.java @@ -17,7 +17,7 @@ package sample.actuator.customsecurity; import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest; -import org.springframework.boot.autoconfigure.security.servlet.StaticResourceRequest; +import org.springframework.boot.autoconfigure.security.servlet.PathRequest; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -43,7 +43,7 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter { http.authorizeRequests() .requestMatchers(EndpointRequest.to("health", "info")).permitAll() .requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ACTUATOR") - .requestMatchers(StaticResourceRequest.toCommonLocations()).permitAll() + .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() .antMatchers("/foo").permitAll() .antMatchers("/**").hasRole("USER") .and() diff --git a/spring-boot-samples/spring-boot-sample-web-secure/src/main/java/sample/web/secure/SampleWebSecureApplication.java b/spring-boot-samples/spring-boot-sample-web-secure/src/main/java/sample/web/secure/SampleWebSecureApplication.java index 3b0d66365a4..b8962b9e082 100644 --- a/spring-boot-samples/spring-boot-sample-web-secure/src/main/java/sample/web/secure/SampleWebSecureApplication.java +++ b/spring-boot-samples/spring-boot-sample-web-secure/src/main/java/sample/web/secure/SampleWebSecureApplication.java @@ -20,7 +20,7 @@ import java.util.Date; import java.util.Map; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.security.servlet.StaticResourceRequest; +import org.springframework.boot.autoconfigure.security.servlet.PathRequest; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -64,7 +64,7 @@ public class SampleWebSecureApplication implements WebMvcConfigurer { protected void configure(HttpSecurity http) throws Exception { // @formatter:off http.authorizeRequests() - .requestMatchers(StaticResourceRequest.toCommonLocations()).permitAll() + .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() .anyRequest().fullyAuthenticated() .and() .formLogin().loginPage("/login").failureUrl("/login?error").permitAll()