SpringBoot multiple authentication adapter

2019-04-10 03:17发布

问题:

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?

回答1:

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

    }
}


回答2:

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

    }
}