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
This commit is contained in:
Dave Syer 2014-05-29 13:20:20 +01:00
parent b1969f5095
commit 5e3cc95ccf
11 changed files with 149 additions and 62 deletions

View File

@ -145,8 +145,9 @@ public class ManagementSecurityAutoConfigurationTests {
PropertyPlaceholderAutoConfiguration.class);
EnvironmentTestUtils.addEnvironment(this.context, "security.basic.enabled:false");
this.context.refresh();
// Just the management endpoints (one filter) and ignores now
assertEquals(7, this.context.getBean(FilterChainProxy.class).getFilterChains()
// Just the management endpoints (one filter) and ignores now plus the backup
// filter on app endpoints
assertEquals(8, this.context.getBean(FilterChainProxy.class).getFilterChains()
.size());
}

View File

@ -34,8 +34,10 @@ import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
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.SecurityConfigurer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
@ -66,6 +68,9 @@ public class AuthenticationManagerConfiguration extends
@Autowired
private SecurityProperties security;
@Autowired
private AuthenticationEventPublisher authenticationEventPublisher;
private BootDefaultingAuthenticationConfigurerAdapter configurer = new BootDefaultingAuthenticationConfigurerAdapter();
@Override
@ -84,7 +89,13 @@ public class AuthenticationManagerConfiguration extends
@Lazy
@Scope(proxyMode = ScopedProxyMode.INTERFACES)
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;
}
/**

View File

@ -20,10 +20,13 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
@ -46,6 +49,13 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
AuthenticationManagerConfiguration.class })
public class SecurityAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public AuthenticationEventPublisher authenticationEventPublisher(
ApplicationEventPublisher publisher) {
return new DefaultAuthenticationEventPublisher(publisher);
}
@Bean
@ConditionalOnMissingBean
public SecurityProperties securityProperties() {

View File

@ -39,7 +39,7 @@ public class SecurityProperties implements SecurityPrequisite {
* useful place to put user-defined access rules if you want to override the default
* 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

View File

@ -30,14 +30,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplicat
import org.springframework.boot.autoconfigure.security.SecurityProperties.Headers;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationEventPublisher;
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.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
@ -89,13 +85,6 @@ public class SpringBootWebSecurityConfiguration {
private static List<String> DEFAULT_IGNORED = Arrays.asList("/css/**", "/js/**",
"/images/**", "/**/favicon.ico");
@Bean
@ConditionalOnMissingBean
public AuthenticationEventPublisher authenticationEventPublisher(
ApplicationEventPublisher publisher) {
return new DefaultAuthenticationEventPublisher(publisher);
}
@Bean
@ConditionalOnMissingBean({ IgnoredPathsWebSecurityConfigurerAdapter.class })
public WebSecurityConfigurer<WebSecurity> ignoredPathsWebSecurityConfigurerAdapter() {
@ -164,7 +153,6 @@ public class SpringBootWebSecurityConfiguration {
// RequestDataValueProcessor
@ConditionalOnClass(RequestDataValueProcessor.class)
@ConditionalOnMissingBean(RequestDataValueProcessor.class)
@ConditionalOnExpression("${security.basic.enabled:true}")
@Configuration
protected static class WebMvcSecurityConfigurationConditions {
@ -179,25 +167,22 @@ public class SpringBootWebSecurityConfiguration {
// Pull in a plain @EnableWebSecurity if Spring MVC is not available
@ConditionalOnMissingBean(WebMvcSecurityConfigurationConditions.class)
@ConditionalOnMissingClass(name = "org.springframework.web.servlet.support.RequestDataValueProcessor")
@ConditionalOnExpression("${security.basic.enabled:true}")
@Configuration
@EnableWebSecurity
protected static class DefaultWebSecurityConfiguration {
}
@ConditionalOnExpression("${security.basic.enabled:true}")
@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER)
protected static class ApplicationWebSecurityConfigurerAdapter extends
/**
* Basic functionality for all web apps (whether or not we are providing basic auth).
* @author Dave Syer
*/
private static class BaseApplicationWebSecurityConfigurerAdapter extends
WebSecurityConfigurerAdapter {
@Autowired
private SecurityProperties security;
@Autowired
private AuthenticationEventPublisher authenticationEventPublisher;
@Override
protected void configure(HttpSecurity http) throws Exception {
@ -205,17 +190,6 @@ public class SpringBootWebSecurityConfiguration {
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()) {
http.csrf().disable();
}
@ -225,6 +199,9 @@ public class SpringBootWebSecurityConfiguration {
SpringBootWebSecurityConfiguration.configureHeaders(http.headers(),
this.security.getHeaders());
String[] paths = getSecureApplicationPaths();
configureAdditionalRules(http, paths);
}
private String[] getSecureApplicationPaths() {
@ -241,22 +218,62 @@ public class SpringBootWebSecurityConfiguration {
return list.toArray(new String[list.size()]);
}
protected void configureAdditionalRules(HttpSecurity http, String... paths)
throws Exception {
}
}
@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
protected void configureAdditionalRules(HttpSecurity http, String... paths)
throws Exception {
if (paths.length > 0) {
http.exceptionHandling().authenticationEntryPoint(entryPoint());
http.httpBasic();
http.requestMatchers().antMatchers(paths);
http.authorizeRequests()
.anyRequest()
.hasAnyRole(
this.security.getUser().getRole().toArray(new String[0]));
}
}
private AuthenticationEntryPoint entryPoint() {
BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint();
entryPoint.setRealmName(this.security.getBasic().getRealm());
return entryPoint;
}
@Override
protected AuthenticationManager authenticationManager() throws Exception {
AuthenticationManager manager = super.authenticationManager();
if (manager instanceof ProviderManager) {
((ProviderManager) manager)
.setAuthenticationEventPublisher(this.authenticationEventPublisher);
}
return manager;
}
}
}

View File

@ -89,8 +89,8 @@ public class SecurityAutoConfigurationTests {
PropertyPlaceholderAutoConfiguration.class);
EnvironmentTestUtils.addEnvironment(this.context, "security.basic.enabled:false");
this.context.refresh();
// No security at all not even ignores
assertEquals(0, this.context.getBeanNamesForType(FilterChainProxy.class).length);
// Ignores and permitAll() security on application endpoints
assertEquals(1, this.context.getBeanNamesForType(FilterChainProxy.class).length);
}
@Test

View File

@ -1,9 +1,8 @@
package org.test
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CountDownLatch
@EnableReactor
@Consumer
@Log
class Runner implements CommandLineRunner {
@ -23,9 +22,27 @@ class Runner implements CommandLineRunner {
latch.await()
}
@Bean
CountDownLatch latch() {
latch
}
}
@Consumer
@Log
class Greeter {
@Autowired
Reactor reactor
@Autowired
private CountDownLatch latch
@Selector(value="hello")
void receive(String data) {
log.info "Hello ${data}"
latch.countDown()
}
}
}

View File

@ -19,6 +19,7 @@ package sample.ui.secure;
import java.util.Date;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.builder.SpringApplicationBuilder;
@ -36,7 +37,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
@ComponentScan
@Controller
public class SampleWebSecureApplication extends WebMvcConfigurerAdapter {
@RequestMapping("/")
public String home(Map<String, Object> model) {
model.put("message", "Hello World");
@ -51,9 +52,7 @@ public class SampleWebSecureApplication extends WebMvcConfigurerAdapter {
}
public static void main(String[] args) throws Exception {
// Set user password to "password" for demo purposes only
new SpringApplicationBuilder(SampleWebSecureApplication.class).properties(
"security.user.password=password").run(args);
new SpringApplicationBuilder(SampleWebSecureApplication.class).run(args);
}
@Override
@ -68,8 +67,16 @@ public class SampleWebSecureApplication extends WebMvcConfigurerAdapter {
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class ApplicationSecurity extends WebSecurityConfigurerAdapter {
@Autowired
private SecurityProperties security;
@Override
protected void configure(HttpSecurity http) throws Exception {
if (!security.isEnableCsrf()) {
// For testing
http.csrf().disable();
}
http.authorizeRequests().anyRequest().fullyAuthenticated().and().formLogin()
.loginPage("/login").failureUrl("/login?error").permitAll();
}

View File

@ -1,3 +1,5 @@
spring.thymeleaf.cache: false
debug: true
security.basic.enabled: false
security.basic.enabled: false
# demo only:
security.user.password: password

View File

@ -27,7 +27,7 @@
</fieldset>
<input type="submit" id="login" value="Login"
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>
</div>
</div>

View File

@ -16,6 +16,10 @@
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 org.junit.Test;
@ -33,9 +37,8 @@ import org.springframework.http.ResponseEntity;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* Basic integration tests for demo application.
@ -45,7 +48,7 @@ import static org.junit.Assert.assertTrue;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SampleWebSecureApplication.class)
@WebAppConfiguration
@IntegrationTest("server.port:0")
@IntegrationTest({ "server.port:0", "security.enable_csrf:false" })
@DirtiesContext
public class SampleSecureApplicationTests {
@ -60,8 +63,27 @@ public class SampleSecureApplicationTests {
"http://localhost:" + this.port, HttpMethod.GET, new HttpEntity<Void>(
headers), String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertTrue("Wrong body (title doesn't match):\n" + entity.getBody(), entity
.getBody().contains("<title>Login"));
assertTrue("Wrong body (title doesn't match):\n" + entity.getBody(),
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