添加登录验证码

在第 3 章中介绍了一种登录验证码的实现方案,我们通过学习了 Spring Security 的过滤器链之后,可能也会发现,使用过滤虑器链来实现登录验证码更加容易。这里就介绍一下使用过滤器实现验证码的方案。

验证码的生成方案依然和 3.3 节中的一致,这里不再述,主要介绍一下 LoginFilter 的定义以及配置。先来看 LoginFilter 的定义:

public class LoginFilter extends UsernamePasswordAuthenticationFilter {

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException(
                    "Authentication method not supported: " + request.getMethod());
        }
        String kaptcha = request.getParameter("kaptcha");
        String sessionKaptcha = (String) request.getSession().getAttribute("kaptcha");
        if (!StringUtils.isEmpty(kaptcha) && !StringUtils.isEmpty(sessionKaptcha) && kaptcha.equalsIgnoreCase(sessionKaptcha)) {
            return super.attemptAuthentication(request, response);
        }
        throw new AuthenticationServiceException("验证码输入错误");
    }
}

在 LoginFilter 中首先判断验证码是否正确,如果验证码输入错误,则直接抛出异常;如果验证码输入正确,则调用父类的 attemptAuthentication 方法进行登录校验。接下来,在 SecurityConfig 中配置 LoginFilter:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth)
            throws Exception {
        auth.inMemoryAuthentication()
                .withUser("javaboy")
                .password("{noop}123")
                .roles("admin");
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean()
            throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    LoginFilter loginFilter() throws Exception {
        LoginFilter loginFilter = new LoginFilter();
        loginFilter.setFilterProcessesUrl("/doLogin");
        loginFilter.setAuthenticationManager(authenticationManagerBean());
        loginFilter.setAuthenticationSuccessHandler(new SimpleUrlAuthenticationSuccessHandler("/hello"));
        loginFilter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler("/mylogin.html"));
        return loginFilter;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/vc.jpg").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/mylogin.html")
                .permitAll()
                .and()
                .csrf().disable();
        http.addFilterAt(loginFilter(),
                UsernamePasswordAuthenticationFilter.class);
    }
}

这里的配置基本和 4.6 节中的配置一致。不同的是,我们修改了登录请求的处理地址,注意这个地址要在 LoginFilter 实例上配置。

这里介绍的第二种添加登录验证码的方式,相比于第一种方式,这种验证码的添加方式更简单也更易于理解。