Below is my test class. The hello-world endpoint simply returns an HTML page containing text i.e. Hello Stranger!
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class HelloWorldTest {
#Autowired
private HelloWorldController controller;
#Autowired
private TestRestTemplate restTemplate;
#LocalServerPort
private int port;
#Test
public void contextLoads() throws Exception {
assertThat(controller).isNotNull();
}
#Test
public void greetingShouldReturnDefaultMessage() throws Exception {
String baseUrl = "http://localhost:" + port;
assertThat(this.restTemplate.getForObject(baseUrl+"/hello-world", String.class))
.contains("Hello Stranger!");
}
}
This is my Security Config:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
}
It simply redirects all authenticated users to the login page
I have tried adding #WithMockUser annotation or adding another security config class in my test directory to override the default config. But so far nothing has seemed to work.
Any help or suggestions on documentation to read is appreciated!
Another way to do it that worked for me was to override the normal security configation for running the integration test like so:
#TestConfiguration
#Order(-101)
#EnableWebSecurity
class TestSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity security) throws Exception {
security.httpBasic().and().formLogin().disable();
}
}
I have managed to solve this issue by first creating another web security config without requiring login/authorization, then by adding #Profile to my config class and production/dev/test profile via application.properties in my test directory (i.e. adding "spring.profiles.active=test").
Not sure if this is the best way to solve this issue, but it works for now.
Related
I am setting up an app with 2 differents users:
one from my ldap that can connect with cas authentication
one external, hard coded with a simple formlogin
I created 2 Security configurations:
External User Configuration:
#EnableWebSecurity
#Configuration
#RequiredArgsConstructor
#Order(1)
public class SecurityVanuatuConfiguration extends WebSecurityConfigurerAdapter {
#Bean
#Override
public UserDetailsService userDetailsService() {
UserDetails user =
User.withUsername("user")
.password("{noop}user")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable().antMatcher("/user/**")
.authorizeRequests()
.requestMatchers(SecurityUtils::isFrameworkInternalRequest).permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage(LOGIN_URL).permitAll()
.loginProcessingUrl(LOGIN_PROCESSING_URL)
.failureUrl(LOGIN_FAILURE_URL)
.successHandler(successHandler)
.failureHandler(successHandler)
.and().logout().logoutSuccessUrl(LOGOUT_SUCCESS_URL);
}
Cas Configuration:
#EnableWebSecurity
#Configuration
#RequiredArgsConstructor
#Order(2)
public class SecurityCasConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().antMatcher("/admin/**")
.authorizeRequests()
.requestMatchers(SecurityUtils::isFrameworkInternalRequest).permitAll()
.anyRequest().authenticated()
.and()
.httpBasic()
.authenticationEntryPoint(authenticationEntryPoint())
.and()
.addFilter(casAuthenticationFilter())
.addFilterBefore(casLogoutFilter(), CasAuthenticationFilter.class);
}
#Bean
public SingleSignOutFilter casLogoutFilter() {
SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
return singleSignOutFilter;
}
// if I remove this bean, external configuration works
#Bean
public CasAuthenticationProvider casAuthenticationProvider() {
CasAuthenticationProvider provider = new CasAuthenticationProvider();
provider.setServiceProperties(serviceProperties());
provider.setTicketValidator(new Cas30ServiceTicketValidator("https://sso.unc.nc/cas"));
provider.setKey("cas");
provider.setAuthenticationUserDetailsService(successHandler);
return provider;
}
Each configuration work when it's alone, but when there is both, the external doesn't work.
It seems that the CasAuthenticationProvider bean prevent the formLogin to work and I endup in the FailureHandler.
Here is the error:
org.springframework.security.authentication.ProviderNotFoundException: No AuthenticationProvider found for org.springframework.security.authentication.UsernamePasswordAuthenticationToken
How can I make these 2 configuration work together?
when you mark something as #Bean you are putting it in the pool of beans that spring can use to inject into classes if they need it.
you have registered
#Bean
public CasAuthenticationProvider casAuthenticationProvider() {
...
}
as a #Bean which is an AuthenticationProvider. When you register this using #Bean it will get picked up by everyone that needs it, meaning that both your WebSecurityConfigurerAdapter will use it.
Spring has no way of knowing that you only want this is in one of your security configurations and not the other.
you have to explicitly define that you want it in one and not the other by removing #Bean and setting it manually.
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(casAuthenticationProvider());
}
public CasAuthenticationProvider casAuthenticationProvider() {
...
}
You have to always decied if you want to set something manually, or use #Bean and let spring inject it for you.
So for instance, you are registering a filter (casLogoutFilter) setting it manually but also defining it as #Bean telling spring to inject it into the filter chain, which sort of doesn't make sense.
Spring boot security allow anonymous user
I am trying configure Spring Boot Security to allow anonymous user reach all URLs except one. By default user and generated security password by Spring.
I need just one page for maintanance application
I already tried a lot tips and tutorials.
1
2
3
4
And others.
But Spring still required authetification for all pages.
My current security config
#EnableWebSecurity
#Configuration
public class SecurityConf extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.anyRequest()
.anonymous()
.antMatchers("/secure")
.authenticated();
}
}
Web configuration
#Configuration
#EnableWebMvc
#EnableAsync
public class WebConf implements WebMvcConfigurer {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
WebMvcConfigurer.super.addResourceHandlers(registry);
registry.addResourceHandler("/webjars/**").addResourceLocations("/webjars/**");
}
#Bean
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(5);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("Asynchronous Process-");
executor.initialize();
return executor;
}
}
And main method
#ComponentScan
#SpringBootApplication
#EnableScheduling
public class MainServiceApplication {
public static void main(String[] args) {
SpringApplication.run(MainServiceApplication.class, args);
}
}
I tried
.permitAll()
.anonymous()
without success.
Edit 1
Project structure
Project structure
Edit 2
Project structure
#ComponentScan()
#SpringBootApplication
#EnableScheduling
public class MainServiceApplication {
public static void main(String[] args) {
SpringApplication.run(MainServiceApplication.class, args);
}
}
Login page
Solved by move config package. Spring did not scan configuration package.
You may need to change the order. Possible issue is antMatchers("/secure").authenticated() has no effect due to /secure endpoint will be considerd in the anyRequest(). Also make sure SecurityConf is in correct package as required for scanning.
#EnableWebSecurity
#Configuration
public class SecurityConf extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/secure").authenticated()
.anyRequest().anonymous();
}
}
OR
#EnableWebSecurity
#Configuration
public class SecurityConf extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/secure").authenticated()
.anyRequest().permitAll();
}
}
UPDATE
You need to create a configs package inside cz.lh.main_service and SecurityConf and WebConf should be part of the cz.lh.main_service.configs
OR
You can use #ComponentScan and can specify the current package in which you have SecurityConf and WebConf
#ComponentScan(“your-config-package”)
According the Spring Security Reference section 5.7 it should be possible to define more than one security adapter.
I try to do the same but without success. After a server reboot, the first x times the API works fine with basic auth, but after a couple of times I'm redirected to the login (form) page, this should only happen for our web app, not for the API calls.
My code:
#EnableWebSecurity
public class MultiHttpSecurityConfig {
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Autowired
private Environment env;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().
withUser("admin").password("pw_test").roles(API_ROLE);
}
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/services/**")
.authorizeRequests()
.anyRequest().hasRole(API_ROLE)
.and()
.httpBasic()
.and()
.csrf()
.disable();
}
}
#Configuration
#Order(2)
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Autowired
private Environment env;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
auth.eraseCredentials(false);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// LDAP FORM AUTHENTICATION
http.authorizeRequests()
.antMatchers("/login.html").permitAll()
.antMatchers("/css/**").permitAll()
.antMatchers("/js/**").permitAll()
.antMatchers("/images/**").permitAll()
.anyRequest().authenticated()
.and().formLogin()
.failureUrl("/login.html?error=1")
.loginPage("/login.html")
.loginProcessingUrl("/j_spring_security_check")
.defaultSuccessUrl("/success.html")
.usernameParameter("j_username")
.passwordParameter("j_password")
.permitAll();
http.csrf().disable();
// iFRAMES SETTINGS
http
.headers()
.frameOptions().sameOrigin()
.httpStrictTransportSecurity().disable();
// HTTPS
http
.requiresChannel()
.anyRequest()
.requiresSecure();
//MAP 8080 to HTTPS PORT
http.portMapper().http(8080).mapsTo(443);
}
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
CustomLdapAuthenticationProvider provider = new CustomLdapAuthenticationProvider(env.getProperty("ldap.domain"), env.getProperty("ldap.url"), env.getProperty("ldap.base"));
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
}
}
Any idea?
I'm using Spring Boot version 1.4.1-RELEASE and Spring Security version 4.1.3-RELEASE.
You use the same AuthenticationManager for both configurations, because you autowire the same AuthenticationManagerBuilder.
See Spring Security Architecture:
#Configuration
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {
... // web stuff here
#Autowired
public void initialize(AuthenticationManagerBuilder builder, DataSource dataSource) {
builder.jdbcAuthentication().dataSource(dataSource).withUser("dave")
.password("secret").roles("USER");
}
}
This example relates to a web application, but the usage of AuthenticationManagerBuilder is more widely applicable (see below for more detail on how web application security is implemented). Note that the AuthenticationManagerBuilder is #Autowired into a method in a #Bean - that is what makes it build the global (parent) AuthenticationManager. In contrast if we had done it this way:
#Configuration
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {
#Autowired
DataSource dataSource;
... // web stuff here
#Override
public void configure(AuthenticationManagerBuilder builder) {
builder.jdbcAuthentication().dataSource(dataSource).withUser("dave")
.password("secret").roles("USER");
}
}
(using an #Override of a method in the configurer) then the AuthenticationManagerBuilder is only used to build a "local" AuthenticationManager, which is a child of the global one.
I have working REST API under Spring 4 using Basic authentication. These REST services are under /api/v1/** URL. However, I want to add another set of REST endpoints under different url /api/v2/**, but protected with token-based authentication.
Is it possible to do this with one servlet ? How to configure Spring Security to use different forms of authentication for different URLs ?
Thank you.
Here's a code sample in Java config that uses UserDetailsService and has different security configurations for different URL endpoints:
#Configuration
#EnableWebMvcSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsService userDetailsService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/v1/**")
.httpBasic()
.realmName("API")
.and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/v1/**").authenticated();
}
}
#Configuration
#Order(2)
public static class ApiTokenSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/v2/**")
/* other config options go here... */
}
}
}
My spring boot application has an Application class. When I run it (as an application), it launches itself within an embedded servlet container (Tomcat, in my case). Somehow (through Application's #annotations, I suppose), WebSecurityConfig (extending WebSecurityConfigurerAdapter) in the same package is loaded.
WebSecurityConfig contains two important blocks of configuration information:
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
#EnableGlobalMethodSecurity(prePostEnabled = true) // enables method-level role-checking
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.userSearchBase("CN=Users,DC=some,DC=domain,DC=com")
.userSearchFilter("(sAMAccountName={0})")
.groupSearchBase("OU=Groups,DC=some,DC=domain,DC=com")
.groupSearchFilter("(member={0})")
.contextSource()
.managerDn("cn=ad-bind,cn=users,dc=some,dc=domain,dc=com")
.managerPassword("APASSWORD!")
.url("ldaps://some.domain.com:636");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("***************** WebSecurityConfig.configure *************************");
http.csrf().disable();
http
.headers()
.frameOptions()
.disable();
http
.authorizeRequests()
.antMatchers("/resources/images/*", "/me", "/products", "/product/**", "/offerings", "/offering/**", "/client/**")
.permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").defaultSuccessUrl("/me")
.permitAll()
.and()
.logout()
.permitAll();
http.logout().logoutSuccessUrl("/me");
}
}
configureGlobal() contains the configuration for our internal LDAP system and it works just fine.
configure() specifies which URLs are public, which are only to be shown to logged-in users and which relative URLs to send users to as they log in.
Now I'm into integration testing and have written some methods to test controllers that do not require authentication. Those tests work as expected. The Application class fires up and the tests execute against it.
But now I want to test controller methods that DO require authentication. The way I think this is accomplished is by telling the test class to fire up an alternative Application class (TestApplication, in my case) and WebSecurityConfig that creates dummy users:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = TestApplication.class) // fires up with TestApplication.class instead of Application.class
#WebAppConfiguration
public class ProductControllerTests {
// test methods here, this time with username/password included
}
#Configuration
#EnableAutoConfiguration
public class TestApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(applicationClass, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(applicationClass);
}
private static Class<TestApplication> applicationClass = TestApplication.class;
}
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("testuser").password("userpass").roles("USER");
auth.inMemoryAuthentication().withUser("testadmin").password("adminpass").roles("ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("***************** WebSecurityConfig.configure *************************");
http.csrf().disable();
http
.headers()
.frameOptions()
.disable();
http
.authorizeRequests()
.antMatchers("/resources/images/*", "/me", "/products", "/product/**", "/offerings", "/offering/**", "/client/**")
.permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").defaultSuccessUrl("/me")
.permitAll()
.and()
.logout()
.permitAll();
http.logout().logoutSuccessUrl("/me");
}
}
So my question is: When I execute the unit test class, I believe TestApplication is firing. However, it is NOT picking up the alternative WebSecurityConfig class and its auth.inMemoryAuthentication() test users. How do I force my application to use one WebSecurityConfig when running the application normally, but a different WebSecurityConfig when running the unit tests?
You can configure your TestApplication to include just the beans that you would like to test. In other words, make sure that your WebSecurityConfig is not part of the test configuration. If you read the javadoc of #SpringBootApplication you will notice that it is a composite annotation that consists of (among others) the #ComponentScan annotation. Consequently your Application and your TestApplication will perform a recursive scan from the package in which the class is located. The Spring reference docs has a specific chapter about Using filters to customize scanning.
Alternatively, if you are using Spring Security version 4 or greater you may find the additions of #WithMockUser and #WithUserDetails interesting.
In your security configuration class, add #Profile annotation to disable in unit test profile. like:
#Configuration
#Profile("!" + Constants.SPRING_PROFILE_UNITTEST)
public class WebSecurityConfig { ....}
And let your another security config for test just in test dir.