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:
parent
b1969f5095
commit
5e3cc95ccf
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
spring.thymeleaf.cache: false
|
||||
debug: true
|
||||
security.basic.enabled: false
|
||||
security.basic.enabled: false
|
||||
# demo only:
|
||||
security.user.password: password
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue