Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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)

like image 946
ielkhalloufi Avatar asked May 18 '26 02:05

ielkhalloufi


1 Answers

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.

like image 97
ielkhalloufi Avatar answered May 20 '26 22:05

ielkhalloufi