在很多应用场景中,如果我们遇到无法绕过的CAPTCHA验证码,依然可以通过识别验证码来解决问题。本节将选择几种主流的CAPTCHA验证码讲解验证码,讲解它们的生成与识别原理和过程,主要包括文本验证码、滑块验证码、图片选择验证码和手机验证码等类型。
在讲解验证码识别技术之前,我们先来看看主流验证码的生成原理。基本上,所有类型的传统验证码都遵循图3-4的处理流程。
图3-4 验证码的处理流程
文本验证码是一种比较原始且目前仍在广泛使用的技术。目前,用于生成文本验证码的开源代码库还是比较多的,但基本上都基于相似的生成逻辑,相关生成逻辑大致如下:
步骤01 生成随机字符串。字符串可以包含数字、字母和特殊字符。
步骤02 创建包含验证码文本的图片。根据指定的高度和宽度创建图片,并将文本渲染到图片上。
步骤03 为验证码图片添加背景和噪声。为了提高验证码的破解和识别难度,通常会在图片中添加背景和噪声。
接下来,我们以Google公司开源的验证码生成工具Kaptcha为例来演示如何生成文本验证码。
首先,在项目中添加Kaptcha依赖包。因为我们使用Maven构建工程,所以需要添加Maven依赖包:
<dependency> <groupId>com.github.penggle</groupId> <artifactId>kaptcha</artifactId> <version>2.3.2</version> </dependency>
接下来,需要注意的是,Kaptcha提供了很多可配置属性。通过配置这些属性,我们可以指定验证码的图片大小、支持的字符集以及噪声干扰程度等。表3-1列举了Kaptcha支持的一些配置属性及其作用。
表3-1 Kaptcha主要配置属性
接下来,我们来看具体的代码实现及其效果。
// 创建Kaptcha实例 DefaultKaptcha kaptcha = new DefaultKaptcha(); // 创建Kaptcha配置 Properties properties = new Properties(); // 设置验证码字符范围 properties.setProperty("kaptcha.textproducer.char.string", "0123456789"); Config config = new Config(properties); kaptcha.setConfig(config); // 生成验证码文本 String text = kaptcha.createText(); // 创建验证码图片 BufferedImage image = kaptcha.createImage(text); // 保存验证码图片 ImageIO.write(image, "png", new File(“captcha.png"));
上述代码成功运行之后,我们就会得到一幅验证码图片,具体效果如图3-5所示。
图3-5 文本验证码生成样例
近几年,以滑块验证码为代表的行为式验证码被越来越多的平台采用,不仅提高了机器破解的难度,还提升了用户体验。滑块验证码的工作原理和流程如下:
步骤01 生成验证码图片。
(1)从服务器随机选择一幅背景图片和滑块模板图片,随机生成滑块所在的 x 、 y 坐标。
(2)使用选定的背景图片和滑块模板图片生成两幅新的图片。
● 滑块图片:只包含被抠出的区域图像的图片。
● 新的背景图片:包含标记抠图区域的背景图片。
步骤02 保存校验信息并发送滑块验证码给客户端。
(1)将滑块在背景图片的坐标信息与session_id进行绑定并存储。
(2)将包含两幅图片和session_id的数据返回给客户端。
步骤03 记录用户对滑块验证码的操作行为。
(1)用户在客户端进行滑块验证时,记录用户的操作轨迹和最终滑动位置坐标。
(2)客户端将滑动位置坐标、操作轨迹等行为数据和session_id发送到服务器端进行验证。
步骤04 服务器端验证用户提交的行为数据。
(1)服务器端根据接收到的session_id从存储中检索出相应的滑块验证码校验信息,然后将用户提交的行为数据与预先存储的校验信息进行对比,并根据预定义的阈值判断验证是否通过。
(2)(可选操作)服务器端在检查用户提交的行为数据时可能会添加反作弊检查,例如滑块拖动轨迹是否合理、滑块移动速度是否合理等。
在开始编写实现代码之前,我们还需要了解有关图片RGBA颜色模型的基础知识。相较于RGB颜色模型,RGBA颜色模型多了Alpha通道,Alpha通道表示像素的透明度,其取值范围通常为0(完全透明)~255(完全不透明)。无论是在滑块验证码的生成还是识别过程中,都会使用到RGBA中的Alpha通道。
现在,正式开始编写滑块验证码生成的示例代码。本次实验的示例代码只会生成滑块验证码的背景图片和滑块图片,不会涉及滑块验证码的验证逻辑。
// 1. 从服务器随机选择一幅背景图片和一幅滑块模板图片,并且生成抠图区域的x、y坐标 BufferedImage originalImage = ImageIO.read(selectedImage); BufferedImage sliderTemplateImage = ImageIO.read(selectedSlider); Random random = new Random(); // 随机生成的x坐标 int x = random.nextInt(originalImage.getWidth() - sliderTemplateImage.getWidth()); int y = random.nextInt(originalImage.getHeight() - sliderTemplateImage.getHeight()); // 随机生成的y坐标 // 2. 使用选定的图片和抠图区域,生成两幅图像 // 2.1 滑块图片 BufferedImage sliderImage = new BufferedImage(sliderTemplateImage.getWidth(), sliderTemplateImage.getHeight(), BufferedImage.TYPE_INT_ARGB); for(int i = 0; i < sliderTemplateImage.getWidth(); i++) { for(int j = 0; j < sliderTemplateImage.getHeight(); j++) { int rgb = sliderTemplateImage.getRGB(i, j); // 如果滑块模板图片的像素点Alpha通道值大于100,就使用原始图片的像素点,否则使用滑块模板 // 图片的像素点 if((rgb & 0xFF000000) >>> 24 > 100) { sliderImage.setRGB(i, j, originalImage.getRGB(x + i, y + j)); } else { sliderImage.setRGB(i, j, rgb); } } } // 2.2 去掉滑块图片的背景图片 Graphics2D g2d = originalImage.createGraphics(); g2d.drawImage(sliderTemplateImage, x, y, sliderTemplateImage.getWidth(), sliderTemplateImage.getHeight(),null); g2d.dispose(); // 3. 保存两幅图像 ImageIO.write(originalImage, "png", new File("shadow.png")); ImageIO.write(sliderImage, "png", new File("cutout.png"));
上述代码成功执行后,就会生成滑块验证码的两幅图片,分别是背景图片和滑块图片,效果如图3-6和图3-7所示。
图3-6 滑块验证码背景图片
图3-7 滑块验证码滑块图片
点选验证码(Click-based CAPTCHA)是一种常见的验证码形式,其目的是通过用户的点击行为来验证其身份。对于自动化程序来讲,点选验证码的识别复杂性要高于文本验证码和滑块验证码。因为相对于文本验证码和滑块验证码,自动化程序通常更难理解人类在点选验证码上的点击行为。点选验证码一般分为文字点选验证码和图标点选验证码。相关验证码的展示效果如图3-8所示。
图3-8 点选验证码示例图
接下来介绍文字点选验证码的生成流程和实现思路。文本点选验证码的具体工作流程如下:
(1)从图库中随机选择一幅图片作为点选验证码的背景图片。
(2)从字库或词库中随机选择指定数量的文字。
(3)为每个文字随机生成在背景图片上的坐标位置。
(4)随机选择字体颜色和样式。
(5)在背景图片上绘制文字。
(6)记录文字的顺序和坐标位置并与session_id进行绑定,用于后续验证用户点击行为的正确性。
(7)将写入文字的背景图片发送给客户端。
(8)用户按照要求点击图片中的文字。
(9)客户端将用户的点击行为数据发送给服务器端。
(10)服务器端验证用户点击行为的正确性。
根据上述流程,用于生成文字点选验证码实现代码如下(仅包含文字点选验证码的图片部分,不包含其他处理逻辑):
int NUM_TEXTS = 4; File backgroundFile = new File("$IMAGE_PATH/backgound_image.jpeg"); BufferedImage backgroundImage = ImageIO.read(backgroundFile); String[] texts = {"爬","虫","系","统"}; Graphics2D graphics = backgroundImage.createGraphics(); int areaWidth = backgroundImage.getWidth() / 4; for (int i = 0; i < NUM_TEXTS; i++) { String text = texts[i]; int x = areaWidth * i + random.nextInt(areaWidth); int y = random.nextInt(backgroundImage.getHeight() - 20) + 10; Color color = new Color(random.nextInt(256), random.nextInt(256), random.nextInt(256)); Font font = new Font("Arial", Font.BOLD, 36 + random.nextInt(10)); graphics.setColor(color); graphics.setFont(font); graphics.drawString(text, x, y); } ImageIO.write(backgroundImage, "jpeg", new File("click_captcha.jpeg"));
上述代码成功执行之后,我们会看到对应的点选验证码图片,如图3-9所示。
图3-9 点选验证码背景图片