连接多种身份验证机制Spring Boot Security

时间:2019-01-24 11:09:32

标签: spring spring-boot authentication spring-security

我的应用程序具有一个安全配置,该配置通过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;
    }

}

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

3 个答案:

答案 0 :(得分:4)

Spring不会使用多个AuthenticationProvider来认证请求,因此第一个(在ArrayList中)AuthenticationProvider支持Authentication对象并成功进行认证该请求将是唯一使用的请求。在您的情况下为activeDirectoryLdapAuthenticationProvider

代替使用ActiveDirectoryLdapAuthenticationProvider,您可以使用委托给LDAP并执行其他检查的自定义AuthenticationProvider:

    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
        }

    }

答案 1 :(得分:1)

作为示例,可以使用多种身份验证机制: 找到代码

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

答案 2 :(得分:1)

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

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

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

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

注意:当然,除了扩展AbstractUserDetailsAuthenticationProvider之外,您还可以自己创建一个更简单的版本。

总的来说,我怀疑创建自定义的UserDetailsContextMapper是最简单的,如果在数据库中找不到,则抛出UsernameNotFoundException。这样,正常流程仍然适用,您可以重用大多数现有基础结构。