Combining basic authentication and form login for the same REST Api

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();
            }
        }