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 Réponses :
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 >
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 } }
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
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.
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).