Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SpringBoot multiple authentication adapter

I have a very special requirements in my Spring Boot web application: I have internal and external users. Internal users login to the web application by using keycloak authentication (they can work in the web application), but our external users login by simple Spring Boot authentication (what they can do is just to download some files generated by web application)

What I want to do is to have multiple authentication model: all the path except /download/* to be authenticated by our Keycloak authentication, but the path /download/* to be authenticated by SpringBoot basic authentication.

At the moment I have the following:

@Configuration
@EnableWebSecurity
public class MultiHttpSecurityConfig {

    @Configuration
    @ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
    @Order(1)
    public static class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {

        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
            auth.authenticationProvider(keycloakAuthenticationProvider());
        }

        @Bean
        @Override
        protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
            return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            super.configure(http);
            http
                .regexMatcher("^(?!.*/download/export/test)")
                .authorizeRequests()
                .anyRequest().hasAnyRole("ADMIN", "SUPER_ADMIN")
                .and()
                .logout().logoutSuccessUrl("/bye");
        }

    }

    @Configuration
    @Order(2)
    public static class DownloadableExportFilesSecurityConfig extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .antMatcher("/download/export/test")
                .authorizeRequests()
                .anyRequest().hasRole("USER1")
                .and()
                .httpBasic();
        }

        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication()
                .withUser("user").password("password1").roles("USER1");
        }

    }
}

But it does not work well, because every time the external user wants to download something (/download/export/test), it prompts the login form, but after entering the correct external user username and password, than it prompts the keycloak authentication login form.

I don't get any error just a warning:

2016-06-20 16:31:28.771  WARN 6872 --- [nio-8087-exec-6] o.k.a.s.token.SpringSecurityTokenStore   : Expected a KeycloakAuthenticationToken, but found org.springframework.security.authentication.UsernamePasswordAuthenticationToken@3fb541cc: Principal: org.springframework.security.core.userdetails.User@36ebcb: Username: user; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER1; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: 4C1BD3EA1FD7F50477548DEC4B5B5162; Granted Authorities: ROLE_USER1

Do you have any ideas?

like image 914
gubak Avatar asked Jun 20 '16 11:06

gubak


People also ask

What is difference between AuthenticationManager and AuthenticationProvider?

The Authentication Manager is only a interface and actual implementation of the authenticate method is provided by the ProviderManager. The ProviderManager has a list of AuthenticationProviders. From it's authenticate method it calls the authenticate method of the appropriate AuthenticateProvider.

Is WebSecurityConfigurerAdapter deprecated?

From Spring Boot 2.7, WebSecurityConfigurerAdapter is deprecated. In this tutorial, I will show you how to update your Web Security Config class in Spring Security without the WebSecurityConfigurerAdapter example.

What is inMemoryAuthentication spring boot?

inMemoryAuthentication() is the method of AuthenticationManagerBuilder class is used to perform in-memory authentication in the Spring Security. This method is used for creating the user with respective roles and passwords.

What is the use of AuthenticationEntryPoint?

AuthenticationEntryPoint is used to send an HTTP response that requests credentials from a client. Sometimes a client will proactively include credentials such as a username/password to request a resource.


2 Answers

I experienced some headaches when implementing basic authentication next to Keycloak authentication, because still while doing multiple WebSecurityAdapter implementations 'by the book', the Keycloak authentication filter was called even when basic authentication succeeded.

The reason lies here: http://www.keycloak.org/docs/latest/securing_apps/index.html#avoid-double-filter-bean-registration

So if you use the Keycloak Spring Security Adapter together with Spring Boot, make sure to add those two beans (in addition to the valid answer by Jacob von Lingen):

@Configuration
@EnableWebSecurity
public class MultiHttpSecurityConfig {

    @Configuration
    @Order(1) //Order is 1 -> First the special case
    public static class DownloadableExportFilesSecurityConfig extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception 
        {
            http
                .antMatcher("/download/export/test")
                    .authorizeRequests()
                    .anyRequest().hasRole("USER1")
                .and()
                    .httpBasic();
        }

        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication()
              .withUser("user").password("password1").roles("USER1");
        }

    }

    @Configuration
    @ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
    //no Order, will be configured last => All other urls should go through the keycloak adapter
    public static class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {

        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {

       auth.authenticationProvider(keycloakAuthenticationProvider());
        }

        @Bean
        @Override
        protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
            return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
        }

        // necessary due to http://www.keycloak.org/docs/latest/securing_apps/index.html#avoid-double-filter-bean-registration
        @Bean
        public FilterRegistrationBean keycloakAuthenticationProcessingFilterRegistrationBean(KeycloakAuthenticationProcessingFilter filter) {
            FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
            registrationBean.setEnabled(false);
            return registrationBean;
        }
        // necessary due to http://www.keycloak.org/docs/latest/securing_apps/index.html#avoid-double-filter-bean-registration
        @Bean
        public FilterRegistrationBean keycloakPreAuthActionsFilterRegistrationBean(KeycloakPreAuthActionsFilter filter) {
            FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
            registrationBean.setEnabled(false);
            return registrationBean;
        }


        @Override
        protected void configure(HttpSecurity http) throws Exception 
        {
            super.configure(http);
            http
                .authorizeRequests()
                .anyRequest().hasAnyRole("ADMIN", "SUPER_ADMIN")
                .and()
                .logout().logoutSuccessUrl("/bye");
        }

    }
}
like image 189
Lars Karschen Avatar answered Oct 15 '22 13:10

Lars Karschen


The key for multiple HttpSecurity is to register the 'special cases' before the normal one. In other words, the /download/export/test authentication adapter should be registered before the keycloak adapter.

Another important thing to notice, once an authentication is successful, no other adapter is called (so the .regexMatcher("^(?!.*/download/export/test)") is not necessary). More info for Multiple HttpSecurity can be found here.

Below you code with minimal changes:

@Configuration
@EnableWebSecurity
public class MultiHttpSecurityConfig {

    @Configuration
    @Order(1) //Order is 1 -> First the special case
    public static class DownloadableExportFilesSecurityConfig extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .antMatcher("/download/export/test")
                    .authorizeRequests()
                    .anyRequest().hasRole("USER1")
                .and()
                    .httpBasic();
        }

        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication()
                .withUser("user").password("password1").roles("USER1");
        }

    }

    @Configuration
    @ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
    @Order(2) //Order is 2 -> All other urls should go through the keycloak adapter
    public static class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {

        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
            auth.authenticationProvider(keycloakAuthenticationProvider());
        }

        @Bean
        @Override
        protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
            return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            super.configure(http);
            http
                //removed .regexMatcher("^(?!.*/download/export/test)")
                .authorizeRequests()
                    .anyRequest().hasAnyRole("ADMIN", "SUPER_ADMIN")
                .and()
                    .logout().logoutSuccessUrl("/bye");
        }

    }
}
like image 31
Jacob van Lingen Avatar answered Oct 15 '22 13:10

Jacob van Lingen