一.背景

图形验证码的生成与使用(防止机器恶意攻击)

二.代码(使用)

1.添加Maven依赖

<!--验证码 -->
<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
    <exclusions>
        <exclusion>
            <artifactId>javax.servlet-api</artifactId>
            <groupId>javax.servlet</groupId>
        </exclusion>
    </exclusions>
</dependency>

2. 验证码配置类

package com.provider.auth.config;

import com.google.code.kaptcha.impl.DefaultKaptcha;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.google.code.kaptcha.util.Config;

import java.util.Properties;

/**
 * 验证码配置
 *
 */
@Configuration
public class CaptchaConfig {

    @Bean(name = "captchaProducer")
    public DefaultKaptcha getKaptchaBean() {
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        Properties properties = new Properties();
        // 是否有边框 默认为true 我们可以自己设置yes,no
        properties.setProperty("kaptcha.border", "yes");
        // 边框颜色 默认为Color.BLACK
        properties.setProperty("kaptcha.border.color", "105,179,90");
        // 验证码文本字符颜色 默认为Color.BLACK
        properties.setProperty("kaptcha.textproducer.font.color", "blue");
        // 验证码图片宽度 默认为200
        properties.setProperty("kaptcha.image.width", "160");
        // 验证码图片高度 默认为50
        properties.setProperty("kaptcha.image.height", "60");
        // 验证码文本字符大小 默认为40
        properties.setProperty("kaptcha.textproducer.font.size", "30");
        // KAPTCHA_SESSION_KEY
        properties.setProperty("kaptcha.session.key", "kaptchaCode");
        // 验证码文本字符间距 默认为2
        properties.setProperty("kaptcha.textproducer.char.space", "3");
        // 验证码文本字符长度 默认为5
        properties.setProperty("kaptcha.textproducer.char.length", "5");
        // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
        properties.setProperty("kaptcha.textproducer.font.names", "Arial,Courier");
        // 验证码噪点颜色 默认为Color.BLACK
        properties.setProperty("kaptcha.noise.color", "white");
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }

    @Bean(name = "captchaProducerMath")
    public DefaultKaptcha getKaptchaBeanMath() {
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        Properties properties = new Properties();
        // 是否有边框 默认为true 我们可以自己设置yes,no
        properties.setProperty("kaptcha.border", "yes");
        // 边框颜色 默认为Color.BLACK
        properties.setProperty("kaptcha.border.color", "105,179,90");
        // 验证码文本字符颜色 默认为Color.BLACK
        properties.setProperty("kaptcha.textproducer.font.color", "blue");
        // 验证码图片宽度 默认为200
        properties.setProperty("kaptcha.image.width", "160");
        // 验证码图片高度 默认为50
        properties.setProperty("kaptcha.image.height", "60");
        // 验证码文本字符大小 默认为40
        properties.setProperty("kaptcha.textproducer.font.size", "35");
        // KAPTCHA_SESSION_KEY
        properties.setProperty("kaptcha.session.key", "kaptchaCodeMath");
        // 验证码文本生成器 【com.provider.auth.config.KaptchaTextCreator 自定义的验证码创建类 后面有附上】
        properties.setProperty("kaptcha.textproducer.impl", "com.provider.auth.config.KaptchaTextCreator");
        // 验证码文本字符间距 默认为2
        properties.setProperty("kaptcha.textproducer.char.space", "3");
        // 验证码文本字符长度 默认为5
        properties.setProperty("kaptcha.textproducer.char.length", "6");
        // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
        properties.setProperty("kaptcha.textproducer.font.names", "Arial,Courier");
        // 验证码噪点颜色 默认为Color.BLACK
        properties.setProperty("kaptcha.noise.color", "white");
        // 干扰实现类
        properties.setProperty("kaptcha.noise.impl", "com.google.code.kaptcha.impl.NoNoise");
        // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy
        properties.setProperty("kaptcha.obscurificator.impl", "com.google.code.kaptcha.impl.ShadowGimpy");
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
}

 

3. 验证码文本生成器 KaptchaTextCreator

 

package com.provider.auth.config;

import java.util.Random;

import com.google.code.kaptcha.text.impl.DefaultTextCreator;

/**
 * 验证码文本生成器
 *
 */
public class KaptchaTextCreator extends DefaultTextCreator {

    private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(",");

    @Override
    public String getText() {
        Integer result = 0;
        Random random = new Random();
        int x = random.nextInt(10);
        int y = random.nextInt(10);
        StringBuilder suChinese = new StringBuilder();
        int randomoperands = (int) Math.round(Math.random() * 2);
        if (randomoperands == 0) {
            result = x * y;
            suChinese.append(CNUMBERS[x]);
            suChinese.append("*");
            suChinese.append(CNUMBERS[y]);
        } else if (randomoperands == 1) {
            if (!(x == 0) && y % x == 0) {
                result = y / x;
                suChinese.append(CNUMBERS[y]);
                suChinese.append("/");
                suChinese.append(CNUMBERS[x]);
            } else {
                result = x + y;
                suChinese.append(CNUMBERS[x]);
                suChinese.append("+");
                suChinese.append(CNUMBERS[y]);
            }
        } else if (randomoperands == 2) {
            if (x >= y) {
                result = x - y;
                suChinese.append(CNUMBERS[x]);
                suChinese.append("-");
                suChinese.append(CNUMBERS[y]);
            } else {
                result = y - x;
                suChinese.append(CNUMBERS[y]);
                suChinese.append("-");
                suChinese.append(CNUMBERS[x]);
            }
        } else {
            result = x + y;
            suChinese.append(CNUMBERS[x]);
            suChinese.append("+");
            suChinese.append(CNUMBERS[y]);
        }
        suChinese.append("[email protected]" + result);
        return suChinese.toString();
    }

}

4.接口的使用Controller 本controller是把生成的code放到redis里面并设置过期时间,每次请求前端携带UUID作为键

来标识那个验证码,还有一种方式是直接用session来储存这里不做过多的演示

package com.provider.auth.web;


import java.awt.image.BufferedImage;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.config.redis.RedisHelper;
import com.core.base.web.AjaxResult;
import com.core.base.web.BaseController;
import com.core.constant.GlobalConstant;
import com.provider.auth.config.TokenDefaultTimeConfig;
import com.utils.common.ResponseUtils;
import com.utils.verify.StringUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.google.code.kaptcha.Producer;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * 图片验证码(支持算术形式)
 *
 */
@RestController
@RequestMapping("/captcha")
@Api(tags = "图片验证码接口", description = "图片验证码接口")
public class CaptchaController extends BaseController {

    @Resource(name = "captchaProducer")
    private Producer captchaProducer;

    @Resource(name = "captchaProducerMath")
    private Producer captchaProducerMath;

    @Autowired
    private RedisHelper redisHelper;

    @Autowired
    private TokenDefaultTimeConfig tokenDefaultTimeConfig;

    /**
     * 验证码生成
     *
     * @param request    HttpServletRequest
     * @param response   HttpServletResponse
     * @param type       captcha type
     * @param identifier identifier
     * @return AjaxResult
     * @author fangyi
     * @date 2019/5/17
     */
    @ApiOperation(value = "验证码生成")
    @GetMapping(value = "/captchaImage")
    public AjaxResult getKaptchaImage(HttpServletRequest request,
                                      HttpServletResponse response,
                                      @RequestParam(value = "type") String type,
                                      @RequestParam("identifier") String identifier) {
        try {
            String capStr;
            String code = null;
            BufferedImage bufferedImage = null;
            // GlobalConstant.CAPTCHA_MATH 类型文尾有附
            if (GlobalConstant.CAPTCHA_MATH.equals(type)) {
                String capText = captchaProducerMath.createText();
                capStr = capText.substring(0, capText.lastIndexOf("@"));
                code = capText.substring(capText.lastIndexOf("@") + 1);
                bufferedImage = captchaProducerMath.createImage(capStr);
            } else if (GlobalConstant.CAPTCHA_CHAR.equals(type)) {
                capStr = code = captchaProducer.createText();
                bufferedImage = captchaProducer.createImage(capStr);
            }
            if (bufferedImage == null) {
                return AjaxResult.error("0401011");
            }
            // 放入redis中,并设置失效时间(默认10分钟)
            // 这里也可采用放入session中的方案
            redisHelper.set(identifier, code, tokenDefaultTimeConfig.getCaptchaValidity());
           // ResponseUtils工具类文尾有附
            ResponseUtils.responseBufferedImage(request, response, bufferedImage);
        } catch (Exception e) {
            e.printStackTrace();
            return AjaxResult.error("500");
        }
        return null;
    }

    /**
     * 验证码验证
     *
     * @param code       captcha code
     * @param identifier identifier
     * @return AjaxResult
     * @author fangyi
     * @date 2019/5/17
     */
    @ApiOperation(value = "验证码验证")
    @GetMapping(value = "/checkCaptchaImage")
    public AjaxResult checkCaptchaImage(@RequestParam(value = "code") String code,
                                        @RequestParam("identifier") String identifier) {
        String redisCode = (String) redisHelper.get(identifier);
        if (StringUtils.isBlank(redisCode)) {
            return AjaxResult.error("0401012");
        }
        if (!redisCode.equals(code)) {
            return AjaxResult.error("0401012");
        }
        return AjaxResult.success();
    }
}

5.结果展示

type 有以下两种方式(上面代码的常量如下)

/*** 验证码生成数学算术类型*/ String CAPTCHA_MATH = "math";

/*** 验证码生成字符串类型*/ String CAPTCHA_CHAR = "char";

type=math如图

http://localhost:8096/captcha/captchaImage?identifier=your UUID &type=math

kaptcha 验证码技术

type=char如图:

http://localhost:8096/captcha/captchaImage?identifier=your UUID &type=char

kaptcha 验证码技术

附:ResponseUtils 响应工具类

package com.utils.common;

import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

import static java.lang.System.out;

/**
 * 响应工具类
 *
 */
public class ResponseUtils {
    public ResponseUtils() {
    }

    public static void setResponseFileHeader(HttpServletRequest request, HttpServletResponse response, String filename, String type, Long size) throws UnsupportedEncodingException {
        String userAgent = request.getHeader("User-Agent");
        if (!userAgent.contains("MSIE") && !userAgent.contains("Trident")) {
            filename = new String(filename.getBytes("UTF-8"), "ISO-8859-1");
        } else {
            filename = URLEncoder.encode(filename, "UTF-8");
        }

        response.addHeader("Content-Disposition", "attachment;filename=" + new String(filename.getBytes(), "UTF-8"));
        response.addHeader("Content-Length", "" + size);
        response.setContentType("application/octet-stream");
    }

    public static void responseBufferedImage(HttpServletRequest request, HttpServletResponse response, BufferedImage bufferedImage) throws IOException {
        if (bufferedImage != null) {
            // 将bufferedImage写入HttpServletResponse OutputStream中
            ServletOutputStream out = null;
            try {
                response.setDateHeader("Expires", 0);
                response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
                response.addHeader("Cache-Control", "post-check=0, pre-check=0");
                response.setHeader("Pragma", "no-cache");
                response.setContentType("image/jpeg");
                out = response.getOutputStream();
                ImageIO.write(bufferedImage, "jpg", out);
                out.flush();
            } finally {
                if (out != null) {
                    out.close();
                }
            }
        }

    }

}

AjaxResult为自定义返回结果类,可以自行自定义 如 code msg data 等

相关文章: