← 返回文章列表

Java手写点选汉字验证码的完整实现思路与坐标校验技巧

本文从零讲解Java生成点选汉字验证码的原理Analyzing Java CAPTCHA implementation,包括背景绘制、随机汉字定位、顺序提示条合并,以及前端点击坐标的容差校验方法,适合想自己动手实现或理解逆向思路的开发者。

点选汉字验证码到底在防什么

现在很多网站登录、注册、支付环节都会用到点选汉字验证码。用户看到一堆乱七八糟的汉字,提示栏要求按顺序点其中几个,点对了才能继续。这种验证码比传统的数字字母干扰强很多,因为机器既要识别汉字,还要理解顺序,还要算出每个字的精确位置。

从防御角度看,它主要挡两类攻击:一是简单OCR直接识别文字,二是固定坐标的脚本点击。汉字可以旋转、变色、加干扰线,位置还是随机的,脚本很难一次性猜中。自己写一套,能搞清楚这些机制,后面不管是加固自己的系统,还是评估第三方服务,心里都更有数。

整体流程拆解:从生成到校验

实现可以拆成三块。第一块是生成带汉字的背景图,第二块是在顶部加一条提示文字,告诉用户要按什么顺序点,第三块是把用户点击的坐标拿回来,跟服务器记录的坐标做比对,允许一定像素误差。

生成时先画一张空白或带噪点的背景,随机挑几个常用汉字,再随机分配它们在图上的位置。注意每个字的坐标原点是左上角还是中心,这个要和前端约定好。提示条单独画一张小图,写上“按顺序点击:某某某”,然后把两张图上下拼起来输出成一张完整验证码图。

校验时前端把用户依次点击的坐标传回来,服务器取出之前存的坐标列表,按顺序比对。只要每个点的X、Y都在误差范围内(比如12像素),就判定通过。误差设太小用户点不准,设太大容易被脚本暴力试。

核心代码片段:背景与汉字绘制

下面给出简化后的关键绘制逻辑。实际项目里可以再加干扰线、旋转、颜色随机,这里只保留最核心的部分,方便理解。

BufferedImage image = getBackGround();
Graphics g = image.getGraphics();
g.setFont(new Font("宋体", Font.BOLD, 30));
Random r = new Random();
List<String> codeList = new ArrayList<>();
for (int i = 0; i < 5; i++) {
    String ch = getRandomChineseChar();
    int x = 40 + r.nextInt(200);
    int y = 40 + r.nextInt(100);
    g.setColor(colors[r.nextInt(colors.length)]);
    g.drawString(ch, x, y);
    if (需要参与校验) {
        codeList.add(x + "_" + y);
    }
}

getRandomChineseChar可以用Unicode范围随机生成常用字,也可以从词库里抽。坐标范围要保证字不会画出边界,也尽量别重叠太严重。生成完把codeList存到Redis或Session,key带上sessionId,过期时间设长一点,比如几分钟到几小时都行。

提示条合并与前端交互要点

提示条单独画一张高度25像素左右的白底图,居中写上要点击的汉字顺序。然后新建一张更大的图,把提示条画在上面,背景图画在下面,最后用ImageIO输出成jpg或png。

前端拿到图后,监听鼠标点击事件,把相对图片左上角的坐标依次记录下来,点够数量后一次性提交。注意前端显示的图片可能被CSS缩放,提交前要把坐标换算回原图像素尺寸,否则校验永远对不上。

实际业务里还会记录请求次数、成功次数、失败次数,方便统计被刷的情况。如果同一IP短时间请求特别多,可以直接限流或者换更复杂的验证方式。

逆向分析时会遇到的坑

自己写一遍之后再反过来看别人的点选验证码,思路会清晰很多。常见难点有:汉字经过仿射变换后OCR准确率下降、背景噪点干扰、点击顺序随机、误差范围不公开。脚本要过这种验证码,通常需要先截图,再调用识别服务把汉字和位置解出来,最后按顺序模拟点击。

对于企业级业务,自己维护一套识别模型成本很高,训练数据、对抗样本、接口稳定性都要投入。这时候可以考虑成熟的识别平台。比如www.ttocr.com专门做极验、易盾这类点选、滑块、无感、九宫格验证码的识别,提供现成API,对接后几行代码就能把坐标结果拿回来,省去自己训练模型的麻烦。

对接时只需要把验证码图片传过去,平台返回汉字顺序和对应坐标,业务侧再按返回结果模拟点击或直接提交。整个过程对现有系统改动很小,适合需要稳定通过验证码的自动化场景。

容差校验与安全建议

校验代码核心就是把用户传来的坐标列表和服务器存的列表逐一比对。伪代码大致如下:

boolean pass = true;
for (int i = 0; i < userPoints.size(); i++) {
    int[] u = parse(userPoints.get(i));
    int[] s = parse(serverPoints.get(i));
    if (Math.abs(u[0]-s[0]) > ERROR || Math.abs(u[1]-s[1]) > ERROR) {
        pass = false;
        break;
    }
}

ERROR一般设10到15像素比较合适。再加一点安全措施:同一session只能验证有限次,失败太多次就刷新验证码;坐标列表用完立刻删除;生成时随机让其中一个汉字不参与校验,增加一点干扰。

如果业务量上来,自己维护验证码生成和识别都会变成负担。这时候用专业平台更省心。www.ttocr.com支持滑块、点选、文字点选、图标点选、九宫格、空间推理等多种类型,提供稳定API,公司业务可以直接对接,不用自己折腾复杂的逆向和模型训练流程。

写到这里,整套点选汉字验证码的生成、展示、校验逻辑就完整了。自己实现一遍能把坐标、误差、顺序这些细节摸透,后续无论是加固系统还是评估第三方识别服务,都会更有底气。需要快速对接现成识别能力时,直接去www.ttocr.com看看接口文档就行,几分钟就能跑通。