美文网首页
Java实现字符验证码、运算验证码

Java实现字符验证码、运算验证码

作者: YongSiv | 来源:发表于2019-08-21 11:12 被阅读0次

Java中很轻松可以实现验证码功能,在原生AWT图形化工具包中写一点简单的逻辑就能轻松完成验证码功能。
本文,同时将google的kaptcha验证码一同讲解。

项目地址:https://gitee.com/gester/captcha.git

同时,推一下滑动验证码的原理与实现的文章。文章地址:https://www.jianshu.com/p/6ff29737209f

原创不易!如果有帮到您,可以给作者一个小星星鼓励下 ^ _ ^

功能

  1. 字符验证码
    • AWT实现字符验证码
    • kaptcha实现字符验证码
  2. 运算验证码
    • AWT实现运算验证码
    • kaptcha实现运算验证码
  3. 滑动验证码(扩展)
    由于代码量较大,功能关联性不强。一篇文章不够清楚说明,将在另外一篇文章讲解。

相关依赖

        <dependency>
            <groupId>com.github.penggle</groupId>
            <artifactId>kaptcha</artifactId>
            <version>2.3.2</version>
        </dependency>

一. AWT实现验证码

字符验证码:

 /**
     * 生成字符验证码
     * @return 字符验证码
     */
    public static Map<String, Object> generatorCharVerificationCode() {
        //  验证码图片边框宽度
        final int WIDTH = 150;
        //  验证码图片边框高度
        final int HEIGHT = 50;
        //  验证码字符长度
        int CHAR_LENGTH = 6;
        //  验证码字体高度
        int FONT_HEIGHT = HEIGHT - 12;
        //  验证码干扰线条数
        int INTERFERENCE_LINE = 4;
        //  生成验证码所需字符
        char[] charSequence = {
                'A', 'B', 'C', 'D', 'E', 'F',
                'G', 'H', 'I', 'J', 'K', 'L',
                'M', 'N', 'O', 'P', 'Q', 'R',
                'S', 'T', 'U', 'V', 'W', 'X',
                'Y', 'Z', '0', '1', '2', '3',
                '4', '5', '6', '7', '8', '9'
        };
        Map<String, Object> verificationCodeMap = null;

        //  生成透明rgb图片
        BufferedImage bufferedImage = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
        Graphics graphics = bufferedImage.getGraphics();
        //  备份原始画笔颜色
        Color color = graphics.getColor();
        graphics.setColor(Color.BLACK);
        //  图片填充黑色
        graphics.fillRect(0, 0, WIDTH, HEIGHT);

        graphics.setColor(Color.WHITE);
        //  图片填充白色;组成黑色边框的白色图片
        graphics.fillRect(1, 1, WIDTH - 2, HEIGHT - 2);

        int newFontHeight = CHAR_LENGTH > 4 ? FONT_HEIGHT * 4 / CHAR_LENGTH : FONT_HEIGHT;

        //  设置画笔字体
        Font font = new Font("微软雅黑", Font.PLAIN, newFontHeight);
        graphics.setFont(font);
        //  根据系统时间创建随机数对象
        Random random = new Random(System.currentTimeMillis());
        int r = 0;
        int g = 0;
        int b = 0;
        //  验证码字符串
        StringBuilder verificationCode = new StringBuilder();
        for (int i = 0; i < CHAR_LENGTH; i++) {
            char ch = charSequence[random.nextInt(charSequence.length)];
            //   随机生成rgb颜色值,并设置画笔颜色
            r = random.nextInt(255);
            g = random.nextInt(255);
            b = random.nextInt(255);
            graphics.setColor(new Color(r, g, b));
            //  根据画笔颜色绘制字符
            graphics.drawString(String.valueOf(ch), i * (newFontHeight), FONT_HEIGHT);
            verificationCode.append(ch);
        }

        //  绘制干扰线
        int x1, y1, x2, y2;
        for (int i = 0; i < INTERFERENCE_LINE; i++) {
            //   随机生成rgb颜色值,并设置画笔颜色
            r = random.nextInt(255);
            g = random.nextInt(255);
            b = random.nextInt(255);
            graphics.setColor(new Color(r, g, b));
            x1 = random.nextInt(WIDTH);
            y1 = random.nextInt(HEIGHT);
            x2 = random.nextInt(WIDTH);
            y2 = random.nextInt(HEIGHT);
            //  绘制线条
            graphics.drawLine(x1, y1, x2, y2);
        }

        //  恢复画笔颜色
        graphics.setColor(color);

        verificationCodeMap = new HashMap<String, Object>();
        verificationCodeMap.put("verificationCodeImage", bufferedImage);
        verificationCodeMap.put("verificationCode", verificationCode);
        return verificationCodeMap;

    }

        public static void main(String[] args) throws IOException {
            Map<String, Object> charMap = generatorCharVerificationCode();
            BufferedImage bufferedImage1 = (BufferedImage) charMap.get("verificationCodeImage");

            OutputStream outputStream1 = new FileOutputStream("C:/Users/Administrator/Desktop/charVerificationCodeImage.png");
            ImageIO.write(bufferedImage1, "png", outputStream1);
            System.out.println("验证码: " + charMap.get("verificationCode"));
            outputStream1.flush();
            outputStream1.close();
        }

预览图

charVerificationCodeImage.png

运算验证码:


    static StringBuilder result = new StringBuilder();  // 运算验证码结果
    /**
     * 生成运算验证码
     * @return  运算验证码
     */
    public static Map<String, Object> generatorOperationVerificationCode() {
        //  验证码图片边框宽度
        final int WIDTH = 185;
        //  验证码图片边框高度
        final int HEIGHT = 50;
        //  验证码字体高度
        int FONT_HEIGHT = HEIGHT - 12;
        //  验证码干扰线条数
        int INTERFERENCE_LINE = 4;

        Map<String, Object> verificationCodeMap = null;

        //  生成透明rgb图片
        BufferedImage bufferedImage = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
        Graphics graphics = bufferedImage.getGraphics();
        //  备份原始画笔颜色
        Color color = graphics.getColor();
        graphics.setColor(Color.BLACK);
        //  图片填充黑色
        graphics.fillRect(0, 0, WIDTH, HEIGHT);

        graphics.setColor(Color.WHITE);
        //  图片填充白色;组成黑色边框的白色图片
        graphics.fillRect(1, 1, WIDTH - 2, HEIGHT - 2);

        //  验证码字符串
        String text = getText();
        //  运算表达式
        String operationExpression = text.substring(0, text.lastIndexOf("@") - 1);
        //  计算结果
        String result = text.substring(text.lastIndexOf("@") + 1, text.length());

        int newFontHeight = operationExpression.length() > 4 ? FONT_HEIGHT * 4 / operationExpression.length() : FONT_HEIGHT;

        //  设置画笔字体
        Font font = new Font("微软雅黑", Font.PLAIN, FONT_HEIGHT);
        graphics.setFont(font);
        //  根据系统时间创建随机数对象
        Random random = new Random(System.currentTimeMillis());
        int r = 0;
        int g = 0;
        int b = 0;

        //   随机生成rgb颜色值,并设置画笔颜色
        r = random.nextInt(255);
        g = random.nextInt(255);
        b = random.nextInt(255);
        graphics.setColor(new Color(r, g, b));
        //  根据画笔颜色绘制字符
        graphics.drawString(operationExpression, 5, FONT_HEIGHT);

        //  绘制干扰线
        int x1, y1, x2, y2;
        for (int i = 0; i < INTERFERENCE_LINE; i++) {
            //   随机生成rgb颜色值,并设置画笔颜色
            r = random.nextInt(255);
            g = random.nextInt(255);
            b = random.nextInt(255);
            graphics.setColor(new Color(r, g, b));
            x1 = random.nextInt(WIDTH);
            y1 = random.nextInt(HEIGHT);
            x2 = random.nextInt(WIDTH);
            y2 = random.nextInt(HEIGHT);
            //  绘制线条
            graphics.drawLine(x1, y1, x2, y2);
        }

        //  恢复画笔颜色
        graphics.setColor(color);

        verificationCodeMap = new HashMap<String, Object>();
        verificationCodeMap.put("verificationCodeImage", bufferedImage);
        verificationCodeMap.put("verificationCode", result);
        return verificationCodeMap;

    }

    /**
     * 获取运算验证码
     * @return 运算验证码
     */
    public static String getText() {
        Random random = new Random(System.currentTimeMillis());
        int x = random.nextInt(51);
        int y = random.nextInt(51);
        int operationalRules = random.nextInt(4);

        switch (operationalRules) {
            case 0:
                add(x, y);
                break;
            case 1:
                subtract(x, y);
                break;
            case 2:
                multiply(x, y);
                break;
            case 3:
                divide(x, y);
                break;
        }
        return result.toString();
    }

    /**
     * 加法运算
     * @param x 变量x
     * @param y 变量y
     */
    private static void add(int x, int y) {
        result.append(x);
        result.append(" + ");
        result.append(y);
        result.append(" = ?@");
        result.append(x + y);
    }

    /**
     * 减法运算
     * @param x 变量x
     * @param y 变量y
     */
    private static void subtract(int x, int y) {
        int max = Math.max(x, y);
        int min = Math.min(x, y);
        result.append(max);
        result.append(" - ");
        result.append(min);
        result.append(" = ?@");
        result.append(max - min);
    }

    /**
     * 乘法运算
     * @param x 变量x
     * @param y 变量y
     */
    private static void multiply(int x, int y) {
        int value = x * y;
        result.append(x);
        result.append(value > 100 ? " + " : " * ");
        result.append(y);
        result.append(" = ?@");
        result.append(value > 100 ? x + y : x * y);
    }

    /**
     * 出发运算
     * @param x 变量x
     * @param y 变量y
     */
    private static void divide(int x, int y) {
        int max = Math.max(x, y);
        int min = Math.min(x, y);
        if (min == 0) {
            multiply(max, min);
        } else if (max % min == 0) {
            result.append(max);
            result.append(" / ");
            result.append(min);
            result.append(" = ?@");
            result.append(max / min);
        } else {
            result.append(max);
            result.append(" % ");
            result.append(min);
            result.append(" = ?@");
            result.append(max % min);
        }
    }


    public static void main(String[] args) throws IOException {
        Map<String, Object> operationMap = generatorOperationVerificationCode();
        BufferedImage bufferedImage2 = (BufferedImage) operationMap.get("verificationCodeImage");

        OutputStream outputStream2 = new FileOutputStream("C:/Users/Administrator/Desktop/operationVerificationCodeImage.png");
        ImageIO.write(bufferedImage2, "png", outputStream2);
        System.out.println("验证码: " + operationMap.get("verificationCode"));
        outputStream2.flush();
        outputStream2.close();
    }

预览图

operationVerificationCodeImage.png

总的来说,AWT实现验证码非常简单,只需要编写简单的逻辑即可实现需要的验证码功能。代码中的验证码可以根据字符长度和运算表达式长度适应画布大小。但是,仍是不完美;这时,kaptcha就登场了,我们为啥要使用kapcha,kapcha的优势有什么?
kaptcha验证码是谷歌编写开源的,也是基于AWT实现。但是功能更加丰富,可以配置背景、画布、尺寸、颜色、样式、噪点等等诸多好处。项目具有轻量级、功能丰富、易于上手,且是大公司开源,稳定性有保障。为何不用?

kaptcha实现字符验证码:
默认情况下,kaptcha是默认生成英文数字组合的字符验证码。但是,这里功能很强大,可以自己编写验证码的生成器规则,注册到kapcha拦截器中,即可使用。运算验证码就是使用这一种方式,待会再议。

配置类代码:

@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",  "black");
        // 验证码文本字符颜色 默认为Color.BLACK
        properties.setProperty("kaptcha.textproducer.font.color", "black");
        // 验证码图片宽度 默认为200
        properties.setProperty("kaptcha.image.width", "150");
        // 验证码图片高度 默认为50
        properties.setProperty("kaptcha.image.height", "60");
        // 验证码文本字符大小 默认为40
        properties.setProperty("kaptcha.textproducer.font.size", "30");
        // KAPTCHA_SESSION_KEY
        properties.setProperty("kaptcha.session.key", "kaptchaCharCode");
        // 验证码文本字符间距 默认为2
        properties.setProperty("kaptcha.textproducer.char.space", "3");
        // 验证码文本字符长度 默认为4
        properties.setProperty("kaptcha.textproducer.char.length", "4");
        // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
        properties.setProperty("kaptcha.textproducer.font.names", "微软雅黑");
        // 验证码噪点颜色 默认为Color.BLACK
        properties.setProperty("kaptcha.noise.color",  "black");
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
}

生成字符验证码

 /**
     * 获取字符验证码
     *
     * @param imageVerificationDto 用户信息
     * @return 字符验证码
     * @throws ServiceException 获取字符验证码异常
     */
    private ImageVerificationVo selectCharVerificationCode(ImageVerificationDto imageVerificationDto) throws ServiceException {

        byte[] bytes = null;
        String text = "";
        BufferedImage bufferedImage = null;
        ImageVerificationVo imageVerificationVo = null;

        try {

            imageVerificationVo = new ImageVerificationVo();
            //  生成字符验证码文本
            text = captchaProducer.createText();
            //  生成字符验证码图片
            bufferedImage = captchaProducer.createImage(text);
            getRequest().getSession().setAttribute("imageVerificationVo", imageVerificationVo);
            //  在分布式应用中,可将session改为redis存储
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ImageIO.write(bufferedImage, "png", byteArrayOutputStream);
            bytes = byteArrayOutputStream.toByteArray();
            //  图片base64加密
            imageVerificationVo.setCharImage(Base64Utils.encodeToString(bytes));
            imageVerificationVo.setType(imageVerificationDto.getType() == null ? "char" : imageVerificationDto.getType());

        } catch (IOException e) {
            log.error(e.getMessage(), e);
            throw new ServiceException(ServiceExceptionCode.SELECT_VERIFICATION_CODE_ERROR);
        }
        return imageVerificationVo;

    }

预览图

QQ截图20190821105231.png

kaptcha实现运算验证码

配置类

@Configuration
public class CaptchaConfig
{
@Bean(name = "captchaProducerMath")
    public DefaultKaptcha getKaptchaBeanMath()
    {
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        Properties properties = new Properties();
        // 是否有边框 默认为true 我们可以自己设置yes,no
        properties.setProperty("kaptcha.border", "no");
        // 边框颜色 默认为Color.BLACK
        properties.setProperty("kaptcha.border.color", "55,160,204");
        // 验证码文本字符颜色 默认为Color.BLACK
        properties.setProperty("kaptcha.textproducer.font.color", "blue");
        // 背景渐变色,开始颜色
        properties.setProperty("kaptcha.background.clear.from", "234,172,236");
        // 背景渐变色,结束颜色
        properties.setProperty("kaptcha.background.clear.to", "234,144,115");
        // 验证码图片宽度 默认为200
        properties.setProperty("kaptcha.image.width", "170");
        // 验证码图片高度 默认为50
        properties.setProperty("kaptcha.image.height", "60");
        // 验证码文本字符大小 默认为40
        properties.setProperty("kaptcha.textproducer.font.size", "35");
        // KAPTCHA_SESSION_KEY
        properties.setProperty("kaptcha.session.key", "kaptchaMathCode");
// --------------验证码文本生成器,这里需要设置成自己项目的包名----------------------
        properties.setProperty("kaptcha.textproducer.impl", "com.selfimpr.captcha.config.KaptchaMathTextCreator");
        // 验证码文本字符间距 默认为2
        properties.setProperty("kaptcha.textproducer.char.space", "3");
        // 验证码文本字符长度 默认为9
        properties.setProperty("kaptcha.textproducer.char.length", "9");
        // 验证码文本字体样式 默认为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", "243,79,67");
        // 干扰实现类
//        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;
    }
}

注意。这里注册了自定义验证码生成器

image.png

自定义运算器,同AWT生成运算验证码的运算方法没有区别。

package com.selfimpr.captcha.config;


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

import java.util.Random;

/**
 * @Description: 运算验证码
 * -------------------
 * @Author: YangXingfu
 * @Date: 2019/07/12 09:11
 */

public class KaptchaMathTextCreator extends DefaultTextCreator {

    StringBuilder result = new StringBuilder();


    @Override
    public String getText() {
        Random random = new Random(System.currentTimeMillis());
        int x = random.nextInt(51);
        int y = random.nextInt(51);
        int operationalRules = random.nextInt(4);

        switch (operationalRules) {
            case 0 : add(x, y); break;
            case 1 : subtract(x, y); break;
            case 2 : multiply(x, y); break;
            case 3 : divide(x, y); break;
        }
        return result.toString();
    }
    
    private void add(int x, int y) {
        result.append(x);
        result.append(" + ");
        result.append(y);
        result.append(" = ?@");
        result.append(x + y);
    }

    private void subtract(int x, int y) {
        int max = Math.max(x, y);
        int min = Math.min(x, y);
        result.append(max);
        result.append(" - ");
        result.append(min);
        result.append(" = ?@");
        result.append(max - min);
    }

    private void multiply(int x, int y) {
        int value = x * y;
        result.append(x);
        result.append(value > 100 ? " + " : " * ");
        result.append(y);
        result.append(" = ?@");
        result.append(value > 100 ? x + y : x * y);
    }

    private void divide(int x, int y) {
        int max = Math.max(x, y);
        int min = Math.min(x, y);
        if (min == 0) {
            multiply(max, min);
        } else if(max % min == 0) {
            result.append(max);
            result.append(" / ");
            result.append(min);
            result.append(" = ?@");
            result.append(max / min);
        } else {
            result.append(max);
            result.append(" % ");
            result.append(min);
            result.append(" = ?@");
            result.append(max % min);
        }
    }

}

生成运算验证码

**
     * 获取运算验证码
     *
     * @param imageVerificationDto 用户信息
     * @return 运算验证吗
     * @throws ServiceException 查询运算验证码异常
     */
    private ImageVerificationVo selectOperationVerificationCode(ImageVerificationDto imageVerificationDto) throws ServiceException {

        byte[] bytes = null;
        String text = "";
        BufferedImage bufferedImage = null;
        ImageVerificationVo imageVerificationVo = null;

        try {

            imageVerificationVo = new ImageVerificationVo();
            imageVerificationVo.setType(imageVerificationDto.getType());
            //  生成运算验证码文本
            text = captchaProducerMath.createText();
            String value = text.substring(0, text.lastIndexOf("@"));
            //  生成运算验证码图片
            bufferedImage = captchaProducerMath.createImage(value);
            //  验证码存入redis
            getRequest().getSession().setAttribute("imageVerificationVo", imageVerificationVo);
            //  在分布式应用中,可将session改为redis存储
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ImageIO.write(bufferedImage, "png", byteArrayOutputStream);
            bytes = byteArrayOutputStream.toByteArray();
            //  图片base64加密
            imageVerificationVo.setOperationImage(Base64Utils.encodeToString(bytes));
            imageVerificationVo.setType(imageVerificationDto.getType());
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            throw new ServiceException(ServiceExceptionCode.SELECT_VERIFICATION_CODE_ERROR);
        }
        return imageVerificationVo;

    }
image.png

后话

这里的验证码样式、字体等都有点丑,原谅作者的艺术水平太高,大大们可自行调整。kapcha验证码与手动编写的验证码可以明显看出kaptcha验证码的优势,推荐大家使用kaptcha来做验证码。
有了这些验证码为什么会有新的验证码?值得大家思考的一个问题。

项目地址:https://gitee.com/gester/captcha.git

如果这篇文章有帮到您,可以给个star,谢谢大大。

同时,推荐一波当下流行的滑动验证码,验证码实现和京东的滑动验证码没有多大区别。下面给个传送门,给个预览图哈。
滑动验证码原理与实现文章地址:https://www.jianshu.com/p/6ff29737209f

1.png

相关文章

网友评论

      本文标题:Java实现字符验证码、运算验证码

      本文链接:https://www.haomeiwen.com/subject/dwtbsctx.html