2015-11-18 37 views
3

Spring 4.1和Spring Security 3.2: 我們實現了一個自定義身份驗證提供程序,如果用戶輸入不正確的密碼,則會引發BadCredentialsException。 當引發BadCredentialsException時,將調用ProviderManager.authenticate方法,該方法會再次在自定義身份驗證中調用身份驗證方法。當拋出LockedException時,自定義驗證提供程序中的驗證方法不會再次調用。我們計劃保持登錄嘗試次數,所以我們不希望將驗證方法稱爲兩次。有誰知道爲什麼自定義認證類中的認證方法會被調用兩次?ProviderManager.authenticate爲BadCredentialsException調用兩次

WebConfig: 


    @Configuration 
@EnableWebMvcSecurity 
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 

    @Autowired 
    private CustomAuthenticationProvider customAuthenticationProvider; 

    @Autowired 
    private AMCiUserDetailsService userDetailsService; 

    @Autowired 
    private CustomImpersonateFailureHandler impersonateFailureHandler; 

    @Autowired 
    private LoginFailureHandler loginFailureHandler; 

    @Override 
    protected void configure(HttpSecurity http) throws Exception { 
     http 
      .csrf().disable() 
      .authorizeRequests() 
       .antMatchers("/jsp/*.css","/jsp/*.js","/images/**").permitAll() 
       .antMatchers("/login/impersonate*").access("hasRole('ADMIN') or hasRole('ROLE_PREVIOUS_ADMINISTRATOR')") 
       .anyRequest().authenticated()          
       .and() 
      .formLogin() 
       .loginPage("/login.jsp") 
       .defaultSuccessUrl("/jsp/Home.jsp",true)     
       .loginProcessingUrl("/login.jsp")         
       .failureHandler(loginFailureHandler) 
       .permitAll() 
       .and() 
      .logout() 
       .logoutSuccessUrl("/login.jsp?msg=1") 
       .permitAll() 
       .and() 
      .addFilter(switchUserFilter()) 
      .authenticationProvider(customAuthenticationProvider); 

      http.exceptionHandling().accessDeniedPage("/jsp/SecurityViolation.jsp"); //if user not authorized to a page, automatically forward them to this page. 
      http.headers().addHeaderWriter(new XFrameOptionsHeaderWriter(XFrameOptionsMode.SAMEORIGIN)); 
    } 


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

    @Bean 
    public PasswordEncoder passwordEncoder() { 
     return new BCryptPasswordEncoder(); 
    } 

    //Used for the impersonate functionality 
    @Bean CustomSwitchUserFilter switchUserFilter() { 
     CustomSwitchUserFilter filter = new CustomSwitchUserFilter(); 
     filter.setUserDetailsService(userDetailsService); 
     filter.setTargetUrl("/jsp/Impersonate.jsp?msg=0"); 
     filter.setSwitchUserUrl("/login/impersonate"); 
     filter.setExitUserUrl("/logout/impersonate"); 
     filter.setFailureHandler(impersonateFailureHandler); 
     return filter; 
    } 
} 

自定義身份驗證提供者:

@Component 
public class CustomAuthenticationProvider implements AuthenticationProvider { 

    @Autowired(required = true) 
    private HttpServletRequest request; 

    @Autowired 
    private AMCiUserDetailsService userService; 

    @Autowired 
    private PasswordEncoder encoder; 

    public Authentication authenticate(Authentication authentication) throws AuthenticationException { 

     String username = authentication.getName().trim(); 
     String password = ((String) authentication.getCredentials()).trim(); 
     if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) { 
      throw new BadCredentialsException("Login failed! Please try again."); 
     } 


     UserDetails user; 
     try { 
      user = userService.loadUserByUsername(username); 
      //log successful attempt 
      auditLoginBean.setComment("Login Successful"); 
      auditLoginBean.insert(); 
     } catch (Exception e) { 
      try { 
       //log unsuccessful attempt 
       auditLoginBean.setComment("Login Unsuccessful"); 
       auditLoginBean.insert(); 
      } catch (Exception e1) { 
       // TODO Auto-generated catch block 
      } 
      throw new BadCredentialsException("Please enter a valid username and password."); 
     } 

     if (!encoder.matches(password, user.getPassword().trim())) { 
      throw new BadCredentialsException("Please enter a valid username and password."); 
     } 

     if (!user.isEnabled()) { 
      throw new DisabledException("Please enter a valid username and password."); 
     } 

     if (!user.isAccountNonLocked()) { 
      throw new LockedException("Account locked. "); 
     } 

     Collection<? extends GrantedAuthority> authorities = user.getAuthorities(); 
     List<GrantedAuthority> permlist = new ArrayList<GrantedAuthority>(authorities); 

     return new UsernamePasswordAuthenticationToken(user, password, permlist); 
    } 


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

回答

3

的原因是,你把你的身份驗證提供兩次,在configure(HttpSecurity)一個時間和configure(AuthenticationManagerBuilder)一次。這將創建一個包含兩個項目的ProviderManager,這兩個項目都是您的提供者。

當處理認證時,供應商將被詢問,直到成功,除非 a LockedException或類似的狀態異常被拋出,那麼循環會中斷。

+0

是的,就是這樣。非常感謝你!。配置(AuthenticationManagerBuilder身份驗證)方法已被刪除,並且一切正常。 –

+0

@MariaSpeicher很高興聽到,不要忘記將答案標記爲接受:) – holmis83

+2

在我的情況下,我沒有'configure(AuthenticationManagerBuilder)'方法定義兩次認證提供程序,但它仍被調用兩次。我通過創建該方法解決了我的問題,並刪除了'configure(HttpSecurity)'中的行。 – Phil

1

有可能是一種情況,你不覆蓋configure(AuthenticationManagerBuilder)和仍然AuthenticationProverauthenticate方法被調用兩次像菲爾在他接受的答案中的評論中提到的。

這是爲什麼?

原因是,如果你不覆蓋configure(AuthenticationManagerBuilder)並且有一個AuthenticationProvider bean,它將被Spring Security註冊,你不必做任何事情。

但是,當覆蓋configure(AuthenticationManagerBuilder)時,Spring Security將調用它並且不會嘗試單獨註冊任何提供程序。 如果你好奇,你可以看看related method。如果您覆蓋configure(AuthenticationManagerBuilder),則disableLocalConfigureAuthenticationBldr爲真。

所以,簡單地說,如果你想註冊只有一個自定義的AuthenticationProvider則不會覆蓋configure(AuthenticationManagerBuilder),不叫authenticationProvider(AuthenticationProvider)configure(HttpSecurity),只是通過註釋@Component讓你AuthenticationProviver實現bean,你是好去。