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

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.

Related

Spring security - How to use role based authentication for different domains?

There is a project with Spring boot back-end on running on localhost:8080 and 2 front-end angular applications on localhost:4200 (User website) and localhost:4201(Admin website).How can i configure spring security so that it allows only users with role - ROLE_USER,ROLE_ADMIN in User website and users with role -ROLE_ADMIN should have access to Admin Website.
Currently both users are able to access both wesbite.Is there any way to restrict certain domains rather restricting paths(URLs) to users.
Current config -
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
Environment env;
#Autowired
UserSecurityService useSecurityService;
private BCryptPasswordEncoder passwordEncoder() {
return SecurityUtility.passwordEncoder();
}
private static final String[] PUBLIC_MATHCES= {
"/css/**",
"/js/**",
"/images/**",
"/book/**",
"/user/**",
"/media/**"
};
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(useSecurityService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(PUBLIC_MATHCES).permitAll()
.anyRequest().authenticated()
.and();
http.csrf().disable()
.cors()
.and()
.httpBasic();
}
#Bean
public HttpSessionIdResolver httpSessionStrategy() {
return HeaderHttpSessionIdResolver.xAuthToken();
}
}
suppose all of your configurations configured properly, then you can make use of the role restriction mechanism as the below sample :
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorize - > {
authorize
.antMatchers("/h2-console/**").permitAll() //do not use in production!
.antMatchers("/css/**", "/js/**", "/images/**", "/book/**", "/user/**", "/media/**").permitAll()
.antMatchers("/website/find", "/main*").permitAll()
.antMatchers(HttpMethod.GET, "/userweb/v1/data/**").permitAll()
.mvcMatchers(HttpMethod.DELETE, "/userweb/v1/info/**").hasRole("ADMIN")
.mvcMatchers(HttpMethod.GET, "/userweb/v1/item/{upc}").permitAll()
.mvcMatchers("/admin/main").hasAnyRole("USER", "ADMIN")
.mvcMatchers(HttpMethod.GET, "/user/api/v1/normal")
.hasAnyRole("USER", "ADMIN", "FOO");
})
.authorizeRequests()
.anyRequest().authenticated()
.and()
.cors()
.and()
.httpBasic()
.and().csrf().disable();
}

Custom login REST endpoint

I am trying to change default POST /login spring security endpoint for logging in to POST /api/users/login
I already tried doing that through
formLogin().loginProcessingUrl("/api/users/login")
or
formLogin().loginPage("/api/users/login")
but it didn't work. How do i do that? I can't find tutorial describing it or stack overflow answer. I also tried reading Spring Security documentation but it didn't help too.
My security config looks like this:
private final MyUserDetailsService myUserDetailsService;
private final PasswordEncoder passwordEncoder;
#Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoder);
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
//go from most restrictive url to least restrictive, from single urls to /**
httpSecurity.csrf().disable().cors().and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager()))
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
//to allow h2 console page
httpSecurity.headers().frameOptions().disable();
}
In the WebSecurityConfigurerAdapter class there is to configure methods that you should override like so:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint(
unAuthorizedResponseAuthenticationEntryPoint)
.and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.anyRequest()
.authenticated();
http.addFilterBefore(authenticationTokenFilter,
UsernamePasswordAuthenticationFilter.class);
}
#Override
public void configure(WebSecurity webSecurity) throws Exception {
webSecurity
.ignoring().antMatchers(HttpMethod.POST, "/api/auth/login")
.antMatchers(HttpMethod.OPTIONS, "/**")
.and().ignoring()
.antMatchers(HttpMethod.GET, "/").and().ignoring();
}
}
Please see in method:
public void configure(WebSecurity webSecurity) throws Exception
the line:
webSecurity.ignoring().antMatchers(HttpMethod.POST, "/api/auth/login")
This is where you put your custom authentication API endpoint!
And then you may proceed as I explained in the following question
How to use custom UserDetailService in Spring OAuth2 Resource Server?

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 Basic Auth and Form login for the same API

I would like to access all my API's via two authentication mechanisms, Basic Auth & Form login. I know that there are existing questions, but, the answers did not work for me, and my use case is a little bit different.
My config:
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {
#Configuration
#Order(1)
public static class SecurityConfigBasicAuth extends WebSecurityConfigurerAdapter {
final private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
#Autowired
public SecurityConfigBasicAuth(RestAuthenticationEntryPoint restAuthenticationEntryPoint,
#Qualifier("customUserDetailsService") UserDetailsService userDetailsService) {
this.restAuthenticationEntryPoint = restAuthenticationEntryPoint;
this.userDetailsService = userDetailsService;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
auth.authenticationProvider(authenticationProvider());
}
// #Bean authenticationProvider()
// #Bean passwordEncoder()
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated()
.and()
.cors()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.httpBasic()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
.formLogin().disable()
.logout().disable();
}
}
#Configuration
public static class SecurityConfigFormLogin extends WebSecurityConfigurerAdapter {
final private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
final private RestfulSavedRequestAwareAuthenticationSuccessHandler restfulSavedRequestAwareAuthenticationSuccessHandler;
final private CustomAuthenticationProvider customAuthenticationProvider;
#Autowired
public SecurityConfigFormLogin(RestAuthenticationEntryPoint restAuthenticationEntryPoint,
RestfulSavedRequestAwareAuthenticationSuccessHandler restfulSavedRequestAwareAuthenticationSuccessHandler,
CustomAuthenticationProvider hashAuthenticationProvider) {
this.restAuthenticationEntryPoint = restAuthenticationEntryPoint;
this.restfulSavedRequestAwareAuthenticationSuccessHandler = restfulSavedRequestAwareAuthenticationSuccessHandler;
this.customAuthenticationProvider = customAuthenticationProvider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated()
.and()
.cors()
.and()
.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
.csrf().disable()
.httpBasic().disable()
.formLogin()
.usernameParameter("id1")
.passwordParameter("Id2")
.loginProcessingUrl("/test/login")
.successHandler(restfulSavedRequestAwareAuthenticationSuccessHandler)
.failureHandler(myFailureHandler())
.and()
.logout();
}
// #Bean myFailureHandler()
}
}
As you can see, I defined two 'WebSecurityConfigurerAdapters', one for Basic Auth, and one for Form login. The Form login is REST compatible (does not redirect, but gives HTTP responses).
The problem is as follows: The first 'WebSecurityConfigurerAdapter' that is loaded works and overrides the second. The above example, makes it possible to use basic auth, but I cannot login on POST '/test/login', I get a:
{
"timestamp": 1534164906450,
"status": 401,
"error": "Unauthorized",
"message": "Unauthorized",
"path": "/test/login"
}
Update fixed: the key was to use the 'requestMatchers()', see answer section for solution (as suggested by jzheaux)
Okay, this is how I fixed this:
I configured the Basic Auth configuration as:
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/api/**")
.and()
.cors()
.and()
.csrf().disable()
.httpBasic()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and();
}
If you do not want that the basic authentication returning new cookie with new JSESSIONID, add:
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.NEVER)
.sessionFixation()
.migrateSession()
The Form login configuration as:
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers(HttpMethod.POST, "/test/login")
.and()
.cors()
.and()
.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and()
.formLogin()
.usernameParameter("id1")
.passwordParameter("id2")
.loginProcessingUrl("/test/login")
.successHandler(authenticationSuccessHandler)
.failureHandler(myFailureHandler())
.and()
.logout();
}
Now, it is possible for me to authenticate via the Form login configuration, and use the cookie session id to call /api/** (configured in the Basic Auth configuration). I can also just use the Basic Auth authentication ofcourse.

Spring permitAll() and ignorning() doesn't work

I'm trying to configure Spring-Boot 2 application with in-memory authorization and it drive me crazy...
I need to secure all resources except /news.
I have created this configuration beans following the Spring's docs but Spring Security isn't permit GET method on /news:
#Slf4j
#Configuration
#EnableWebSecurity
public class ApplicationAuthentication extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers(HttpMethod.GET,"/news");
}
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth
.inMemoryAuthentication()
.withUser("user")
.password("password")
.roles("USER")
.and()
.withUser("admin")
.password("admin")
.roles("USER", "ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.GET,"/news")
.permitAll()
.anyRequest()
.authenticated()
.and()
.httpBasic();
}
}
I'm using spring-boot 2.0.4.RELEASE,
Can someone help me?

Resources