From c3dad06ea6097042fc6eaa689a045adefb80c295 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Fri, 5 Jul 2019 09:51:41 -0400 Subject: [PATCH] Allow configuration of request matchers through nested builder Issue: gh-5557 --- .../annotation/web/builders/HttpSecurity.java | 101 +++++++++++++++ .../HttpSecurityRequestMatchersTests.java | 119 +++++++++++++++++- .../RequestMatcherConfigurerTests.java | 33 +++++ 3 files changed, 252 insertions(+), 1 deletion(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java index c609fa6056..175cf5b11f 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java @@ -2234,6 +2234,107 @@ public final class HttpSecurity extends return requestMatcherConfigurer; } + /** + * Allows specifying which {@link HttpServletRequest} instances this + * {@link HttpSecurity} will be invoked on. This method allows for easily invoking the + * {@link HttpSecurity} for multiple different {@link RequestMatcher} instances. If + * only a single {@link RequestMatcher} is necessary consider using {@link #mvcMatcher(String)}, + * {@link #antMatcher(String)}, {@link #regexMatcher(String)}, or + * {@link #requestMatcher(RequestMatcher)}. + * + *

+ * Invoking {@link #requestMatchers()} will not override previous invocations of {@link #mvcMatcher(String)}}, + * {@link #requestMatchers()}, {@link #antMatcher(String)}, + * {@link #regexMatcher(String)}, and {@link #requestMatcher(RequestMatcher)}. + *

+ * + *

Example Configurations

+ * + * The following configuration enables the {@link HttpSecurity} for URLs that begin + * with "/api/" or "/oauth/". + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class RequestMatchersSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.requestMatchers(requestMatchers ->
+	 * 				requestMatchers
+	 * 					.antMatchers("/api/**", "/oauth/**")
+	 * 			)
+	 * 			.authorizeRequests(authorizeRequests ->
+	 * 				authorizeRequests
+	 * 					.antMatchers("/**").hasRole("USER")
+	 * 			)
+	 * 			.httpBasic(withDefaults());
+	 * 	}
+	 * }
+	 * 
+ * + * The configuration below is the same as the previous configuration. + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class RequestMatchersSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.requestMatchers(requestMatchers ->
+	 * 				requestMatchers
+	 * 					.antMatchers("/api/**")
+	 * 					.antMatchers("/oauth/**")
+	 * 			)
+	 * 			.authorizeRequests(authorizeRequests ->
+	 * 				authorizeRequests
+	 * 					.antMatchers("/**").hasRole("USER")
+	 * 			)
+	 * 			.httpBasic(withDefaults());
+	 * 	}
+	 * }
+	 * 
+ * + * The configuration below is also the same as the above configuration. + * + *
+	 * @Configuration
+	 * @EnableWebSecurity
+	 * public class RequestMatchersSecurityConfig extends WebSecurityConfigurerAdapter {
+	 *
+	 * 	@Override
+	 * 	protected void configure(HttpSecurity http) throws Exception {
+	 * 		http
+	 * 			.requestMatchers(requestMatchers ->
+	 * 				requestMatchers
+	 * 					.antMatchers("/api/**")
+	 * 			)
+	 *			.requestMatchers(requestMatchers ->
+	 *			requestMatchers
+	 * 				.antMatchers("/oauth/**")
+	 * 			)
+	 * 			.authorizeRequests(authorizeRequests ->
+	 * 				authorizeRequests
+	 * 					.antMatchers("/**").hasRole("USER")
+	 * 			)
+	 * 			.httpBasic(withDefaults());
+	 * 	}
+	 * }
+	 * 
+ * + * @param requestMatcherCustomizer the {@link Customizer} to provide more options for + * the {@link RequestMatcherConfigurer} + * @return the {@link HttpSecurity} for further customizations + * @throws Exception + */ + public HttpSecurity requestMatchers(Customizer requestMatcherCustomizer) throws Exception { + requestMatcherCustomizer.customize(requestMatcherConfigurer); + return HttpSecurity.this; + } + /** * Allows configuring the {@link HttpSecurity} to only be invoked when matching the * provided {@link RequestMatcher}. If more advanced configuration is necessary, diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityRequestMatchersTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityRequestMatchersTests.java index c05f8064a6..ef4d4a2e42 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityRequestMatchersTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityRequestMatchersTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2019 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. @@ -38,6 +38,7 @@ import org.springframework.web.context.support.AnnotationConfigWebApplicationCon import org.springframework.web.servlet.config.annotation.EnableWebMvc; import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.security.config.Customizer.withDefaults; /** * @author Rob Winch @@ -195,6 +196,62 @@ public class HttpSecurityRequestMatchersTests { } } + @Test + public void requestMatchersWhenMvcMatcherInLambdaThenPathIsSecured() throws Exception { + loadConfig(RequestMatchersMvcMatcherInLambdaConfig.class); + + this.request.setServletPath("/path"); + this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); + + assertThat(this.response.getStatus()) + .isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); + + setup(); + + this.request.setServletPath("/path.html"); + this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); + + assertThat(this.response.getStatus()) + .isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); + + setup(); + + this.request.setServletPath("/path/"); + this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); + + assertThat(this.response.getStatus()) + .isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); + } + + @EnableWebSecurity + @Configuration + @EnableWebMvc + static class RequestMatchersMvcMatcherInLambdaConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .requestMatchers(requestMatchers -> + requestMatchers + .mvcMatchers("/path") + ) + .httpBasic(withDefaults()) + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().denyAll() + ); + // @formatter:on + } + + @RestController + static class PathController { + @RequestMapping("/path") + public String path() { + return "path"; + } + } + } + @Test public void requestMatchersMvcMatcherServletPath() throws Exception { loadConfig(RequestMatchersMvcMatcherServeltPathConfig.class); @@ -260,6 +317,66 @@ public class HttpSecurityRequestMatchersTests { } } + @Test + public void requestMatcherWhensMvcMatcherServletPathInLambdaThenPathIsSecured() throws Exception { + loadConfig(RequestMatchersMvcMatcherServletPathInLambdaConfig.class); + + this.request.setServletPath("/spring"); + this.request.setRequestURI("/spring/path"); + this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); + + assertThat(this.response.getStatus()) + .isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); + + setup(); + + this.request.setServletPath(""); + this.request.setRequestURI("/path"); + this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); + + assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + + setup(); + + this.request.setServletPath("/other"); + this.request.setRequestURI("/other/path"); + + this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); + + assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + } + + @EnableWebSecurity + @Configuration + @EnableWebMvc + static class RequestMatchersMvcMatcherServletPathInLambdaConfig + extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .requestMatchers(requestMatchers -> + requestMatchers + .mvcMatchers("/path").servletPath("/spring") + .mvcMatchers("/never-match") + ) + .httpBasic(withDefaults()) + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().denyAll() + ); + // @formatter:on + } + + @RestController + static class PathController { + @RequestMapping("/path") + public String path() { + return "path"; + } + } + } + public void loadConfig(Class... configs) { this.context = new AnnotationConfigWebApplicationContext(); this.context.register(configs); diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestMatcherConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestMatcherConfigurerTests.java index c47b8f970f..4bc131761b 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestMatcherConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestMatcherConfigurerTests.java @@ -71,4 +71,37 @@ public class RequestMatcherConfigurerTests { // @formatter:on } } + + @Test + public void authorizeRequestsWhenInvokedMultipleTimesInLambdaThenChainsPaths() throws Exception { + this.spring.register(AuthorizeRequestInLambdaConfig.class).autowire(); + + this.mvc.perform(get("/oauth/abc")) + .andExpect(status().isForbidden()); + this.mvc.perform(get("/api/abc")) + .andExpect(status().isForbidden()); + } + + @EnableWebSecurity + static class AuthorizeRequestInLambdaConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .requestMatchers(requestMatchers -> + requestMatchers + .antMatchers("/api/**") + ) + .requestMatchers(requestMatchers -> + requestMatchers + .antMatchers("/oauth/**") + ) + .authorizeRequests(authorizeRequests -> + authorizeRequests + .anyRequest().denyAll() + ); + // @formatter:on + } + } }