1
votes

Connectez plusieurs mécanismes d'authentification Spring Boot Security

J'ai une configuration de sécurité pour mon application qui authentifie l'utilisateur via LDAP . Cela fonctionne assez bien, mais j'aimerais maintenant ajouter un autre AuthenticationProvider qui effectue quelques vérifications supplémentaires sur l'utilisateur qui essaie de s'authentifier. J'ai donc essayé d'ajouter un DbAuthenticationProvider qui (à des fins de test) refuse toujours l'accès. Ainsi, lorsque j'essaie de me connecter avec mon compte de domaine (cela fonctionne pour le activeDirectoryLdapAuthenticationProvider ), je ne peux pas accéder à la page car le deuxième fournisseur échoue à l'authentification.

Pour atteindre cet objectif, J'ai utilisé le code suivant:

@Component
public class DbAuthenticationProvider implements AuthenticationProvider {

    Logger logger = LoggerFactory.getLogger(DbAuthenticationProvider.class);

    @Override
    public Authentication authenticate(Authentication auth) throws AuthenticationException {
        auth.setAuthenticated(false);
        this.logger.info("Got initialized");
        return auth;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return true;
    }

}

Et voici mon DbAuthenticationProvider:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${ad.domain}")
    private String AD_DOMAIN;

    @Value("${ad.url}")
    private String AD_URL;

    @Autowired
    UserRoleComponent userRoleComponent;

    @Autowired
    DbAuthenticationProvider dbAuthenticationProvider;

    private final Logger logger = LoggerFactory.getLogger(WebSecurityConfig.class);

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        this.logger.info("Verify logging level");
        http.authorizeRequests().anyRequest().fullyAuthenticated().and().formLogin()
                .successHandler(new CustomAuthenticationSuccessHandler()).and().httpBasic().and().logout()
                .logoutUrl("/logout").invalidateHttpSession(true).deleteCookies("JSESSIONID");
        http.formLogin().defaultSuccessUrl("/", true);
    }


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
        auth.authenticationProvider(dbAuthenticationProvider);
    }

    @Bean
    public AuthenticationManager authenticationManager() {
        return new ProviderManager(Arrays.asList(activeDirectoryLdapAuthenticationProvider(), dbAuthenticationProvider));
    }

    @Bean
    public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
        ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(AD_DOMAIN,
                AD_URL);
        provider.setConvertSubErrorCodesToExceptions(true);
        provider.setUseAuthenticationRequestCredentials(true);
        return provider;
    }
}

Malheureusement Je peux me connecter (l'accès n'est pas refusé comme je m'y attendais). Ai-je manqué quelque chose?


3 commentaires

juste comme un indice: vous voudrez peut-être rechercher le keycloak de redhats, qui pourrait répondre à vos besoins et plus encore et qui a une excellente intégration de ressort.


Je ne suis pas sûr de suivre. Les fournisseurs d'authentification sont exécutés dans l'ordre si l'un d'entre eux réussit la demande. Donc, dans votre cas, le fournisseur LDAP traite l'authentification et la demande est authentifiée. N'est-ce pas le comportement?


@Veeram Mon comportement souhaité était de m'authentifier via ldap et db à cette étape. Vérifiez donc l'authentification via le fournisseur ldap et le fournisseur de base de données (si l'utilisateur est dans la base de données et l'authentification via ldap a fonctionné, authentifiez la demande, sinon - ne le faites pas).


3 Réponses :


2
votes

Comme exemple de travail autour de plusieurs mécanismes d'authentification: trouver le code

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
      auth.jdbcAuthentication().dataSource(dataSource)
           .passwordEncoder(new BCryptPasswordEncoder());
      auth.authenticationProvider(new CustomAuthenticationProvider(this.dataSource));

      auth.objectPostProcessor(new ObjectPostProcessor<Object>() {
        @Override
        public <O> O postProcess(O object) {
            ProviderManager providerManager = (ProviderManager) object;
            Collections.swap(providerManager.getProviders(), 0, 1);
            return object;
        }
    });
}

configuré deux fournisseurs d'authentification dans Spring Security

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


@Configuration
@EnableWebSecurity
public class XSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private LDAPAuthenticationProvider authenticationProvider;

    @Autowired
    private DBAuthenticationProvider dbauthenticationProvider;

    @Override
      public void configure(WebSecurity web) throws Exception {
        web
          .ignoring()
             .antMatchers("/scripts/**","/styles/**","/images/**","/error/**");
      }

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

    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
        .authorizeRequests()
            .antMatchers("/","/logout").permitAll()
            .antMatchers("/admin").hasRole("ADMIN")         
            .anyRequest().authenticated()
            .and()
        .formLogin()
            .loginPage("/index")
            .loginProcessingUrl("/perform_login")
            .usernameParameter("user")
            .passwordParameter("password")
            .failureUrl("/index?failed=true")
            .defaultSuccessUrl("/test",true)
            .permitAll()
            .and()
         .logout().logoutUrl("/logout")
                  .logoutSuccessUrl("/index?logout=true").permitAll()
            .and()
            .exceptionHandling().accessDeniedPage("/error");
    }

}

configuration qui permet de configurer plusieurs fournisseurs d'authentification dans la configuration java. p >

 <security:authentication-manager>
      <security:authentication-provider ref="AuthenticationProvider " />
      <security:authentication-provider ref="dbAuthenticationProvider" />
   </security:authentication-manager>

objectPostProcessor dans la méthode configure nécessite AuthenticationManagerBuilder pour construire réellement l'objet avant que nous puissions accéder et modifier l'ordre des fournisseurs

@Configuration
@EnableWebSecurity
@Profile("container")
public class CustomWebSecurityConfig  extends WebSecurityConfigurerAdapter {

@Autowired
private AuthenticationProvider authenticationProvider;

@Autowired
private AuthenticationProvider authenticationProviderDB;

@Override
@Order(1)
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(authenticationProvider);
}

@Order(2)
protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(authenticationProviderDB);
}

@Override
  public void configure(WebSecurity web) throws Exception {
    web
      .ignoring()
         .antMatchers("/scripts/**","/styles/**","/images/**","/error/**");
  }

@Override
public void configure(HttpSecurity http) throws Exception {
    http
            .authorizeRequests()
            .antMatchers("/rest/**").authenticated()
            .antMatchers("/**").permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .successHandler(new AuthenticationSuccessHandler() {
                @Override
                public void onAuthenticationSuccess(
                        HttpServletRequest request,
                        HttpServletResponse response,
                        Authentication a) throws IOException, ServletException {
                            //To change body of generated methods,
                            response.setStatus(HttpServletResponse.SC_OK);
                        }
            })
            .failureHandler(new AuthenticationFailureHandler() {

                @Override
                public void onAuthenticationFailure(
                        HttpServletRequest request,
                        HttpServletResponse response,
                        AuthenticationException ae) throws IOException, ServletException {
                            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                        }
            })
            .loginProcessingUrl("/access/login")
            .and()
            .logout()
            .logoutUrl("/access/logout")                
            .logoutSuccessHandler(new LogoutSuccessHandler() {
                @Override
                public void onLogoutSuccess(
                        HttpServletRequest request, 
                        HttpServletResponse response, 
                        Authentication a) throws IOException, ServletException {
                    response.setStatus(HttpServletResponse.SC_NO_CONTENT);
                }
            })
            .invalidateHttpSession(true)
            .and()
            .exceptionHandling()
            .authenticationEntryPoint(new Http403ForbiddenEntryPoint())
            .and()
            .csrf()//Disabled CSRF protection
            .disable();
    }
} 

p >


0 commentaires

6
votes

Spring n'utilisera pas plus d'un AuthenticationProvider pour authentifier la demande, donc le premier (dans la ArrayList ) AuthenticationProvider qui prend en charge le L'objet Authentication et l'authentification réussie de la demande seront les seules utilisées. dans votre cas, c'est activeDirectoryLdapAuthenticationProvider .

au lieu d'utiliser ActiveDirectoryLdapAuthenticationProvider , vous pouvez utiliser un AuthenticationProvider personnalisé qui délègue à LDAP et effectuer des vérifications supplémentaires:

    CustomerAuthenticationProvider implements AuthenticationProvider{
        privtae ActiveDirectoryLdapAuthenticationProvider  delegate; // add additional methods to initialize delegate during your configuration

          @Override
         public Authentication authenticate(Authentication auth) throws 
             AuthenticationException {
            Authentication  authentication= delegate.authenticate(auth);
            additionalChecks(authentication);
           return auth;
           }


          @Override
          public boolean supports(Class<?> authentication) {
            return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
          }

        public void additionalCheck(Authentication authentication){
               // throw AuthenticationException when it's not allowed
        }

    }


2 commentaires

C'est une excellente approche, malheureusement, ActiveDirectoryLdapAuthenticationProvider est une classe finale, puis-je contourner ce problème?


Pouvez-vous s'il vous plaît me dire comment faire cette partie "ajouter des méthodes supplémentaires pour initialiser le délégué lors de votre configuration"? Votre approche est la meilleure



2
votes

Ce n'est pas ainsi qu'un AuthenticationProvider fonctionne, un seul sera consulté pour l'authentification. Apparemment, vous souhaitez combiner certaines informations de LDAP et de la base de données. Pour cela, vous pouvez configurer un UserDetailsContextMapper et / ou GrantedAuthoritiesMapper . L'implémentation par défaut utilisera les informations de LDAP pour construire les UserDetails et ses GrantedAuthorities mais vous pouvez implémenter une stratégie qui consulte la base de données.

Une autre solution consiste à utiliser le LdapUserDetailsService qui vous permet d'utiliser le DaoAuthenticationProvider normal. Le nom est trompeur car il nécessite en fait un UserDetailsService . Ce AuthenticationProvider effectue des vérifications supplémentaires en utilisant UserDetailsChecker , qui par défaut vérifie certaines des propriétés de UserDetails , mais peut être étendu avec votre vérifications supplémentaires.

REMARQUE: Le LdapUserDetailsService utilise le LDAP brut, donc je ne sais pas si cela s'applique à l'approche légèrement différente d'Active Directory!

Une solution finale pourrait être de créer un DelegatingAuthenticationProvider qui s'étend de AbstractUserDetailsAuthenticationProvider afin que vous puissiez réutiliser la logique là-dedans pour utiliser le UserDetailsChecker . La méthode retrieveUser déléguerait alors au ActiveDirectoryLdapAuthenticationProvider réel pour faire l'authentification.

REMARQUE: Au lieu d'étendre le AbstractUserDetailsAuthenticationProvider , vous pouvez bien sûr également créer une version plus simple vous-même.

Dans l'ensemble, je soupçonne que créer un UserDetailsContextMapper personnalisé serait le plus simple et s'il ne se trouve pas dans DB, lancez une UsernameNotFoundException . De cette façon, le flux normal s'applique toujours et vous pouvez réutiliser la plupart de l'infrastructure existante.


0 commentaires