Combining basic authentication and form login for the same REST Api - spring

Is there a way to set up basic authentication and form login for the same REST service? I'd like to let logged in user trigger this service both through web browser after loggin in and from command line running curl -u username:password hostname.com/api/process
Now I've seen this post: Basic and form based authentication with Spring security Javaconfig
but it's slightly different from what I'm trying to do.
Is there a way to set this up with spring?
What I have now is this:
package com.my.company.my.app.security;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import javax.sql.DataSource;
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
DataSource dataSource;
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/js/**", "/css/**")
.permitAll();
http
.authorizeRequests()
.antMatchers("/api/**")
.authenticated()
.and()
.httpBasic();
http
.authorizeRequests()
.antMatchers("/","/index")
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/j_spring_security_check")
.defaultSuccessUrl("/monitor")
.failureUrl("/login?error")
.usernameParameter("j_username")
.passwordParameter("j_password")
.permitAll()
.and()
.logout()
.logoutUrl("/j_spring_security_logout")
.logoutSuccessUrl("/login?logout")
.permitAll()
.and()
.csrf()
.disable();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.passwordEncoder(passwordEncoder())
.usersByUsernameQuery("SELECT username, password, enabled FROM users WHERE username=?")
.authoritiesByUsernameQuery("SELECT username, authority FROM authorities WHERE username=?");
}
#Bean
public PasswordEncoder passwordEncoder() {
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
}
The only problem is that it doesn't redirect to my login page when hostname.com/index or hostname.com/ is called instead window pop ups asking for basic authentication credentials.

You can achieve this easily by using multiple http configuration as below, this code only explains multiple http configuration. I am assuming that you are well aware of the other essential configurations related to spring security e.g authenticationManger etc.
#EnableWebSecurity
public class MultiHttpSecurityCustomConfig {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER").and().withUser("admin").password("password")
.roles("USER", "ADMIN");
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**").authorizeRequests().anyRequest().hasRole("ADMIN").and().httpBasic();
}
}
#Configuration
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().formLogin();
}
}
}
Please refer spring security official link: Multiple HttpSecurity
I will also reccomend you to check out Secure REST Services with Spring Security
Feel free to comment if you encounter any problem!

One might try with the only ConfigurationAdapter class rather than two, e.g.:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf()
.disable()
.httpBasic()
.and()
.authorizeRequests()
.antMatchers("/login/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
;
}
Ref.: https://medium.com/#haytambenayed/basic-authentication-and-form-based-authentication-using-spring-security-ed79951dbb2e

I found out that the previous code snippet is not working in Spring Security 5 because of an issue in the CSRF filter in the Basic authentication filter chain. It is possible to make it work by disabling CSRF for Basic auth.
BTW the override of Basic auth by Form auth is because redirection to /error page which is caused by this CSRF filter issue.
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**")
.authorizeRequests()
.anyRequest()
.hasRole("ADMIN")
.and()
.httpBasic()
.csrf().disable();
}
}

Related

How to use multiple login pages one for admin and the other one for user

I want two different login pages. One for admins and the other one for users.
Using the code below only the class with #Order(1) works when I remove the #Order annotation from the static classes, only the last one works. I used them in two different ConfigSecurity files, and it didn't work.
I m expecting for both of them to work. Yet only one is working.
package com.example.FlightAgency.security;
import com.example.FlightAgency.service.UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
#Configuration
#EnableWebSecurity
public class SecurityConfig {
#Configuration
#Order(1)
public static class App1ConfigurationAdapter {
#Bean
public UserDetailsService userDetailsService() {
return new UserService();
}
#Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
#Bean
public SecurityFilterChain filterChainApp1(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.requestMatchers("/user/**").hasAuthority("USER")
.and()
.formLogin()
.loginPage("/user/login")
.usernameParameter("email")
.loginProcessingUrl("/user/login")
.defaultSuccessUrl("/user/")
.permitAll()
.and()
.logout()
.logoutUrl("/user/logout")
.logoutSuccessUrl("/user/login")
.deleteCookies("JSESSIONID")
.and()
.exceptionHandling()
.accessDeniedPage("/403")
.and()
.csrf().disable();
return http.build();
}
}
#Configuration
#Order(2)
public static class App2ConfigurationAdapter {
#Bean
public SecurityFilterChain filterChainApp2(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.requestMatchers("/admin/**").hasAuthority("ADMIN")
.and()
.formLogin()
.loginPage("/admin/login")
.usernameParameter("email")
.loginProcessingUrl("/admin/login")
.defaultSuccessUrl("/admin/")
.permitAll()
.and()
.logout()
.logoutUrl("/admin/logout")
.logoutSuccessUrl("/admin/login")
.deleteCookies("JSESSIONID")
.and()
.exceptionHandling()
.accessDeniedPage("/403")
.and()
.csrf().disable();
return http.build();
}
}
} `
Both security filter chains are not restricted (default is /**). You have to restrict the first one with securityMatcher, see Spring Security Reference:
Multiple HttpSecurity Instances
We can configure multiple HttpSecurity instances just as we can have multiple <http> blocks in XML. The key is to register multiple SecurityFilterChain #Beans. The following example has a different configuration for URL’s that start with /api/.
#Configuration
#EnableWebSecurity
public class MultiHttpSecurityConfig {
#Bean
public UserDetailsService userDetailsService() throws Exception {
// ensure the passwords are encoded properly
UserBuilder users = User.withDefaultPasswordEncoder();
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(users.username("user").password("password").roles("USER").build());
manager.createUser(users.username("admin").password("password").roles("USER","ADMIN").build());
return manager;
}
#Bean
#Order(1)
public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/api/**")
.authorizeHttpRequests(authorize -> authorize
.anyRequest().hasRole("ADMIN")
)
.httpBasic(withDefaults());
return http.build();
}
#Bean
public SecurityFilterChain formLoginFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.formLogin(withDefaults());
return http.build();
}
}
Configure Authentication as usual.
Create an instance of SecurityFilterChain that contains #Order to specify which SecurityFilterChain should be considered first.
The http.securityMatcher states that this HttpSecurity is applicable only to URLs that start with /api/.
Create another instance of SecurityFilterChain. If the URL does not start with /api/, this configuration is used. This configuration is considered after apiFilterChain, since it has an #Order value after 1 (no #Order defaults to last).

Multiple authentication methods for one endpoint? [duplicate]

I am trying to use Spring Security and I have a use case where I want different login pages and different set of URLs to be secured.
Here is my configuration:
#Configuration
#Order(1)
public static class ProviderSecurity extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.antMatchers("/admin/login").permitAll()
.antMatchers("/admin/**").access("hasRole('BASE_USER')")
.and()
.formLogin()
.loginPage("/admin/login").permitAll()
.defaultSuccessUrl("/admin/home")
.failureUrl("/admin/login?error=true").permitAll()
.usernameParameter("username")
.passwordParameter("password")
.and()
.csrf()
.and()
.exceptionHandling().accessDeniedPage("/Access_Denied");
}
}
#Configuration
#Order(2)
public static class ConsumerSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/consumer/login").permitAll()
.antMatchers("/consumer/**").access("hasRole('BASE_USER')")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/consumer/login").permitAll()
.defaultSuccessUrl("/consumer/home")
.failureUrl("/consumer/login?error=true").permitAll()
.usernameParameter("username")
.passwordParameter("password")
.and().csrf()
.and()
.exceptionHandling().accessDeniedPage("/Access_Denied");
}
}
These classes are inner classes of another class MultipleHttpSecurityConfig that has annotation #EnableWebSecurity.
The security for admin/** is working fine, but none of the consumer/** pages are secured, no redirection is happening for login page. I've searched for other answers but none worked.
Look at the Spring Security Reference:
#EnableWebSecurity
public class MultiHttpSecurityConfig {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) { 1
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}
#Configuration
#Order(1) 2
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**") 3
.authorizeRequests()
.anyRequest().hasRole("ADMIN")
.and()
.httpBasic();
}
}
#Configuration 4
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin();
}
}
}
1 Configure Authentication as normal
2 Create an instance of WebSecurityConfigurerAdapter that contains #Order to specify which WebSecurityConfigurerAdapter should be considered first.
3 The http.antMatcher states that this HttpSecurity will only be applicable to URLs that start with /api/
4 Create another instance of WebSecurityConfigurerAdapter. If the URL does not start with /api/ this configuration will be used. This configuration is considered after ApiWebSecurityConfigurationAdapter since it has an #Order value after 1 (no #Order defaults to last).
Your second configuration is not used, because your first configuration matches /** (no antMatcher configured). And your first configuration restricts only /admin/**, all other URLs are permitted by default.
Your first WebSecurityConfigurerAdapter's
http
.authorizeRequests()
matches all the URLs, limit it to only URLs start with /admin by using antMatcher:
#Configuration
#Order(1)
public static class ProviderSecurity extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/admin/**")
.authorizeRequests()
.antMatchers("/admin/login").permitAll()
.antMatchers("/admin/**").access("hasRole('BASE_USER')")
.and()
...

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

How to migrate code from Spring Security to Reactive Spring Security?

I'm trying to migrate classic Spring Boot Application to Reactive Spring Boot Application, but I have a problems with this task.
How to migrate the code below
package com.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api").anonymous()
.antMatchers("/api/**").authenticated().and()
.httpBasic();
http
.authorizeRequests()
.antMatchers("/login").anonymous()
.antMatchers("/", "/error", "/**/favicon.ico", "/css/**", "/fonts/**", "/js/**", "/images/avatar.png", "/images/logo.png", "/profile", "/profile/find", "/profile/view/**", "/api/register").permitAll()
.anyRequest().authenticated().and()
.formLogin().loginPage("/login").loginProcessingUrl("/profile/login").failureUrl("/login?error").usernameParameter("usr").passwordParameter("pass").and()
.logout().logoutUrl("/logout").invalidateHttpSession(true).deleteCookies("jsessionid","nebp").logoutSuccessUrl("/login?logout").and()
.rememberMe().key("nebpps").tokenValiditySeconds(2419200).rememberMeParameter("remember_me").rememberMeCookieName("nebp").useSecureCookie(true).and()
.csrf().ignoringAntMatchers("/api/**").and()
.exceptionHandling().accessDeniedPage("/403");//.and()
//.requiresChannel().anyRequest().requiresSecure();
}
#Bean(name = "passwordEncoder")
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
to style code like below
#Configuration
#EnableWebFluxSecurity
public class SecurityConfiguration {
#Bean
SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) {
return http
.csrf().disable()
.authorizeExchange()
.pathMatchers("/login", "/logout").permitAll()
.pathMatchers("/i18n/**",
"/css/**",
"/fonts/**",
"/icons-reference/**",
"/img/**",
"/js/**",
"/vendor/**").permitAll()
.anyExchange()
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.and()
.logout()
.logoutUrl("/logout")
.and()
.build();
}
//in case you want to encrypt password
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
I accepted that certain elements can not be defined as before like usernameParameter.
First of all, how to set that the given path (/logout) is only for anonymous users.
Secondly, how to have CSRF enabled, but to have exclusion for addresses beginning with /api

Configuring Spring Security for Form Login AND RESTful API [duplicate]

I am trying to use Spring Security and I have a use case where I want different login pages and different set of URLs to be secured.
Here is my configuration:
#Configuration
#Order(1)
public static class ProviderSecurity extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.antMatchers("/admin/login").permitAll()
.antMatchers("/admin/**").access("hasRole('BASE_USER')")
.and()
.formLogin()
.loginPage("/admin/login").permitAll()
.defaultSuccessUrl("/admin/home")
.failureUrl("/admin/login?error=true").permitAll()
.usernameParameter("username")
.passwordParameter("password")
.and()
.csrf()
.and()
.exceptionHandling().accessDeniedPage("/Access_Denied");
}
}
#Configuration
#Order(2)
public static class ConsumerSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/consumer/login").permitAll()
.antMatchers("/consumer/**").access("hasRole('BASE_USER')")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/consumer/login").permitAll()
.defaultSuccessUrl("/consumer/home")
.failureUrl("/consumer/login?error=true").permitAll()
.usernameParameter("username")
.passwordParameter("password")
.and().csrf()
.and()
.exceptionHandling().accessDeniedPage("/Access_Denied");
}
}
These classes are inner classes of another class MultipleHttpSecurityConfig that has annotation #EnableWebSecurity.
The security for admin/** is working fine, but none of the consumer/** pages are secured, no redirection is happening for login page. I've searched for other answers but none worked.
Look at the Spring Security Reference:
#EnableWebSecurity
public class MultiHttpSecurityConfig {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) { 1
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}
#Configuration
#Order(1) 2
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**") 3
.authorizeRequests()
.anyRequest().hasRole("ADMIN")
.and()
.httpBasic();
}
}
#Configuration 4
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin();
}
}
}
1 Configure Authentication as normal
2 Create an instance of WebSecurityConfigurerAdapter that contains #Order to specify which WebSecurityConfigurerAdapter should be considered first.
3 The http.antMatcher states that this HttpSecurity will only be applicable to URLs that start with /api/
4 Create another instance of WebSecurityConfigurerAdapter. If the URL does not start with /api/ this configuration will be used. This configuration is considered after ApiWebSecurityConfigurationAdapter since it has an #Order value after 1 (no #Order defaults to last).
Your second configuration is not used, because your first configuration matches /** (no antMatcher configured). And your first configuration restricts only /admin/**, all other URLs are permitted by default.
Your first WebSecurityConfigurerAdapter's
http
.authorizeRequests()
matches all the URLs, limit it to only URLs start with /admin by using antMatcher:
#Configuration
#Order(1)
public static class ProviderSecurity extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/admin/**")
.authorizeRequests()
.antMatchers("/admin/login").permitAll()
.antMatchers("/admin/**").access("hasRole('BASE_USER')")
.and()
...

Resources