【问题标题】:Connect multiple authentication mechanisms Spring Boot Security连接多种认证机制 Spring Boot Security
【发布时间】:2019-06-18 02:32:27
【问题描述】:

我的应用程序有一个安全配置,它通过LDAP 对用户进行身份验证。这效果很好,但现在我想添加另一个AuthenticationProvider,它对尝试进行身份验证的用户进行更多检查。所以我尝试添加一个DbAuthenticationProvider(用于测试目的)总是拒绝访问。因此,当我尝试使用我的域帐户(适用于 activeDirectoryLdapAuthenticationProvider)登录时,我无法访问该页面,因为第二个提供商未通过身份验证。

为了实现这个目标,我使用了以下代码:

@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;
    }
}

这是我的DbAuthenticationProvider

@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;
    }

}

遗憾的是,我能够登录(访问没有像我预期的那样被拒绝)。我错过了什么吗?

【问题讨论】:

  • 只是一个提示:您可能想查找 redhats keycloak,它可以满足您的要求,并且开箱即用,并且具有出色的 spring 集成。
  • 不确定我是否关注。如果其中任何一个通过请求,则身份验证提供程序将按顺序运行。因此,在您的情况下,ldap 提供程序会处理身份验证并验证请求。这不是行为吗?
  • @Veeram 我想要的行为是在此步骤中通过 ldap 和 db 进行身份验证。因此,请检查通过 ldap 提供程序和数据库提供程序进行的身份验证(如果用户在数据库中并且通过 ldap 进行身份验证,则对请求进行身份验证,如果不是 - 不要)。

标签: spring spring-boot authentication spring-security


【解决方案1】:

Spring 不会使用多个AuthenticationProvider 来验证请求,因此支持Authentication 对象并成功验证请求的第一个(在ArrayList 中)AuthenticationProvider 将是唯一使用的.在你的情况下,它是activeDirectoryLdapAuthenticationProvider

您可以使用委托给 LDAP 并进行额外检查的自定义 AuthenticationProvider,而不是使用 ActiveDirectoryLdapAuthenticationProvider

    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
        }

    }

【讨论】:

  • 这是一个很好的方法,遗憾的是ActiveDirectoryLdapAuthenticationProvider 是最后一课,我可以以某种方式解决这个问题吗?
  • 你能告诉我如何做这部分“在你的配置过程中添加额外的方法来初始化委托”吗?你的方法是最好的
【解决方案2】:

AuthenticationProvider 不是这样工作的,只会咨询一个进行身份验证。显然你想结合来自 LDAP 和数据库的一些信息。为此,您可以配置自定义UserDetailsContextMapper 和/或GrantedAuthoritiesMapper。默认实现将使用来自 LDAP 的信息来构建UserDetails 及其GrantedAuthorities,但是您可以实现咨询数据库的策略。

另一种解决方案是使用LdapUserDetailsService,它允许您使用常规DaoAuthenticationProvider。该名称具有误导性,因为它实际上需要UserDetailsService。这个AuthenticationProvider 使用UserDetailsChecker 进行附加检查,默认情况下检查UserDetails 上的一些属性,但可以通过附加检查进行扩展。

注意:LdapUserDetailsService 使用普通 LDAP,所以我不知道这是否适用于稍微不同的 Active Directory 方法!

最终的解决方案可能是创建一个从AbstractUserDetailsAuthenticationProvider 扩展的DelegatingAuthenticationProvider,以便您可以重用其中的逻辑来利用UserDetailsChecker。然后retrieveUser 方法将委托给实际的ActiveDirectoryLdapAuthenticationProvider 进行身份验证。

注意:您当然也可以自己创建一个更简单的版本,而不是扩展 AbstractUserDetailsAuthenticationProvider

总而言之,我怀疑创建自定义 UserDetailsContextMapper 将是最简单的,当在 DB 中找不到时抛出 UsernameNotFoundException。这样,正常流程仍然适用,您可以重用大部分现有基础架构。

【讨论】:

    【解决方案3】:

    作为解决多重身份验证机制的示例: 找到代码

    @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();
        }
    } 
    

    在 Spring Security 中配置了两个身份验证提供者

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

    有助于在 java config 中配置多个身份验证提供程序的配置。

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

    configure 方法中的objectPostProcessor 需要 AuthenticationManagerBuilder 来实际构建对象,然后我们才能访问和更改提供者的顺序

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

    【讨论】:

      猜你喜欢
      • 2015-03-17
      • 2020-11-14
      • 2018-02-03
      • 2012-08-15
      • 2016-08-11
      • 1970-01-01
      • 1970-01-01
      • 2014-08-02
      • 2015-03-11
      相关资源
      最近更新 更多