图形验证码及其重构

Posted fly-book

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图形验证码及其重构相关的知识,希望对你有一定的参考价值。

图形验证码

        <!--验证码组件-->
        <dependency>
            <groupId>com.github.penggle</groupId>
            <artifactId>kaptcha</artifactId>
            <version>2.3.2</version>
        </dependency>

定义验证码

public class ImageCode {
    private String code;
    //有效期
    private LocalDateTime expireTime;
    private BufferedImage image;

    public ImageCode(String code, int expireTime, BufferedImage image) {
        this.code = code;
        this.expireTime = LocalDateTime.now().plusSeconds(expireTime);
        this.image = image;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public LocalDateTime getExpireTime() {
        return expireTime;
    }

    public void setExpireTime(LocalDateTime expireTime) {
        this.expireTime = expireTime;
    }

    public BufferedImage getImage() {
        return image;
    }

    public void setImage(BufferedImage image) {
        this.image = image;
    }

    public boolean isExpried() {
        return LocalDateTime.now().isAfter(expireTime);
    }
}

请求的验证码会保存到session中

@RestController
public class ValidateCodeController {
    public static final String SESSION_KEY = "captcha";

    @Autowired
    private Producer captchaProducer;
    @Bean
    public Producer captcha(){
        Properties properties = new Properties();
        properties.setProperty("kaptcha.image.width","150");
        properties.setProperty("kaptcha.image.height","150");
        //字符集
        properties.setProperty("kaptcha.textproducer.char.string","0123456789");
        //字符长度
        properties.setProperty("kaptcha.textproducer.char.length","4");
        Config config = new Config(properties);
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }

    @GetMapping("/captcha")
    public void getCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setContentType("image/jpeg");
        String capText = captchaProducer.createText();
        BufferedImage bi = captchaProducer.createImage(capText);
        ImageCode imageCode = new ImageCode(capText,60,bi);
        request.getSession().setAttribute(SESSION_KEY,imageCode);
        ServletOutputStream out = response.getOutputStream();
        ImageIO.write(bi,"jpg",out);
        try {
            out.flush();
        } finally {
            out.close();
        }
    }
}
/**
 * 验证码异常
 */
public class VerificationCodeException extends AuthenticationException {
    public VerificationCodeException(String msg) {
        super(msg);
    }
}

验证过滤器

public class ValidateCodeFilter extends OncePerRequestFilter {
    public ValidateCodeFilter(AuthenticationFailureHandler flyAuthenticationFailureHandler) {
        this.flyAuthenticationFailureHandler = flyAuthenticationFailureHandler;
    }

    private AuthenticationFailureHandler flyAuthenticationFailureHandler;

    public AuthenticationFailureHandler getFlyAuthenticationFailureHandler() {
        return flyAuthenticationFailureHandler;
    }

    public void setFlyAuthenticationFailureHandler(AuthenticationFailureHandler flyAuthenticationFailureHandler) {
        this.flyAuthenticationFailureHandler = flyAuthenticationFailureHandler;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        if ("/authentication/form".equals(httpServletRequest.getRequestURI())
            &&"post".equalsIgnoreCase(httpServletRequest.getMethod())){
            try {
                validate(httpServletRequest);
            }catch (VerificationCodeException e){
                flyAuthenticationFailureHandler.onAuthenticationFailure(httpServletRequest,httpServletResponse,e);
                return;
            }
        }
        filterChain.doFilter(httpServletRequest,httpServletResponse);
    }

    private void validate(HttpServletRequest request) {
        HttpSession session = request.getSession();
        ImageCode codeInSession = (ImageCode) session.getAttribute(ValidateCodeController.SESSION_KEY);
        String imageCode = request.getParameter("imageCode");
        if (StringUtils.isEmpty(imageCode)){
            throw new VerificationCodeException("验证码的值不能为空");
        }
        if (codeInSession==null){
            throw new VerificationCodeException("验证码不存在");
        }
        if (codeInSession.isExpried()){
            session.removeAttribute(ValidateCodeController.SESSION_KEY);
            throw new VerificationCodeException("验证码已过期");
        }
        if (!imageCode.equals(codeInSession.getCode())){
            throw new VerificationCodeException("验证码不匹配");
        }
        session.removeAttribute(ValidateCodeController.SESSION_KEY);
    }
}

将验证码过滤加到UsernamePasswordAuthenticationFilter前面,将图片验证码请求允许访问

  @Override
    protected void configure(HttpSecurity http) throws Exception {
        ValidateCodeFilter codeFilter = new ValidateCodeFilter(flyAuthenticationFailureHandler);
            http
                .addFilterBefore(codeFilter, UsernamePasswordAuthenticationFilter.class)
                .formLogin()
                .loginPage("/authentication/request")
                .loginProcessingUrl("/authentication/form")
                .successHandler(flyAuthenticationSuccessHandler)
                .failureHandler(flyAuthenticationFailureHandler)
                .and()
                .authorizeRequests()
                .antMatchers("/authentication/request",
                        securityProperties.getBrowser().getLoginPage(),
                        "/captcha")
                .permitAll()
                .anyRequest().authenticated()
                .and().csrf().disable();
    }

开始login吧

<form action="/authentication/form" method="post">
    用户名: <input type="text" name="username"><br>
    密码: <input type="password" name="password"><br>
    验证码:<input type="text" name="imageCode"><img src="/captcha">
    <input type="submit">
</form>

图形验证码重构

public interface ValidateCodeGenerator {
    ImageCode generate(HttpServletRequest request);
}
public class ImageCodeGenerator implements ValidateCodeGenerator {
    @Autowired
    private SecurityProperties securityProperties;
    public void setSecurityProperties(SecurityProperties securityProperties) {
        this.securityProperties = securityProperties;
    }

    private Producer captcha(){
        Properties properties = new Properties();
        properties.setProperty("kaptcha.image.width",String.valueOf(securityProperties.getCode().getImageCode().getWidth()));
        properties.setProperty("kaptcha.image.height",String.valueOf(securityProperties.getCode().getImageCode().getHeight()));
        //字符集
        properties.setProperty("kaptcha.textproducer.char.string","0123456789");
        //字符长度
        properties.setProperty("kaptcha.textproducer.char.length",String.valueOf(securityProperties.getCode().getImageCode().getLength()));
        Config config = new Config(properties);
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
    @Override
    public ImageCode generate(HttpServletRequest request) {
        Producer captchaProducer = captcha();
        String capText = captchaProducer.createText();
        BufferedImage bi = captchaProducer.createImage(capText);
        return new ImageCode(capText,securityProperties.getCode().getImageCode().getExpireIn(),bi);
    }
}

只需创建继承ValidateCodeGenerator的imageValidateCodeGenerator的bean就可以覆盖实现方法

@Configuration
public class ValidateCodeBeanConfig {
    @Autowired
    private SecurityProperties securityProperties;

    @Bean
    @ConditionalOnMissingBean(name = "imageValidateCodeGenerator")
    public ValidateCodeGenerator imageValidateCodeGenerator(){
        ImageCodeGenerator codeGenerator = new ImageCodeGenerator();
        codeGenerator.setSecurityProperties(securityProperties);
        return codeGenerator;
    }
}
@RestController
public class ValidateCodeController {
    public static final String SESSION_KEY = "captcha";

    @Autowired
    private ValidateCodeGenerator imageCodeGenerator;

    @GetMapping("/captcha")
    public void getCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setContentType("image/jpeg");
        ImageCode imageCode = imageCodeGenerator.generate(request);
        request.getSession().setAttribute(SESSION_KEY,imageCode);
        ServletOutputStream out = response.getOutputStream();
        ImageIO.write(imageCode.getImage(),"jpg",out);
        try {
            out.flush();
        } finally {
            out.close();
        }
    }
}

用户自己实现

@Component("imageValidateCodeGenerator")
public class MyImageValidateCodeGenerator implements ValidateCodeGenerator {
    public ImageCode generate(HttpServletRequest request) {
        System.out.println("图形验证码实现");
        //。。。
        return null;
    }
}

以上是关于图形验证码及其重构的主要内容,如果未能解决你的问题,请参考以下文章

015 图像验证码

springboot验证码重构

原创干货 | Java代码审计之图形验证码模块

重构客户注册-基于ActiveMQ实现短信验证码生产者

python web框架Flask——图形验证码及验证码的动态刷新

验证码识别——图形验证码