configure(HttpSecurity http) and configure(AuthenticationManagerBuilder) get ignored with certain annotations in WebSecurityConfigurerAdapter - spring

I'm trying to secure my spring application that has different user roles. While the Authentication part is set and works flawlessly, I realised during the implementation of the Authorisation part that with certain annotations, one of the two overrides methods inside my SecurityConfiguration extends WebSecurityConfigurerAdapter class, gets ignored.
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private WebApplicationContext applicationContext;
private CredentialsService userDetailsService;
#Autowired
private DataSource dataSource;
#PostConstruct
public void completeSetup() {
userDetailsService = applicationContext.getBean(CredentialsService.class);
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login")
.permitAll()
.and()
.formLogin()
.permitAll()
.and()
.httpBasic()
.disable()
.authorizeRequests()
.antMatchers("/admin", "/admin/**")
.hasRole("ADMIN")
.and()
.authorizeRequests()
.antMatchers("/employee", "/employee/**")
.hasRole("EMPLOYEE")
.and()
.authorizeRequests()
.antMatchers("/customer", "/customer/**")
.hasRole("CUSTOMER");
}
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(encoder())
.and()
.authenticationProvider(authenticationProvider())
.jdbcAuthentication()
.dataSource(dataSource);
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
final DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(encoder());
return authProvider;
}
#Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder(12);
}
}
Now the problem is the following, as it is, this class authenticate my users but has one major drawback: the
configure(final HttpSecurity http) throws Exception {
gets completely ignored.
On the other side though, if I add the #Configuration annotation on top of my class, the
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
gets completely ignored, hence will break the authorisation as it won't be able to call the getUsername() and getPassword on my custom UserDetailsService implementation.
As you can see, I've used a DaoAuthenticationProvider instance as authenticationProvider, since my application retrieve the users/password from an external database.
The quick fix I adopted right now it's the addition of the following method on my main class
#EnableGlobalMethodSecurity(
securedEnabled = true,
jsr250Enabled = true,
prePostEnabled = true
)
and the use of the #Secured annotation on my restricted controllers. That works, but I'd like to understand why Spring has such strange behaviour and what step can I take to address these problems.

Since you are assigning roles to your users, use the syntax
.antMatchers("/admin", "/admin/**")
.hasRole("ADMIN")
OR
.antMatchers("/admin", "/admin/**")
.hasAuthority("ROLE_ADMIN")
Roles are just stored as authorities with the "ROLE_" prefix.
So the role "ADMIN" is equivalent to the authority "ROLE_ADMIN".
EDIT 1
You can also simplify your configuration to make it clear where everything is coming from.
Since you UserDetailsService (CredentialsService) is already a bean, it will be picked up automatically by Spring Security.
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
// The password encoder should be a bean
#Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder(12);
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login")
.permitAll()
.and()
.formLogin()
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/admin", "/admin/**")
.hasRole("ADMIN")
.and()
.authorizeRequests()
.antMatchers("/manager", "/manager/**")
.hasRole("MANAGER")
.and()
.authorizeRequests()
.antMatchers("/customer", "/customer/**")
.hasRole("CUSTOMER");
}
}

Related

spring security 2 login form call one another without authenticating

I am writing a spring security code with 2 login forms and 2 login URLs. The problem is that when I pres on sign in button on any login form without even true authenticating it directs me to the other login form. When I try the other login form the same happens. If someone has any clue is welcome to comment.
My code is:
#Order(1)
#Configuration
#EnableWebSecurity
//#Order(Ordered.LOWEST_PRECEDENCE)
public class SecurityConfigurationAdmin extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/adminlogin*")
.authorizeRequests()
.antMatchers(
"/login2",
"/login",
"/registration**",
"/js/**",
"/css/**",
"/img/**").permitAll()
.antMatchers("/adminlogin*").hasRole("USER2")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login2").permitAll()
// .loginProcessingUrl("/login22")
.usernameParameter("username2")
.passwordParameter("password2")
.successForwardUrl("/adminlogin")
.defaultSuccessUrl("/adminlogin",true)
// .failureUrl("/login2")
.permitAll()
.and()
.logout()
.invalidateHttpSession(true)
.clearAuthentication(true)
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/?logout")
.permitAll();
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password("{noop}admin").roles("USER2");
}
and :
#Order(2)
#Configuration
#EnableWebSecurity
//#Order(Ordered.HIGHEST_PRECEDENCE)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserService userService;
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/librarianlogin*")
.authorizeRequests()
.antMatchers(
"/login",
"/login2",
"/registration**",
"/js/**",
"/css/**",
"/img/**").permitAll()
.antMatchers("/librarianlogin").hasRole("USER")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
// .loginProcessingUrl("/login1")
.successForwardUrl("/librarianlogin")
.defaultSuccessUrl("/librarianlogin",true)
// .failureUrl("/login")
.permitAll()
.and()
.logout()
.invalidateHttpSession(true)
.clearAuthentication(true)
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/?logout")
.permitAll();
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider auth = new DaoAuthenticationProvider();
auth.setUserDetailsService(userService);
auth.setPasswordEncoder(passwordEncoder());
return auth;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
}
I m sure that everything that is missing is in the WebSecurityConfigurerAdapter classes since when I compile the code separately from the 2 log in forms they work perfectly. When i combine them together something goes wrong.
Looking at your configuration, it appears that you want to have two separate user bases, one for administrators, and one for librarians. You are using different login pages in order to know which is which.
To do this, you need to have multiple filter chains, which is how you've already begun. I'd suggest some tweaks, though.
First, the top-level antMatcher call is for segmenting out your application. For example, it's common for all admin pages to be served under the /admin path. In that case, you can do:
#Order(1)
#Configuration
public class AdminSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/admin/**")
.authorizeRequests((authz) -> authz
.mvcMatchers("/error").permitAll()
.anyRequest().authenticated()
)
.formLogin((form) -> form
.loginPage("/admin/login").permitAll()
);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
UserDetailsService adminUsers = // ... construct
auth.userDetailsService(adminUsers);
}
}
for the admin's part of the site, and:
#Order(2)
#Configuration
public LibrarianSecurityConfig extends WebSecurityConfigurerAdatper {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/**")
.authorizeRequests((authz) -> authz
.anyRequest().authenticated()
)
.formLogin((form) -> form
.loginPage("/login").permitAll()
);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
UserDetailsService users = // ...
auth.userDetailsService(adminUsers);
}
}
for the librarian part.
Some things to keep in mind:
Order matters. When you have multiple filter chains, Spring Security will pick the first chain whose matcher matches the request path. So, /admin/** goes first since it is a smaller expression than /**
You need to configure your front end to support CSRF since Spring Security expects CSRF tokens by default for any POST request
Permitting /error is important at least while debugging your login setup since otherwise any errors will get swallowed behind the authentication wall
You can find the complete code in this sample.

Spring Security Multiple HTTPSecurity with Different User Details Services Not Working in Spring Boot

I have two types of users: Application User and End User and I have separate tables for these. Now, I want to apply security on these two tables.
I provided custom implementation of UserDetailsService for Application users:
#Component("applicationUserDetailsService")
public class ApplicationUserDetailsService implements UserDetailsService {}
And, I provided another second custom implementation of UserDetailsService for End users:
#Component("endUserDetailsService")
public class EndUserDetailsService implements UserDetailsService {}
Now, in the following code snippet, I have registered two endpoints for both type of users. I have injected both implementation of UserDetailsService and registered by #Overide configure(AuthenticationManagerBuilder auth) method for both application and end user separately.
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
#Import(SecurityProblemSupport.class)
public class SecurityConfiguration {
// Injected via Constructor Injection
private final EndUserDetailsService endUserDetailsService;
private final ApplicationUserDetailsService applicationUserDetailsService;
#Configuration
#Order(1)
public class ApplicationUserSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.OPTIONS, "/**")
.antMatchers("/swagger-ui/index.html")
.antMatchers("/test/**");
}
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.csrf()
.disable()
.addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
.exceptionHandling()
.authenticationEntryPoint(problemSupport)
.accessDeniedHandler(problemSupport)
.and()
.headers()
.frameOptions()
.disable()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.antMatcher("/api/customer/**")
.authorizeRequests()
.antMatchers("/api/customer/authenticate").permitAll()
.antMatchers("/api/customer/**")
.authenticated()
.and()
.apply(securityConfigurerAdapter());
// #formatter:on
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(endUserDetailsService);
}
}
//no #Order defaults to last
#Configuration
public class EndUserSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.OPTIONS, "/**")
.antMatchers("/swagger-ui/index.html")
.antMatchers("/test/**");
}
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.csrf()
.disable()
.addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
.exceptionHandling()
.authenticationEntryPoint(problemSupport)
.accessDeniedHandler(problemSupport)
.and()
.headers()
.frameOptions()
.disable()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/authenticate").permitAll()
.antMatchers("/api/**").authenticated()
.and()
.apply(securityConfigurerAdapter());
// #formatter:on
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(applicationUserDetailsService);
}
}
private JWTConfigurer securityConfigurerAdapter() {
return new JWTConfigurer(tokenProvider);
}
}
And, I'm trying to authenticate the user like this:
//Injected via Constructor Injection
private final AuthenticationManagerBuilder authenticationManagerBuilder;
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginVM.getUsername(), loginVM.getPassword());
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
When the above code snippet is executed, I get the Null Pointer Exception because authenticationManagerBuilder.getObject() returns NULL. And when I use just when implementation of UserDetailService with #Component("userDetailsService") and not set UserDetailService in security config like auth.userDetailsService("..."), it works fine but by that way I can't achieve authentication from multiple tables.
What I want to Achieve:
In simple words, I want spring security to authenticate user from two tables.
requestMatchers() is the call that you need as it allows you to isolate adapters by URL:
#Order(1)
#EnableWebSecurity
class EndUserConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/api/customer/**")
.and()
.authorizeRequests()
.antMatchers("/**").hasRole("CUSTOMER")
.and()
.apply(yourJointConfigurations());
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(endUserDetailsService);
}
}
Regarding calling the AuthenticationManager directly, it would be ideal if you could rely on the existing filter chain to do the work for you. For example, since you are stateless, HTTP Basic might be a better fit for you, which you could apply to both configurations, instead of trying to have a dedicated /authenticate endpoint.

Spring Security - Filter Ordering and Multiple HttpSecurity

I want two diffrent http configurations to come in depending on the url i am entering. For example, when i type in "localhost:8080/HQ/test_web" i want this configuration to come in.
#Configuration
#Order(1)
public static class FirstWaveFilters extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/HQ/test_web/**").anonymous().and().addFilterBefore(new CustomFilter(),BasicAuthenticationFilter.class);
}
}
But, if its anything else, i want this configuration to come in:
#Configuration
#Order(2)
public static class SecondWaveFilters extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().authorizeRequests()
.antMatchers("/h2-console/**").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers(HttpMethod.POST, SIGN_UP_URL).permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
// this disables session creation on Spring Security
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests().antMatchers("/**").permitAll();
http.csrf().disable();
http.headers().frameOptions().disable();
}
}
I have them set up in the same class as the Spring Security doc suggested:
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
private UserDetailsServiceImpl userDetailsService;
private BCryptPasswordEncoder bCryptPasswordEncoder;
public WebSecurity(UserDetailsServiceImpl userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
this.userDetailsService = userDetailsService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
#Configuration
#Order(1)
public static class FirstWaveFilters extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/HQ/test_web/**").anonymous().and().addFilterBefore(new CustomFilter(),BasicAuthenticationFilter.class);
}
}
#Configuration
#Order(2)
public static class SecondWaveFilters extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().authorizeRequests()
.antMatchers("/h2-console/**").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers(HttpMethod.POST, SIGN_UP_URL).permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
// this disables session creation on Spring Security
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests().antMatchers("/**").permitAll();
http.csrf().disable();
http.headers().frameOptions().disable();
}
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
}
But it does not seem to work. Whatever url i enter, only the CUSTOMFILTER() gets called, so only the first configuration. Basically what i am trying to achieve is, if the user enters the first url i want that customfilter() to be the filter the request has to go through, if its any other url, i want it to go the second configuration and the two filters defined there to be the ones the request must go through. Why is this not working ?
http.antMatcher(...) - means, apply this http and all what is configured here when pattern in antMatcher is met.
http.authorizeRequests()... - defines your permissions, if user hit that endpoint he should has "ADMIN", "logged" etc.
In your FirstWaveFilters you have to start your http with http.antMatcher():
http.antMatcher("/HQ/test_web/**");
http.authorizeRequests().antMatchers("/HQ/test_web/**").anonymous()
.and()
.addFilterBefore(new CustomFilter(),BasicAuthenticationFilter.class);
If you are not add http.antMatcher(...); than that http will intercept all urls and SecondWaveFilters never will be reached.
http.authorizeRequests().antMatchers("/HQ/test_web/**").anonymous() - means that any anonymous user may hit /HQ/test_web/**, but it doesn't say "apply FirstWaveFilters when /HQ/test_web/**" it just mean anyone who hists /HQ/test_web/** may be anonymous.

multiple entry points in spring security

I have a spring boot application that should allow form based authentication against database and SSO CAS based authentication.
I have followed the example from here (https://www.baeldung.com/spring-security-multiple-entry-points) and seems to me that Order is not working as expected. it is always using the one that is annotated as Order(1) as entry point.
here is my code,
#Configuration
#EnableWebSecurity
public class SecurityConfig {
#Configuration
#Order(2)
public static class WebSecurityCASConfig extends WebSecurityConfigurerAdapter {
public WebSecurityCASConfig() {
super();
}
#Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(
"/js/**",
"/css/**",
"/images/**").permitAll()
.regexMatchers("/login1")
.authenticated()
.and()
.authorizeRequests()
.and()
.httpBasic()
.authenticationEntryPoint(authenticationEntryPoint);
}
}
//second
#Configuration
#Order(1)
public static class WebSecurityDatabaseConfig extends WebSecurityConfigurerAdapter {
public WebSecurityDatabaseConfig() {
super();
}
#Autowired
UserDetailServiceImpl userDetailsService;
#Autowired
BCryptPasswordEncoder passwordEncoder;
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(
"/js/**",
"/css/**",
"/images/**").permitAll()
//.antMatchers("/catalog").access("hasAnyRole('ROLE_USER', 'ROLE_ADMIN')")
////.antMatchers("/login1").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/catalog", true)
.permitAll()
.usernameParameter("username")
.passwordParameter("password")
.and()
.logout()
.permitAll()
.logoutUrl("/logout").logoutSuccessUrl("/logout")
.and().exceptionHandling().accessDeniedPage("/403");
}
}
}
I want both configurations work based on url pattern. Any solutions/help/suggestions would be highly appreciated. Thanks.
I found a solution for this. I just simply followed what the spring document says in 5.9 (https://docs.spring.io/spring-security/site/docs/5.0.0.RELEASE/reference/htmlsingle/) and also another question on stackoverflow, Spring Security : Multiple HTTP Config not working

Redirect in a filter with Spring Boot

In my configuration Spring Boot WebSecurityConfig have a filter that I need to see if the user has the expired password, if it is enabled on the application ..
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
IdentityService userDetailsService;
#Autowired
AccountFilter accountFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.and()
.authorizeRequests()
.antMatchers("/login", "/recover-credntial",
"/logout", "/resources/**").permitAll()
.and()
.formLogin()
.loginPage("/login").failureUrl("/login?error")
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/login?logout")
.permitAll()
.and()
.exceptionHandling()
.accessDeniedPage("/403")
.and().addFilterAfter(accountFilter, UsernamePasswordAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder authManagerBuilder)
throws Exception {
authManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
}
As you see I have .and().addFilterAfter(Account Filter, UsernamePasswordAuthenticationFilter.class); in the HTTP configuration.
How do I define my filter so that it can perform a redirect to the URL of some of my controller?
I'm using in Java Web Application 'Spring Boot' with Java Configuration, not file xml!
One approach would be as follows using ExceptionMappingAuthenticationFailureHandler. This will mean not using the servlet though.
Configuration
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.failureHandler(authenticationFailureHandler())
.and()
.logout()
.permitAll();
}
Authentication Failure Handler
#Bean
public AuthenticationFailureHandler authenticationFailureHandler() {
ExceptionMappingAuthenticationFailureHandler exceptionMappingAuthenticationFailureHandler = new ExceptionMappingAuthenticationFailureHandler();
Map<String, String> exMap = new HashMap<String, String>();
exMap.put("org.springframework.security.authentication.CredentialsExpiredException","/loginerror/credentialsexpired.htm");
exceptionMappingAuthenticationFailureHandler.setExceptionMappings(exMap);
return exceptionMappingAuthenticationFailureHandler;
}
Custom User Details Service
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
// Check if Password Expired then throw
throw new CredentialsExpiredException("Expired");
}
}

Resources