I have a Spring-boot app that is using Spring-security, configured with Java-config. Ideally, I will have a customer UserDetailsService so I can add/modify users. Until then I am failing to configure this correctly.
I am using the following dependencies:
compile("org.springframework.boot:spring-boot-starter-web:1.1.1.RELEASE")
compile("org.springframework.boot:spring-boot:1.0.1.RELEASE")
compile("org.springframework.boot:spring-boot-starter-thymeleaf")
compile("org.springframework.boot:spring-boot-starter-data-jpa:1.1.1.RELEASE")
compile("org.springframework.security:spring-security-web:4.0.0.M1")
compile("org.springframework.security:spring-security-config:4.0.0.M1")
I have the following Configurations
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
#Configuration
#EnableWebMvcSecurity
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {
#Autowired
private DataSource datasource;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/resources/**").permitAll()
.antMatchers("/css/**").permitAll();
http
.formLogin().failureUrl("/login?error")
.defaultSuccessUrl("/")
.loginPage("/login")
.permitAll()
.and()
.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).logoutSuccessUrl("/")
.permitAll();
http
.authorizeRequests().anyRequest().authenticated();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
JdbcUserDetailsManager userDetailsService = jdbcUserService();
// userDetailsService.setDataSource(datasource);
// PasswordEncoder encoder = new BCryptPasswordEncoder();
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
auth.jdbcAuthentication().dataSource(datasource);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public org.springframework.security.provisioning.JdbcUserDetailsManager jdbcUserService() throws Exception {
JdbcUserDetailsManager jdbcUserDetailsManager = new JdbcUserDetailsManager();
jdbcUserDetailsManager.setDataSource(datasource);
jdbcUserDetailsManager.setAuthenticationManager(authenticationManagerBean());
return jdbcUserDetailsManager;
}
}
#Order(Ordered.HIGHEST_PRECEDENCE)
#Configuration
public class AuthenticationSecurity extends GlobalAuthenticationConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth
.jdbcAuthentication()
.dataSource( dataSource );
}
}
So, I realize that my configurations are wrong but not really sure how to best fix them. The symptoms are that when I log into my Thymeleaf UI, the session never exires.
I have used various online resources for my spring-security learning & implementation. Unfortunately, I am still not grasping why this is not correct.
You appear to be configuring 3 filter chains (3 WebSecurityConfigurerAdapters) but only one of them configures the HttpSecurity. That's probably not what you intended to do. Maybe consolidate down to WebSecurityConfigurerAdapter and one GlobalAuthenticationConfigurerAdapter and see where it gets you.
Related
I'm developing a Spring Boot application where I'm trying to use the #PreAuthorize annotation to filter access to the User resource so that an User only can access to his own resource. Here is my UserRepository:
#Repository
#RepositoryRestResource
public interface MyUserRepository extends PagingAndSortingRepository<MyUser, UUID> {
#Override
#PreAuthorize("principal.getId().equals(#uuid)")
MyUser findOne(UUID uuid);
MyUser findByUsername(#Param("username") String username);
MyUser findByEmail(#Param("email") String email);
}
You can see the stack trace here.
Somewhere in the stacktrace references the class WebSecurityConfig line 42. This is the method configureAuthentication of the following class:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private RestAuthenticationEntryPoint unauthorizedHandler;
#Autowired
private BasicUserDetailsService userDetailsService;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(this.userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
JwtAuthenticationTokenFilter authenticationTokenFilter = new JwtAuthenticationTokenFilter();
authenticationTokenFilter.setAuthenticationManager(authenticationManagerBean());
return authenticationTokenFilter;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// we don't need CSRF because our token is invulnerable
.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
// don't create session
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.antMatchers(HttpMethod.POST, "/myUsers").permitAll()
.antMatchers(HttpMethod.PUT).authenticated()
.antMatchers(HttpMethod.POST).authenticated()
.antMatchers(HttpMethod.DELETE).authenticated()
.antMatchers(HttpMethod.PATCH).authenticated()
.anyRequest().permitAll();
// Custom JWT based security filter
http.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
http.addFilterBefore(new CORSFilter(), ChannelProcessingFilter.class);
}
}
Thank you!
Updating to Spring boot 1.4 has solved the problem.
Trying to make spring security oauth2 and form login work in a spring boot application.
I took cues from
https://github.com/spring-projects/spring-security-oauth/tree/master/samples/oauth2/sparklr
and
https://github.com/royclarkson/spring-rest-service-oauth/issues/11
Below is my configuration
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Inject
private AjaxAuthenticationSuccessHandler ajaxAuthenticationSuccessHandler;
#Inject
private AjaxAuthenticationFailureHandler ajaxAuthenticationFailureHandler;
#Inject
private AjaxLogoutSuccessHandler ajaxLogoutSuccessHandler;
#Inject
private UserDetailsService userDetailsService;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Inject
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean
public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
return new SecurityEvaluationContextExtension();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/scripts/**/*.{js,html}")
.antMatchers("/bower_components/**")
.antMatchers("/i18n/**")
.antMatchers("/assets/**")
.antMatchers("/swagger-ui/index.html")
.antMatchers("/api/register")
.antMatchers("/api/activate")
.antMatchers("/api/account/reset_password/init")
.antMatchers("/api/account/reset_password/finish")
.antMatchers("/api/home/**")
.antMatchers("/api/product/**")
.antMatchers("/test/**")
.antMatchers("/devadmin/**")
.antMatchers("/signin")
.antMatchers("/static/api-guide.html");
}
#Override
#Order(Ordered.HIGHEST_PRECEDENCE)
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable().authorizeRequests().antMatchers("/web/**","/for","/admin","/here").authenticated()
.and()
.formLogin()
.loginProcessingUrl("/web/authentication")
.successHandler(ajaxAuthenticationSuccessHandler)
.failureHandler(ajaxAuthenticationFailureHandler)
.usernameParameter("j_username")
.passwordParameter("j_password")
.permitAll()
.and()
.logout()
.logoutUrl("/web/logout")
.logoutSuccessHandler(ajaxLogoutSuccessHandler)
.deleteCookies("JSESSIONID")
.permitAll()
.and()
.exceptionHandling()
;
}
#Configuration
#EnableAuthorizationServer
#EnableConfigurationProperties(SecurityConfigurationProperties.class)
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter implements EnvironmentAware {
private static final String ENV_OAUTH = "authentication.oauth.";
private static final String PROP_CLIENTID = "clientid";
private static final String PROP_SECRET = "secret";
private static final String PROP_TOKEN_VALIDITY_SECONDS = "tokenValidityInSeconds";
private RelaxedPropertyResolver propertyResolver;
#Inject
private DataSource dataSource;
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Inject
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints
.tokenStore(tokenStore())
.authenticationManager(authenticationManager);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient(propertyResolver.getProperty(PROP_CLIENTID))
.scopes("read", "write")
.authorities(AuthoritiesConstants.ADMIN, AuthoritiesConstants.USER)
.authorizedGrantTypes("password", "refresh_token")
.secret(propertyResolver.getProperty(PROP_SECRET))
.accessTokenValiditySeconds(propertyResolver.getProperty(PROP_TOKEN_VALIDITY_SECONDS, Integer.class, 1800));
}
#Override
public void setEnvironment(Environment environment) {
this.propertyResolver = new RelaxedPropertyResolver(environment, ENV_OAUTH);
}
}
#Order(2)
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.requestMatchers().antMatchers("/api/**")
.and()
.authorizeRequests().antMatchers("/api /**").access("#oauth2.hasScope('read') and hasRole('ROLE_USER')");
// #formatter:on
}
}
}
With above configuration oauth2 login works fine when I try authenticating via
/oauth/token
However login via
/web/authentication
always shows
405 Request method 'POST' not supported
usernamepasswordauthenticationfilter is not getting invoked.
After commenting the ResourceServerConfiguration part in above code,
form login via
/web/authentication
is working fine.
Also I can see usernamepasswordauthenticationfilter being invoked from the logs.
My question is that even the end points are configured different for oauth2 resource server and form login, why oauth2 is overriding the httpsecurity for formlogin and why not usernamepasswordauthenticationfilter being called when resource server is configured?
We would like to apply Oauth2 based security for the Rest Controllers while the rest of the application will have Spring Security. Will that be possible? Can you provide any examples please?
It seems like WebSecurityConfigurerAdapter and ResourceServerConfigurerAdapter conflicting when both configured.
Thank you in advance.
Yes it's possible. Here the example template configuration code is given. Please change the required configs to your need. The key is to define Sub static classes of configuration with different order. Here i have considered any requests which is orginating from \api as a REST API call.
I have not checked the code by compiling it.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, proxyTargetClass = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
#Order(1)
#Configuration
public static class ApiWebSecurityConfig extends OAuth2ServerConfigurerAdapter{
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//Write the AuthenticationManagerBuilder codes for the OAuth
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.antMatcher("/api/**")
.authorizeRequests()
.anyRequest().authenticated()
.and()
.apply(new OAuth2ServerConfigurer())
.tokenStore(new InMemoryTokenStore())
.resourceId(applicationName);
}
}
}
#Order(2)
#Configuration
public static class FormWebSecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
//Write the AuthenticationManagerBuilder codes for the Normal authentication
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable() //HTTP with Disable CSRF
.authorizeRequests() //Authorize Request Configuration
.anyRequest().authenticated()
.and() //Login Form configuration for all others
.formLogin()
.loginPage("/login").permitAll()
.and() //Logout Form configuration
.logout().permitAll();
}
}
}
I'm having 2 classes which extends WebSecurityConfigurerAdapter. And can't make them work together.
The idea is as follows:
Have one WebSecurityConfigurerAdapter which only adds custom filter to security chain. The filter does some custom authentication and saves Authentication into SecurityContext. This generally works fine. Configured as follows (imports omitted):
#Order(1)
#Configuration
#EnableWebMvcSecurity
public class BestSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private BestPreAuthenticationFilter ssoAuthenticationFilter;
#Bean
protected FilterRegistrationBean getSSOAuthenticationFilter() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(ssoAuthenticationFilter);
// Avoid include to the default chain
filterRegistrationBean.setEnabled(false);
return filterRegistrationBean;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterAfter(ssoAuthenticationFilter, SecurityContextPersistenceFilter.class);
}
#Configuration
protected static class AuthenticationConfiguration extends
GlobalAuthenticationConfigurerAdapter {
#Autowired
private BestAuthenticationProvider authenticationProvider;
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
}
}
I want the above to be kind of library class which anyone can include via #ComponentScan and get the custom authentication sorted. Obviously they want to provide custom HttpSecurity to secure edpoints. Trying something like:
#Configuration
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/testUrl").hasRole("NON_EXISTING")
.anyRequest().authenticated();
}
}
Obviously the test URL should not be accessible as my user is not member of role NON_EXISTING. Unfortunatelly she is.
If I move the security authorizeRequests() part to the configuration class form 1. next to adding the security filter then it blocks the access as expected. But in my case it looks like the second configuration is ignored.
I also debugged the configure() methods and noticed that HttpSecurity is not the same object which smells a bit.
Any tips how can I make this work much appreciated.
Sum up of the goal:
have one WebSecurityConfigurerAdapter which adds the filter and is hidden from the user of the library
let the user define her own custom endpoint security
Spring boot 1.1.6-RELEASE
Define a special interface
public interface ServiceWebSecurityConfigurer {
void configure(HttpSecurity http) throws Exception;
}
Then have just one ConfigurerAdapter:
public class MyConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Autowired(required = false)
ServiceWebSecurityConfigurer serviceSecConfig;
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests(). // whatever
if (serviceSecConfig != null) serviceSecConfig.configure(http);
http.authorizeRequests(). // whatever
}
}
and then just implement ServiceWebSecurityConfigurer elsewhere when needed. There can be multiple implementations as well, just autowire them as list and iterate and use them all in your main configuration.
So one option I just found is:
Remove the #Configuration annotation from the first bean
And change the 2. to:
#Configuration
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfig extends BestSecurityConfig { //Note the changed extend !
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http); // Merge of the 2 HTTP configurations
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/testUrl").hasRole("NON_EXISTING")
.anyRequest().authenticated();
}
}
Any comments on whether this is right or wrong approach much appreciated
Edit: After few years I still didn't find other way but I like this way more and more. Even in the default case you extend the abstract WebSecurityConfigurerAdapter there is no reason why some other layer of abstraction can't provide another abstract extension which provides meaningful defaults.
I founded (in my opinion) a cleaner way of structuring some default configurations and make it simple to integrate in new projects by using Custom DSLs.
I'm using it to config JWT authentication filters, but i think a CORS filter is more simple and didactic:
public class CustomCorsFilterDsl extends AbstractHttpConfigurer<CustomCorsFilterDsl, HttpSecurity> {
#Override
public void init(HttpSecurity http) throws Exception {
//your init code here, no needed in this case
}
#Override
public void configure(HttpSecurity http) throws Exception {
CorsFilter corsFilter = corsFilter(corsProperties);
http.addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class);
}
private CorsFilter corsFilter(CorsProperties corsProperties) {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("http://localhost:9000");
config.addAllowedHeader("*");
config.addAllowedMethod("GET, POST, PUT, PATCH, DELETE");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
public static CustomCorsFilterDsl dsl() {
return new CustomCorsFilterDsl();
}
}
And in your WebSecurityConfig you can use it like this:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.exceptionHandling()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/foo/**").permitAll()
//... your configurations
.antMatchers("/**").authenticated()
.and()
.apply(CustomCorsFilterDsl.dsl());
}
}
And you accomplished your objective of having libraries with default configurations independent of your projects code, in a more clear way, because you can visualize in the project's WebSecurityConfig a custom CORS entry.
I have updated recently to spring-security-3.2.0.RC2 from RC1, and according to the blog post the QUIESCENT_POST_PROCESSOR have been removed. Before I used to create an AuthenticationManager bean like this below:
#Bean(name = {"defaultAuthenticationManager", "authenticationManager"})
public AuthenticationManager defaultAuthenticationManager() throws Exception {
return new AuthenticationManagerBuilder(null).userDetailsService(context.getBean(MyUserDetailsService.class)).passwordEncoder(new Md5PasswordEncoder()).and().build();
}
so I've changed it to:
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws BeansException, Exception {
auth.userDetailsService(context.getBean(MyUserDetailsService.class)).passwordEncoder(new Md5PasswordEncoder());
}
but unfortunately I can't get hold of the AuthenticationManager any more. I'm also creating RememberMeAuthenticationFilter like this:
#Bean(name = { "defaultRememberMeAuthenticationFilter", "rememberMeAuthenticationFilter" })
protected RememberMeAuthenticationFilter defaultRememberMeAuthenticationFilter() throws Exception {
return new RememberMeAuthenticationFilter(defaultAuthenticationManager(), context.getBean(DefaultRememberMeServices.class));
}
so as you can see I need to get hold of AuthenticationManager, but I don't know how???
You really shouldn't need to get a hold of the AuthenticationManager. From the javadoc of HttpSecurity the following should work just fine:
#Configuration
#EnableWebSecurity
public class RememberMeSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/**").hasRole("USER")
.and()
.formLogin()
.permitAll()
.and()
// Example Remember Me Configuration
.rememberMe();
}
}
Of course if you are using global AuthenticationManager, this will work too:
#Configuration
#EnableWebSecurity
public class RememberMeSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/**").hasRole("USER")
.and()
.formLogin()
.permitAll()
.and()
// Example Remember Me Configuration
.rememberMe();
}
}
The only difference is the first example isolates the AuthenticationManger to the HttpSecurity where as the second example will allow the AuthenticationManager to be used by global method security or another HttpSecurity (WebSecurityConfigurerAdapter).
The reason this works is the .rememberMe() will automatically find the AuthenticationManager, UserDetailsService and use that when creating the RememberMeAuthenticationFilter. It also creates the appropriate RememberMeServices so there is no need to do that. Of course there are additional options on .rememberMe() if you want to customize it, so refer to the RememberMeConfigurer javadoc for additional options.
If you REALLY need a reference to the AuthenticationManager instance you can do the following:
#Configuration
#EnableWebSecurity
public class RememberMeSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationManagerBuilder auth;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
#Bean
public AuthenticationManager authenticationManager() {
return auth.build();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/**").hasRole("USER")
.and()
.formLogin()
.permitAll()
.and()
// Example Remember Me Configuration
.rememberMe();
}
}
If you want to have multiple AuthenticationManager instances, you can do the following:
#Autowired
private ObjectPostProcessor<Object> opp;
public AuthenticationManager authenticationManager()
throws Exception {
return new AuthenticationManagerBuilder(opp)
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.and()
.build();
}
public AuthenticationManager authenticationManager2()
throws Exception {
return new AuthenticationManagerBuilder(opp)
.inMemoryAuthentication()
.withUser("admin").password("password").roles("ADMIN").and()
.and()
.build();
}
NOTE This is almost the same as you had things before hand except instead of using the QUIESENT_POST_PROCESSOR you are using a real ObjectPostProcessor using the #Autowired annotation
PS: Thanks for giving RC2 a try!
The way to expose and get access to the AuthenticationManager bean is as follows:
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception
{
return super.authenticationManagerBean();
}