ProviderManager.authenticate为BadCredentialsException调用了两次

Mar*_*her 6 spring spring-security

Spring 4.1和Spring Security 3.2:我们实现了一个自定义身份验证提供程序,如果用户输入的密码不正确,则会抛出BadCredentialsException.抛出BadCredentialsException时,将调用ProviderManager.authenticate方法,该方法再次调用自定义身份验证中的authenticate方法.抛出LockedException时,不会再次调用自定义身份验证提供程序中的authenicate方法.我们计划保留登录尝试次数,因此我们不希望将authenticate方法调用两次.有谁知道为什么自定义身份验证类中的authenticate方法将被调用两次?

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

定制认证提供商:

@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));
    }
Run Code Online (Sandbox Code Playgroud)

hol*_*s83 7

原因是您添加了两次身份验证提供程序,一次configure(HttpSecurity)和一次configure(AuthenticationManagerBuilder).这将创建一个ProviderManager包含两个项目,两者都是您的提供者.

在处理身份验证,供应商将被要求以直至成功制成,除非一个LockedException或类似的状况异常被抛出,则循环将打破.

  • 在我的例子中,我没有两次定义身份验证提供程序的`configure(AuthenticationManagerBuilder)`方法,但它仍然被调用了两次.我通过创建该方法解决了我的问题,并删除了`configure(HttpSecurity)`中的行. (2认同)

sed*_*ooe 5

可能有一种情况您没有覆盖,configure(AuthenticationManagerBuilder)并且仍然调用了相同AuthenticationProverauthenticate方法两次,就像 Phil 在他在接受的答案中的评论中提到的那样。

这是为什么?

原因是当你没有覆盖configure(AuthenticationManagerBuilder)并拥有一个AuthenticationProviderbean时,它会被Spring Security注册,你不需要做任何其他事情。

但是,当configure(AuthenticationManagerBuilder)被覆盖时,Spring Security 将调用它并且不会尝试自行注册任何提供程序。如果你好奇,你可以看看相关的方法disableLocalConfigureAuthenticationBldr如果您覆盖configure(AuthenticationManagerBuilder).

所以,简单地说,如果你想注册只有一个自定义的AuthenticationProvider话不重写configure(AuthenticationManagerBuilder),不叫authenticationProvider(AuthenticationProvider)configure(HttpSecurity),只是让你AuthenticationProviver通过注释实现bean @Component,你是好去。