java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java文字验证码及人机验证

Java实现简单文字验证码以及人机验证

作者:The_cute_cat

人机验证技术的发展也在不断进化,从最初的简单验证码到现在的人工智能驱动的高级验证系统,下面这篇文章主要介绍了Java实现简单文字验证码以及人机验证的相关资料,需要的朋友可以参考下

一、代码引用

首先,如果你想直接用,可以直接用下面这个类。

可以调用CaptchaGenerator类中的captchaCreateImage方法,其方法参数列表为(int width, int height, int captchaLength, String[] returnCaptcha, int degree),方法返回验证码图像

width -----------------------文字验证码图片的宽度

height-----------------------文字验证码图片的高度

captchaLength-----------文字验证码的长度

returnCaptcha-----------返回的文字验证码

degree---------------------干扰的程度(1-5,不在范围默认为5)

程度展示

 代码引用处

import java.awt.*; // 导入 AWT 图形库
import java.awt.geom.AffineTransform; // 导入用于执行几何变换的类
import java.awt.image.BufferedImage; // 导入用于处理图像的类
import java.util.Random; // 导入随机数生成器类
import java.util.concurrent.ThreadLocalRandom;

public class CaptchaGenerator {
    // 创建随机数生成器
    private final Random random = ThreadLocalRandom.current();

    // 创建验证码的方法
    private String createCaptcha(int length) {
        // 定义可选字符池(不包含容易混淆的字符0和O等)
        String charPool = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789";
        StringBuilder result = new StringBuilder(); // 用于构建验证码字符串

        // 随机选择字符生成验证码
        for (int i = 0; i < length; i++) {
            result.append(charPool.charAt(random.nextInt(charPool.length()))); // 从字符池中随机选择字符
        }
        return result.toString(); // 返回生成的验证码字符串
    }

    // 创建颜色的方法,生成指定范围内随机颜色
    private Color createColor(int min, int max) {
        int r = min + random.nextInt(max - min+1); // 随机生成红色分量
        int g = min + random.nextInt(max - min+1); // 随机生成绿色分量
        int b = min + random.nextInt(max - min+1); // 随机生成蓝色分量
        return new Color(r, g, b); // 创建并返回颜色对象
    }

    // 添加干扰元素的方法
    private void addInterference(Graphics2D g, int degree, int width, int height) {
        // 确保干扰元素的数量在0到5之间
        degree = (degree <= 0 || degree > 5) ? 5 : degree;

        // 根据度数生成干扰元素
        for (int i = 0; i < degree * 20; i++) {
            int x = random.nextInt(width); // 随机生成x坐标
            int y = random.nextInt(height); // 随机生成y坐标

            // 随机选择干扰元素的颜色
            Color color = (random.nextBoolean()) ? createColor(0, 255) : ((random.nextBoolean()) ? Color.WHITE : Color.BLACK);
            g.setColor(color); // 设置画笔颜色

            // 随机选择干扰元素的类型并画出
            switch (random.nextInt(3)) {
                case 0 -> g.fillOval(x, y, random.nextInt(3) + 1, random.nextInt(3) + 1); // 画圆点
                case 1 -> {
                    int change = random.nextInt(3); // 随机变化值
                    // 画出线条构成的随机图形
                    g.drawLine(x, y, x + change, y + change);
                    g.drawLine(x + change, y + change, x + 2 * change, y);
                    g.drawLine(x, y, x + change, y - change);
                    g.drawLine(x + change, y - change, x + 2 * change, y);
                }
                case 2 -> g.drawLine(x, y, x + random.nextInt(5) + 1, y + random.nextInt(5) + 1); // 画线
            }
        }

        // 生成更多随机干扰线
        for (int i = 0; i < 5 * degree; i++) {
            Color color = (random.nextBoolean()) ? createColor(0, 255) : ((random.nextBoolean()) ? Color.WHITE : Color.BLACK);
            g.setColor(color);
            // 随机生成干扰线的起止点
            g.drawLine(random.nextInt(width), random.nextInt(height), random.nextInt(width), random.nextInt(height));
        }
    }

    // 创建验证码图像的方法
    private BufferedImage createImage(int width, int height, int captchaLength, String[] returnCaptcha, int degree) {

        // 创建新图像
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = image.createGraphics(); // 获取图形上下文
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // 启用抗锯齿

        // 创建背景色
        Color backgroundColor = createColor(0, 255);
        g.setColor(backgroundColor);
        g.fillRect(0, 0, width, height); // 填充背景

        // 生成验证码
        String captcha = createCaptcha(captchaLength);
        returnCaptcha[0] = captcha; // 将生成的验证码存入数组

        // 设置字体大小和干扰详细参数
        int fontSize = (int) (height * 0.5);
        AffineTransform at = new AffineTransform(); // 创建变换对象
        at.shear(random.nextDouble() * 0.4 - 0.2, random.nextDouble() * 0.4 - 0.2); // 随机倾斜变换

        Font font = new Font(Font.SANS_SERIF, Font.BOLD, fontSize); // 创建字体对象
        font = font.deriveFont(at); // 生成倾斜字体

        addInterference(g, degree, width, height); // 添加干扰元素

        Color fontColor, prevFontColor = null; // 字体颜色和前一个字体颜色
        int fontX, fontY, fontWidth, changeX; // 不同的坐标和宽度
        FontMetrics fontMetrics = g.getFontMetrics(); // 字体度量
        fontWidth = fontMetrics.stringWidth(captcha) + (captchaLength - 1) * (int) (width * 0.05); // 计算验证码的宽度
        fontX = (width - fontWidth) / 2; // 计算x坐标以居中对齐
        fontY = (height - (fontMetrics.getAscent() + fontMetrics.getDescent())) / 2 + fontMetrics.getAscent(); // 计算y坐标

        double tempX = fontX; // 保存当前x坐标

        // 逐个绘制验证码字符
        for (int i = 0; i < captchaLength; i++) {
            // 根据背景色的亮度生成对比度较强的字体颜色
            fontColor = (backgroundColor.getRed() > 180) ? createColor(0, 160) : createColor(200, 255);
            int maxAttempts = 10,count=0;//设置最大循环数,避免死循环
            // 确保字体颜色与前一个字体颜色不相近
            while (prevFontColor != null&&count++<maxAttempts) {
                double brightness = (backgroundColor.getRed() * 299 + backgroundColor.getBlue() * 114 + backgroundColor.getGreen() * 587) / 1000.0;
                fontColor = (brightness > 128) ? createColor(0, 128) : createColor(128, 255); // 确定字体颜色
                int dr = fontColor.getBlue() - prevFontColor.getBlue();
                int dg = fontColor.getGreen() - prevFontColor.getGreen();
                int db = fontColor.getRed() - prevFontColor.getRed();
                prevFontColor = fontColor; // 更新前一个颜色
                // 如果颜色差异大于亮度则退出循环
                if (Math.sqrt(dr * dr + dg * dg + db * db) > brightness) break;
            }

            prevFontColor = fontColor; // 更新前一个颜色
            g.setFont(font); // 设置当前字体
            g.setColor(fontColor); // 设置当前字体颜色

            // 随机旋转角度
            int RotationAngle = random.nextInt(60) - 30;
            g.rotate(Math.toRadians(RotationAngle), tempX, fontY); // 绕中心点旋转
            // 绘制字符
            g.drawString(String.valueOf(captcha.charAt(i)), (int) tempX, fontY);
            g.rotate(-Math.toRadians(RotationAngle), tempX, fontY); // 逆旋转恢复状态

            changeX = fontMetrics.stringWidth(String.valueOf(captcha.charAt(i))); // 获取当前字符宽度
            tempX += (changeX + (int) (width * 0.05)); // 更新临时x坐标,为下一个字符准备空间
        }
        g.dispose(); // 释放图形上下文资源
        if(captchaLength <= 0|| returnCaptcha[0].isEmpty()) {
            throw new IllegalArgumentException("returnCaptcha array must be non-null and have at least one element");
        }
        return image; // 返回生成的图像
    }
    public BufferedImage captchaCreateImage(int width, int height, int captchaLength, String[] returnCaptcha, int degree){
        return createImage(width, height, captchaLength, returnCaptcha, degree);
    }
}

二、代码实现

1.生成验证码

由于0和O等字符容易混淆,因此需要去除这些字符。

private String createCaptcha(int length){
        String charPool = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789";
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < length; i++) {
            result.append(charPool.charAt(random.nextInt(charPool.length())));
        }
        return result.toString();
    }

2.生成图片前的准备

(1)创建一个指定宽度、高度和类型的BufferedImage对象

BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

(2)获取Graphics2D对象,用于绘制图像,并启用抗锯齿

Graphics2D g = image.createGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

(3)填充背景颜色

Color backgroundColor = createColor(0, 255); // 随机生成背景颜色
g.setColor(backgroundColor); // 设置背景颜色
g.fillRect(0, 0, width, height); // 填充背景色

(4)获取验证码

String captcha = createCaptcha(captchaLength);
returnCaptcha[0]=captcha;

3.图片添加干扰

总体预览

private void addInterference(Graphics2D g,int degree,int width,int height){
        degree=(degree <= 0||degree > 5)?5:degree;
        for (int i = 0; i < degree*20; i++) {
            int x=random.nextInt(width);
            int y=random.nextInt(height);
            Color color=(random.nextBoolean())?createColor(0,255):((random.nextBoolean())?Color.WHITE:Color.BLACK);
            g.setColor(color);
            switch (random.nextInt(3)){
                case 0-> g.fillOval(x,y, random.nextInt(3)+1,random.nextInt(3)+1);
                case 1-> {
                    int change=random.nextInt(3);
                    g.drawLine(x,y,x+change,y+change);
                    g.drawLine(x+change,y+change,x+2*change,y);
                    g.drawLine(x,y,x+change,y-change);
                    g.drawLine(x+change,y-change,x+2*change,y);
                }
                case 2->g.drawLine(x,y,x+random.nextInt(5)+1,y+random.nextInt(5)+1);
            }
        }
        for(int i=0;i<5*degree;i++){
            Color color=(random.nextBoolean())?createColor(0,255):((random.nextBoolean())?Color.WHITE:Color.BLACK);
            g.setColor(color);
            g.drawLine(random.nextInt(width),random.nextInt(height),random.nextInt(width),random.nextInt(height));
        }
    }

(1)确保程度合法

degree=(degree <= 0||degree > 5)?5:degree;

(2)生成噪点

a.随机坐标

int x=random.nextInt(width);
int y=random.nextInt(height);

b.颜色设置

要么彩色,要么黑白

Color color=(random.nextBoolean())?createColor(0,255):((random.nextBoolean())?Color.WHITE:Color.BLACK);
g.setColor(color);

 c.噪点样式选择

总体预览

switch (random.nextInt(3)){
                case 0-> g.fillOval(x,y, random.nextInt(3)+1,random.nextInt(3)+1);
                case 1-> {
                    int change=random.nextInt(3);
                    g.drawLine(x,y,x+change,y+change);
                    g.drawLine(x+change,y+change,x+2*change,y);
                    g.drawLine(x,y,x+change,y-change);
                    g.drawLine(x+change,y-change,x+2*change,y);
                }
                case 2->g.drawLine(x,y,x+random.nextInt(5)+1,y+random.nextInt(5)+1);
            }

1.椭圆形

case 0-> g.fillOval(x,y, random.nextInt(3)+1,random.nextInt(3)+1);

 2.菱形

case 1-> {
                    int change=random.nextInt(3);
                    g.drawLine(x,y,x+change,y+change);
                    g.drawLine(x+change,y+change,x+2*change,y);
                    g.drawLine(x,y,x+change,y-change);
                    g.drawLine(x+change,y-change,x+2*change,y);
                }

3.随机短线段

case 2->g.drawLine(x,y,x+random.nextInt(5)+1,y+random.nextInt(5)+1);

4.验证码内容处理

(1)处理字体

总体预览

int fontSize=(int)(height*0.5);
AffineTransform at = new AffineTransform();
at.shear(random.nextDouble()*0.4-0.2,random.nextDouble()*0.4-0.2);
Font font=new Font("微软雅黑", Font.BOLD,fontSize);
font=font.deriveFont(at);
g.setFont(font);

a.设置字体大小

int fontSize=(int)(height*0.5);

b.创建AffineTransform对象,用于几何变换,将字体扭曲,程度为[-0.2,0.2]。

AffineTransform at = new AffineTransform();
at.shear(random.nextDouble()*0.4-0.2,random.nextDouble()*0.4-0.2);

c.设置字体样式,并应用扭曲

Font font=new Font("微软雅黑", Font.BOLD,fontSize);
font=font.deriveFont(at);

(2)字符居中处理

总体预览

int fontX,fontY,fontWidth,changeX;
FontMetrics fontMetrics = g.getFontMetrics();
fontWidth=fontMetrics.stringWidth(captcha)+(captchaLength-1)*(int)(width*0.05);
fontX=(width-fontWidth)/2;
fontY=(height - (fontMetrics.getAscent() + fontMetrics.getDescent())) / 2 + fontMetrics.getAscent();

a.获取横坐标

fontMetrics.stringWidth(captcha)是所有的字符的总宽度。

(captchaLength-1)*(int)(width*0.05)是字符之间的总间隔大小,其中(captchaLength-1)为间隔数,(width*0.05)为间隔大小。

fontWidth=fontMetrics.stringWidth(captcha)+(captchaLength-1)*(int)(width*0.05);
fontX=(width-fontWidth)/2;

b.获取纵坐标

FontMetrics fontMetrics = g.getFontMetrics();
fontY=(height - (fontMetrics.getAscent() + fontMetrics.getDescent())) / 2 + fontMetrics.getAscent();

fontMetrics.getAscent():基线到字体最高点的距离

fontMetrics.getDescent():基线到字体最低点的距离

怎么理解?

FontMetrics类提供了关键参数:

一、将文字想象成一个“盒子”

假设每个字符是一个矩形盒子,其高度由三部分组成:

二、Java绘图的“基线对齐”

当调用 g.drawString(text, x, y) 时:

画布顶部(y=50)
|        |
|        |
|      —————————— ← Ascent(y=35)
|        
|      —————————— ← 基线(y=30)
|        
|      —————————— ← 画布中心(y=25)
|        ▲
|        | 
|        ▼
|      —————————— ← Descent(y=15)
|        |
|        |
画布底部(y=0)

(3)字符颜色处理

总体预览

fontColor=(backgroundColor.getRed()>180)?createColor(0,160):createColor(200,255);
            int maxAttempts = 10,count=0;
            while (prevFontColor!=null&&count++<maxAttempts) {
                double brightness=(backgroundColor.getRed()*299+backgroundColor.getBlue()*114+backgroundColor.getGreen()*587)/1000.0;
                fontColor=(brightness>128)?createColor(0,128):createColor(128,255);
                int dr=fontColor.getBlue()-prevFontColor.getBlue();
                int dg=fontColor.getGreen()-prevFontColor.getGreen();
                int db=fontColor.getRed()-prevFontColor.getRed();
                prevFontColor=fontColor;
                if(Math.sqrt(dr*dr+dg*dg+db*db)>brightness)break;
            }
            prevFontColor=fontColor;
            g.setColor(fontColor);

a.随机颜色

避免与背景颜色相近。

fontColor=(backgroundColor.getRed()>180)?createColor(0,160):createColor(200,255);

b.处理相邻颜色相近与对比度不明显问题

总体预览

while (prevFontColor!=null&&count++<maxAttempts) {
                double brightness=(backgroundColor.getRed()*299+backgroundColor.getBlue()*114+backgroundColor.getGreen()*587)/1000.0;
                fontColor=(brightness>128)?createColor(0,128):createColor(128,255);
                int dr=fontColor.getBlue()-prevFontColor.getBlue();
                int dg=fontColor.getGreen()-prevFontColor.getGreen();
                int db=fontColor.getRed()-prevFontColor.getRed();
                prevFontColor=fontColor;
                if(Math.sqrt(dr*dr+dg*dg+db*db)>brightness)break;
            }
            prevFontColor=fontColor;

1.颜色差异检测机制(欧氏距离)

颜色距离公式

使用欧氏距离公式计算颜色差异:

\Delta = \sqrt{(R_1-R_2)^2 + (G_1-G_2)^2 + (B_1-B_2)^2}

该公式能综合衡量RGB三个通道的差异,更符合人眼对颜色差异的感知

2.背景对比度优化(YIQ模型)

(4)字符旋转处理

总体预览

int RotationAngle=random.nextInt(60)-30;
g.rotate(Math.toRadians(RotationAngle),tempX,fontY);
g.drawString(String.valueOf(captcha.charAt(i)),(int)tempX,fontY);
g.rotate(-Math.toRadians(RotationAngle),tempX,fontY);
changeX=fontMetrics.stringWidth(String.valueOf(captcha.charAt(i)));
tempX+=(changeX+(int)(width*0.05));

a.随机角度[-30°,30°]

int RotationAngle=random.nextInt(60)-30;

b.旋转字符

1.旋转画布

g.rotate(Math.toRadians(RotationAngle),tempX,fontY);

通过这行代码,当前画布将围绕指定的 (tempX, fontY) 点旋转,旋转角度为 RotationAngle

2.绘制字符 

g.drawString(String.valueOf(captcha.charAt(i)),(int)tempX,fontY);

这行代码则在旋转后的画布上绘制当前字符。

3.恢复画布的旋转状态

g.rotate(-Math.toRadians(RotationAngle),tempX,fontY);

这个步骤确保在绘制完当前字符后,画布的状态返回到原始位置,方便后续绘制其他字符时不会受到影响。

c.绘制字符位置处理

changeX=fontMetrics.stringWidth(String.valueOf(captcha.charAt(i)));
tempX+=(changeX+(int)(width*0.05));

1. 计算字符宽度

2.更新绘制位置

总结 

到此这篇关于Java实现简单文字验证码以及人机验证的文章就介绍到这了,更多相关Java文字验证码及人机验证内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文