Commit 5e3cc95c authored by Dave Syer's avatar Dave Syer

Adjust security.basic.enabled=false behaviour

Actually the web-secure sample is misusing
security.basic.enabled=false (IMO) - it should be a flag
to say that you want to temporarily disable the basic security
fallback on application endpoins, not  way to disable all
security autoconfiguration.

Added test case to web-secure sample to ensure a user
can log in.

Fixes gh-979
parent b1969f50
...@@ -145,8 +145,9 @@ public class ManagementSecurityAutoConfigurationTests { ...@@ -145,8 +145,9 @@ public class ManagementSecurityAutoConfigurationTests {
PropertyPlaceholderAutoConfiguration.class); PropertyPlaceholderAutoConfiguration.class);
EnvironmentTestUtils.addEnvironment(this.context, "security.basic.enabled:false"); EnvironmentTestUtils.addEnvironment(this.context, "security.basic.enabled:false");
this.context.refresh(); this.context.refresh();
// Just the management endpoints (one filter) and ignores now // Just the management endpoints (one filter) and ignores now plus the backup
assertEquals(7, this.context.getBean(FilterChainProxy.class).getFilterChains() // filter on app endpoints
assertEquals(8, this.context.getBean(FilterChainProxy.class).getFilterChains()
.size()); .size());
} }
......
...@@ -34,8 +34,10 @@ import org.springframework.context.annotation.Scope; ...@@ -34,8 +34,10 @@ import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.SecurityConfigurer; import org.springframework.security.config.annotation.SecurityConfigurer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
...@@ -66,6 +68,9 @@ public class AuthenticationManagerConfiguration extends ...@@ -66,6 +68,9 @@ public class AuthenticationManagerConfiguration extends
@Autowired @Autowired
private SecurityProperties security; private SecurityProperties security;
@Autowired
private AuthenticationEventPublisher authenticationEventPublisher;
private BootDefaultingAuthenticationConfigurerAdapter configurer = new BootDefaultingAuthenticationConfigurerAdapter(); private BootDefaultingAuthenticationConfigurerAdapter configurer = new BootDefaultingAuthenticationConfigurerAdapter();
@Override @Override
...@@ -84,7 +89,13 @@ public class AuthenticationManagerConfiguration extends ...@@ -84,7 +89,13 @@ public class AuthenticationManagerConfiguration extends
@Lazy @Lazy
@Scope(proxyMode = ScopedProxyMode.INTERFACES) @Scope(proxyMode = ScopedProxyMode.INTERFACES)
protected AuthenticationManager lazyAuthenticationManager() { protected AuthenticationManager lazyAuthenticationManager() {
return this.configurer.getAuthenticationManagerBuilder().getOrBuild(); AuthenticationManager manager = this.configurer.getAuthenticationManagerBuilder()
.getOrBuild();
if (manager instanceof ProviderManager) {
((ProviderManager) manager)
.setAuthenticationEventPublisher(this.authenticationEventPublisher);
}
return manager;
} }
/** /**
......
...@@ -20,10 +20,13 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; ...@@ -20,10 +20,13 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/** /**
...@@ -46,6 +49,13 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur ...@@ -46,6 +49,13 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
AuthenticationManagerConfiguration.class }) AuthenticationManagerConfiguration.class })
public class SecurityAutoConfiguration { public class SecurityAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public AuthenticationEventPublisher authenticationEventPublisher(
ApplicationEventPublisher publisher) {
return new DefaultAuthenticationEventPublisher(publisher);
}
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public SecurityProperties securityProperties() { public SecurityProperties securityProperties() {
......
...@@ -39,7 +39,7 @@ public class SecurityProperties implements SecurityPrequisite { ...@@ -39,7 +39,7 @@ public class SecurityProperties implements SecurityPrequisite {
* useful place to put user-defined access rules if you want to override the default * useful place to put user-defined access rules if you want to override the default
* access rules. * access rules.
*/ */
public static final int ACCESS_OVERRIDE_ORDER = SecurityProperties.BASIC_AUTH_ORDER - 1; public static final int ACCESS_OVERRIDE_ORDER = SecurityProperties.BASIC_AUTH_ORDER - 2;
/** /**
* Order applied to the WebSecurityConfigurerAdapter that is used to configure basic * Order applied to the WebSecurityConfigurerAdapter that is used to configure basic
......
...@@ -30,14 +30,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat ...@@ -30,14 +30,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat
import org.springframework.boot.autoconfigure.security.SecurityProperties.Headers; import org.springframework.boot.autoconfigure.security.SecurityProperties.Headers;
import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.web.WebSecurityConfigurer; import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity;
...@@ -89,13 +85,6 @@ public class SpringBootWebSecurityConfiguration { ...@@ -89,13 +85,6 @@ public class SpringBootWebSecurityConfiguration {
private static List<String> DEFAULT_IGNORED = Arrays.asList("/css/**", "/js/**", private static List<String> DEFAULT_IGNORED = Arrays.asList("/css/**", "/js/**",
"/images/**", "/**/favicon.ico"); "/images/**", "/**/favicon.ico");
@Bean
@ConditionalOnMissingBean
public AuthenticationEventPublisher authenticationEventPublisher(
ApplicationEventPublisher publisher) {
return new DefaultAuthenticationEventPublisher(publisher);
}
@Bean @Bean
@ConditionalOnMissingBean({ IgnoredPathsWebSecurityConfigurerAdapter.class }) @ConditionalOnMissingBean({ IgnoredPathsWebSecurityConfigurerAdapter.class })
public WebSecurityConfigurer<WebSecurity> ignoredPathsWebSecurityConfigurerAdapter() { public WebSecurityConfigurer<WebSecurity> ignoredPathsWebSecurityConfigurerAdapter() {
...@@ -164,7 +153,6 @@ public class SpringBootWebSecurityConfiguration { ...@@ -164,7 +153,6 @@ public class SpringBootWebSecurityConfiguration {
// RequestDataValueProcessor // RequestDataValueProcessor
@ConditionalOnClass(RequestDataValueProcessor.class) @ConditionalOnClass(RequestDataValueProcessor.class)
@ConditionalOnMissingBean(RequestDataValueProcessor.class) @ConditionalOnMissingBean(RequestDataValueProcessor.class)
@ConditionalOnExpression("${security.basic.enabled:true}")
@Configuration @Configuration
protected static class WebMvcSecurityConfigurationConditions { protected static class WebMvcSecurityConfigurationConditions {
...@@ -179,25 +167,22 @@ public class SpringBootWebSecurityConfiguration { ...@@ -179,25 +167,22 @@ public class SpringBootWebSecurityConfiguration {
// Pull in a plain @EnableWebSecurity if Spring MVC is not available // Pull in a plain @EnableWebSecurity if Spring MVC is not available
@ConditionalOnMissingBean(WebMvcSecurityConfigurationConditions.class) @ConditionalOnMissingBean(WebMvcSecurityConfigurationConditions.class)
@ConditionalOnMissingClass(name = "org.springframework.web.servlet.support.RequestDataValueProcessor") @ConditionalOnMissingClass(name = "org.springframework.web.servlet.support.RequestDataValueProcessor")
@ConditionalOnExpression("${security.basic.enabled:true}")
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
protected static class DefaultWebSecurityConfiguration { protected static class DefaultWebSecurityConfiguration {
} }
@ConditionalOnExpression("${security.basic.enabled:true}") /**
@Configuration * Basic functionality for all web apps (whether or not we are providing basic auth).
@Order(SecurityProperties.BASIC_AUTH_ORDER) * @author Dave Syer
protected static class ApplicationWebSecurityConfigurerAdapter extends */
private static class BaseApplicationWebSecurityConfigurerAdapter extends
WebSecurityConfigurerAdapter { WebSecurityConfigurerAdapter {
@Autowired @Autowired
private SecurityProperties security; private SecurityProperties security;
@Autowired
private AuthenticationEventPublisher authenticationEventPublisher;
@Override @Override
protected void configure(HttpSecurity http) throws Exception { protected void configure(HttpSecurity http) throws Exception {
...@@ -205,17 +190,6 @@ public class SpringBootWebSecurityConfiguration { ...@@ -205,17 +190,6 @@ public class SpringBootWebSecurityConfiguration {
http.requiresChannel().anyRequest().requiresSecure(); http.requiresChannel().anyRequest().requiresSecure();
} }
String[] paths = getSecureApplicationPaths();
if (this.security.getBasic().isEnabled() && paths.length > 0) {
http.exceptionHandling().authenticationEntryPoint(entryPoint());
http.requestMatchers().antMatchers(paths);
http.authorizeRequests()
.anyRequest()
.hasAnyRole(
this.security.getUser().getRole().toArray(new String[0])) //
.and().httpBasic() //
.and().anonymous().disable();
}
if (!this.security.isEnableCsrf()) { if (!this.security.isEnableCsrf()) {
http.csrf().disable(); http.csrf().disable();
} }
...@@ -225,6 +199,9 @@ public class SpringBootWebSecurityConfiguration { ...@@ -225,6 +199,9 @@ public class SpringBootWebSecurityConfiguration {
SpringBootWebSecurityConfiguration.configureHeaders(http.headers(), SpringBootWebSecurityConfiguration.configureHeaders(http.headers(),
this.security.getHeaders()); this.security.getHeaders());
String[] paths = getSecureApplicationPaths();
configureAdditionalRules(http, paths);
} }
private String[] getSecureApplicationPaths() { private String[] getSecureApplicationPaths() {
...@@ -241,20 +218,60 @@ public class SpringBootWebSecurityConfiguration { ...@@ -241,20 +218,60 @@ public class SpringBootWebSecurityConfiguration {
return list.toArray(new String[list.size()]); return list.toArray(new String[list.size()]);
} }
private AuthenticationEntryPoint entryPoint() { protected void configureAdditionalRules(HttpSecurity http, String... paths)
BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint(); throws Exception {
entryPoint.setRealmName(this.security.getBasic().getRealm()); }
return entryPoint;
}
@ConditionalOnExpression("!${security.basic.enabled:true}")
@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER)
protected static class ApplicationNoWebSecurityConfigurerAdapter extends
BaseApplicationWebSecurityConfigurerAdapter {
@Override
protected void configureAdditionalRules(HttpSecurity http, String... paths)
throws Exception {
if (paths.length > 0) {
http.requestMatchers().antMatchers(paths);
// The basic security was disabled
http.authorizeRequests().anyRequest().permitAll();
}
} }
}
@ConditionalOnExpression("${security.basic.enabled:true}")
@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER)
protected static class ApplicationWebSecurityConfigurerAdapter extends
BaseApplicationWebSecurityConfigurerAdapter {
@Autowired
private SecurityProperties security;
@Override @Override
protected AuthenticationManager authenticationManager() throws Exception { protected void configureAdditionalRules(HttpSecurity http, String... paths)
AuthenticationManager manager = super.authenticationManager(); throws Exception {
if (manager instanceof ProviderManager) {
((ProviderManager) manager) if (paths.length > 0) {
.setAuthenticationEventPublisher(this.authenticationEventPublisher); http.exceptionHandling().authenticationEntryPoint(entryPoint());
http.httpBasic();
http.requestMatchers().antMatchers(paths);
http.authorizeRequests()
.anyRequest()
.hasAnyRole(
this.security.getUser().getRole().toArray(new String[0]));
} }
return manager;
}
private AuthenticationEntryPoint entryPoint() {
BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint();
entryPoint.setRealmName(this.security.getBasic().getRealm());
return entryPoint;
} }
} }
......
...@@ -89,8 +89,8 @@ public class SecurityAutoConfigurationTests { ...@@ -89,8 +89,8 @@ public class SecurityAutoConfigurationTests {
PropertyPlaceholderAutoConfiguration.class); PropertyPlaceholderAutoConfiguration.class);
EnvironmentTestUtils.addEnvironment(this.context, "security.basic.enabled:false"); EnvironmentTestUtils.addEnvironment(this.context, "security.basic.enabled:false");
this.context.refresh(); this.context.refresh();
// No security at all not even ignores // Ignores and permitAll() security on application endpoints
assertEquals(0, this.context.getBeanNamesForType(FilterChainProxy.class).length); assertEquals(1, this.context.getBeanNamesForType(FilterChainProxy.class).length);
} }
@Test @Test
......
package org.test package org.test
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch
@EnableReactor @EnableReactor
@Consumer
@Log @Log
class Runner implements CommandLineRunner { class Runner implements CommandLineRunner {
...@@ -23,9 +22,27 @@ class Runner implements CommandLineRunner { ...@@ -23,9 +22,27 @@ class Runner implements CommandLineRunner {
latch.await() latch.await()
} }
@Bean
CountDownLatch latch() {
latch
}
}
@Consumer
@Log
class Greeter {
@Autowired
Reactor reactor
@Autowired
private CountDownLatch latch
@Selector(value="hello") @Selector(value="hello")
void receive(String data) { void receive(String data) {
log.info "Hello ${data}" log.info "Hello ${data}"
latch.countDown() latch.countDown()
} }
} }
\ No newline at end of file
...@@ -19,6 +19,7 @@ package sample.ui.secure; ...@@ -19,6 +19,7 @@ package sample.ui.secure;
import java.util.Date; import java.util.Date;
import java.util.Map; import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.builder.SpringApplicationBuilder;
...@@ -51,9 +52,7 @@ public class SampleWebSecureApplication extends WebMvcConfigurerAdapter { ...@@ -51,9 +52,7 @@ public class SampleWebSecureApplication extends WebMvcConfigurerAdapter {
} }
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
// Set user password to "password" for demo purposes only new SpringApplicationBuilder(SampleWebSecureApplication.class).run(args);
new SpringApplicationBuilder(SampleWebSecureApplication.class).properties(
"security.user.password=password").run(args);
} }
@Override @Override
...@@ -68,8 +67,16 @@ public class SampleWebSecureApplication extends WebMvcConfigurerAdapter { ...@@ -68,8 +67,16 @@ public class SampleWebSecureApplication extends WebMvcConfigurerAdapter {
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class ApplicationSecurity extends WebSecurityConfigurerAdapter { protected static class ApplicationSecurity extends WebSecurityConfigurerAdapter {
@Autowired
private SecurityProperties security;
@Override @Override
protected void configure(HttpSecurity http) throws Exception { protected void configure(HttpSecurity http) throws Exception {
if (!security.isEnableCsrf()) {
// For testing
http.csrf().disable();
}
http.authorizeRequests().anyRequest().fullyAuthenticated().and().formLogin() http.authorizeRequests().anyRequest().fullyAuthenticated().and().formLogin()
.loginPage("/login").failureUrl("/login?error").permitAll(); .loginPage("/login").failureUrl("/login?error").permitAll();
} }
......
spring.thymeleaf.cache: false spring.thymeleaf.cache: false
debug: true debug: true
security.basic.enabled: false security.basic.enabled: false
# demo only:
security.user.password: password
\ No newline at end of file
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
</fieldset> </fieldset>
<input type="submit" id="login" value="Login" <input type="submit" id="login" value="Login"
class="btn btn-primary" /> <input type="hidden" class="btn btn-primary" /> <input type="hidden"
th:name="${_csrf.parameterName}" th:value="${_csrf.token}" /> th:name="${_csrf.parameterName}" th:value="${_csrf.token}" th:if="${_csrf}"/>
</form> </form>
</div> </div>
</div> </div>
......
...@@ -16,6 +16,10 @@ ...@@ -16,6 +16,10 @@
package sample.ui.secure; package sample.ui.secure;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.Arrays; import java.util.Arrays;
import org.junit.Test; import org.junit.Test;
...@@ -33,9 +37,8 @@ import org.springframework.http.ResponseEntity; ...@@ -33,9 +37,8 @@ import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.util.LinkedMultiValueMap;
import static org.junit.Assert.assertEquals; import org.springframework.util.MultiValueMap;
import static org.junit.Assert.assertTrue;
/** /**
* Basic integration tests for demo application. * Basic integration tests for demo application.
...@@ -45,7 +48,7 @@ import static org.junit.Assert.assertTrue; ...@@ -45,7 +48,7 @@ import static org.junit.Assert.assertTrue;
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SampleWebSecureApplication.class) @SpringApplicationConfiguration(classes = SampleWebSecureApplication.class)
@WebAppConfiguration @WebAppConfiguration
@IntegrationTest("server.port:0") @IntegrationTest({ "server.port:0", "security.enable_csrf:false" })
@DirtiesContext @DirtiesContext
public class SampleSecureApplicationTests { public class SampleSecureApplicationTests {
...@@ -60,8 +63,27 @@ public class SampleSecureApplicationTests { ...@@ -60,8 +63,27 @@ public class SampleSecureApplicationTests {
"http://localhost:" + this.port, HttpMethod.GET, new HttpEntity<Void>( "http://localhost:" + this.port, HttpMethod.GET, new HttpEntity<Void>(
headers), String.class); headers), String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode()); assertEquals(HttpStatus.OK, entity.getStatusCode());
assertTrue("Wrong body (title doesn't match):\n" + entity.getBody(), entity assertTrue("Wrong body (title doesn't match):\n" + entity.getBody(),
.getBody().contains("<title>Login")); entity.getBody().contains("<title>Login"));
}
@Test
public void testLogin() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.TEXT_HTML));
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>();
form.set("username", "user");
form.set("password", "password");
ResponseEntity<String> entity = new TestRestTemplate().exchange(
"http://localhost:" + this.port + "/login", HttpMethod.POST,
new HttpEntity<MultiValueMap<String, String>>(form, headers),
String.class);
assertEquals(HttpStatus.FOUND, entity.getStatusCode());
assertTrue("Wrong location:\n" + entity.getHeaders(),
entity.getHeaders().getLocation().toString().endsWith(port + "/"));
assertNotNull("Missing cookie:\n" + entity.getHeaders(),
entity.getHeaders().get("Set-Cookie"));
} }
@Test @Test
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment