1.背景
目前收到反馈,存在一类用户,在利用会员权益大量进行二次销售;而且还是自动进行操作的. 那么意味着他们有一个自动平台在对我们的商品进行二次销售.
这是就该我们的主角登场了. 验证码模块可以有效防止机器人刷接口
2.开源验证码框架
通过在网上查找资料, 发现了几个验证码开源框架
Kaptcha
: 是Google发布的一款Java验证码框架。该框架使用简单,支持生成图片和音频验证码,并且有多种配置选项供开发者进行定制。JCaptcha
:是由SourceForge托管的一个开源Java验证码框架。该框架提供了基于DHTML的动态鱼眼效果来防止恶意程序自动猜解验证码,并且支持多种前端引擎,JCaptcha 即为 Java 版本的 CAPTCHA 项目,其是一个开源项目,支持生成图形和声音版的验证码,在生成声音版的验证码时,需要使用到 FreeTTS。SimpleCaptcha
:是一个轻量级的Java验证码框架,使用简单方便,支持各种文字、图形、干扰线等特效的复杂验证码,Kaptcha
也是基于SimpleCaptcha的验证码开源项目。Google Authenticator
:是Google旗下的两步验证应用,同时也提供了基于Java的身份验证库,可用于保护Web应用中帐户的登录过程。咱不是很清楚,就不过多描述.tianai-captcha
: 由国内开发者开发出的一套验证码框架. 支持 滑块验证, 旋转验证,文字点选验证等行为验证方式.开箱即,比较方便. 本文就基于这套开源代码展开说说
3.tianai-captcha
访问Gitee地址: tianai-captcha
以下部分文字是直接引用 tianai-captcha 中的内容.
3.1整体架构设计
tianai-captcha 验证码整体分为 生成器(ImageCaptchaGenerator)、校验器(ImageCaptchaValidator)、资源管理器(ImageCaptchaResourceManager) 其中生成器、校验器、资源管理器等都是基于接口模式实现 可插拔的,可以替换为自定义实现,灵活度高
- 生成器(ImageCaptchaGenerator)
- 主要负责生成行为验证码所需的图片
- 校验器(ImageCaptchaValidator)
- 主要负责校验用户滑动的行为轨迹是否合规
- 资源管理器(ImageCaptchaResourceManager)
- 主要负责读取验证码背景图片和模板图片等
- 资源管理器细分为 资源存储(ResourceStore)、资源提供者(ResourceProvider)
- 资源存储(ResourceStore) 负责存储背景图和模板图
- 资源提供者(ResourceProvider) 负责将资源存储器中对应的资源转换为文件流
- 一般资源存储器中存储的是图片的url地址或者id之类, 资源提供者 就是负责将url或者别的id转换为真正的图片文件
- 图片转换器 (ImageTransform)
- 主要负责将图片文件流转换成字符串类型,可以是base64格式/url 或其它加密格式,默认实现是bas64格式;
下面我们来依次解析
3.2 生成器 ImageCaptchaGenerator
ImageCaptchaGenerator 作为一个图片生成器接口,其中包含了 8 个方法
/**
* 初始化
*
* @param initDefaultResource 是否初始化默认资源
* @return ImageCaptchaGenerator
*/
ImageCaptchaGenerator init(boolean initDefaultResource);
/**
* 生成验证码图片
*
* @param type 类型 {@link CaptchaTypeConstant}
* @return SliderCaptchaInfo
*/
ImageCaptchaInfo generateCaptchaImage(String type);
/**
* 生成滑块验证码
*
* @param type type {@link CaptchaTypeConstant}
* @param targetFormatName jpeg或者webp格式
* @param matrixFormatName png或者webp格式
* @return SliderCaptchaInfo
*/
ImageCaptchaInfo generateCaptchaImage(String type, String targetFormatName, String matrixFormatName);
/**
* 生成滑块验证码
*
* @param param 生成参数
* @return SliderCaptchaInfo
*/
ImageCaptchaInfo generateCaptchaImage(GenerateParam param);
/**
* 获取滑块验证码资源管理器
*
* @return SliderCaptchaResourceManager
*/
ImageCaptchaResourceManager getImageResourceManager();
/**
* 设置滑块验证码资源管理器
*
* @param imageCaptchaResourceManager
*/
void setImageResourceManager(ImageCaptchaResourceManager imageCaptchaResourceManager);
/**
* 获取图片转换器
*
* @return ImageTransform
*/
ImageTransform getImageTransform();
/**
* 设置图片转换器
*
* @param imageTransform imageTransform
* @return ImageTransform
*/
void setImageTransform(ImageTransform imageTransform);
从上面的接口挑了几个,分析下源码, 也让自己了解的更多.
一种比较好的学习方法就是把自己所学的东西 教授给别人,由此可以发现自己的不足以及盲区.
3.2.1生成器初始化方法init
/**
* 初始化
* @param initDefaultResource 是否初始化默认资源
* @return
*/
@Override
public ImageCaptchaGenerator init(boolean initDefaultResource) {
// 这里进行初始化,只需要初始化一次
if (init) {
return this;
}
init = true;
try {
log.info("图片验证码[{}]初始化...", this.getClass().getSimpleName());
// 设置默认图片转换器
if (getImageTransform() == null) {
setImageTransform(new Base64ImageTransform());
}
// 具体初始化,这是一个抽象方法,由子类实现具体逻辑,比如滑块/旋转/字符验证码等子类
// 待会可以看一下滑块子类的具体实现 ①
doInit(initDefaultResource);
} catch (Exception e) {
init = false;
log.error("[{}]初始化失败,ex", this.getClass().getSimpleName(), e);
throw e;
}
return this;
}
3.2.1验证码生成方法generateCaptchaImage
// 这是一个重载的方法, 可以使用默认参数,也可以进行自定义传参
@Override
public ImageCaptchaInfo generateCaptchaImage(String type) {
// 这里调用了重载方法,传默认值
return generateCaptchaImage(type, defaultBgImageType, defaultSliderImageType);
}
@SneakyThrows
@Override
public ImageCaptchaInfo generateCaptchaImage(String type, String backgroundFormatName, String templateFormatName) {
// 进行验证码的生成, 随着代码的深入查看, 里面是一个 抽象方法,由子类生成验证码 随后我们会看到②
return generateCaptchaImage(GenerateParam.builder()
.type(type)
.backgroundFormatName(backgroundFormatName)
.templateFormatName(templateFormatName)
.obfuscate(false)
.build());
}
/**
* 生成验证码方法
*
* @param param param
* @return ImageCaptchaInfo
*/
protected abstract ImageCaptchaInfo doGenerateCaptchaImage(GenerateParam param);
4.接下来看下具体的验证码类的实现
子类有以下几个:
我们在这重点看一下滑块验证码的源代码
4.1 滑块验证码生成器StandardSliderImageCaptchaGenerator
滑块验证码也是一种经常使用的方式.需要鼠标拖动到指定位置,来判断是否通过.
比如:
doInit(boolean initDefaultResource)方法
这个方法是初始化资源的地方.初始化一些 系统的图片模板,系统的资源文件等
@Override
protected void doInit(boolean initDefaultResource) {
if (initDefaultResource) {
// 初始化默认资源
initDefaultResource();
}
}
/**
* 初始化默认资源
*/
public void initDefaultResource() {
// 获取资源存储的实例
ResourceStore resourceStore = imageCaptchaResourceManager.getResourceStore();
// 添加一些系统的资源文件
resourceStore.addResource(CaptchaTypeConstant.SLIDER, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/1.jpg")));
// 添加一些系统的 模板文件
ResourceMap template1 = new ResourceMap(DEFAULT_TAG, 4);
template1.put(SliderCaptchaConstant.TEMPLATE_ACTIVE_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/1/active.png")));
template1.put(SliderCaptchaConstant.TEMPLATE_FIXED_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/1/fixed.png")));
template1.put(SliderCaptchaConstant.TEMPLATE_MATRIX_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/1/matrix.png")));
resourceStore.addTemplate(CaptchaTypeConstant.SLIDER, template1);
ResourceMap template2 = new ResourceMap(DEFAULT_TAG, 4);
template2.put(SliderCaptchaConstant.TEMPLATE_ACTIVE_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/2/active.png")));
template2.put(SliderCaptchaConstant.TEMPLATE_FIXED_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/2/fixed.png")));
template2.put(SliderCaptchaConstant.TEMPLATE_MATRIX_IMAGE_NAME, new Resource(ClassPathResourceProvider.NAME, DEFAULT_SLIDER_IMAGE_TEMPLATE_PATH.concat("/2/matrix.png")));
resourceStore.addTemplate(CaptchaTypeConstant.SLIDER, template2);
}
doGenerateCaptchaImage生成验证码方法
这就是验证码的重点方法之一.此方法里面有详细生成验证码的过程
@SneakyThrows
@Override
public ImageCaptchaInfo doGenerateCaptchaImage(GenerateParam param) {
Boolean obfuscate = param.getObfuscate();
ResourceMap templateImages = requiredRandomGetTemplate(param.getType(), param.getTemplateImageTag());
Collection<InputStream> inputStreams = new LinkedList<>();
try {
// 获取背景图片资源
Resource resourceImage = requiredRandomGetResource(param.getType(), param.getBackgroundImageTag());
InputStream resourceInputStream = imageCaptchaResourceManager.getResourceInputStream(resourceImage);
inputStreams.add(resourceInputStream);
BufferedImage cutBackground = CaptchaImageUtils.wrapFile2BufferedImage(resourceInputStream);
// 拷贝一份图片
BufferedImage targetBackground = CaptchaImageUtils.copyImage(cutBackground, cutBackground.getType());
// 获取固定、活动、滑块矩阵等模板图片资源,并将其加入输入流队列中,待使用
InputStream fixedTemplateInput = getTemplateFile(templateImages, SliderCaptchaConstant.TEMPLATE_FIXED_IMAGE_NAME);
inputStreams.add(fixedTemplateInput);
BufferedImage fixedTemplate = CaptchaImageUtils.wrapFile2BufferedImage(fixedTemplateInput);
InputStream activeTemplateInput = getTemplateFile(templateImages, SliderCaptchaConstant.TEMPLATE_ACTIVE_IMAGE_NAME);
inputStreams.add(activeTemplateInput);
BufferedImage activeTemplate = CaptchaImageUtils.wrapFile2BufferedImage(activeTemplateInput);
InputStream matrixTemplateInput = getTemplateFile(templateImages, SliderCaptchaConstant.TEMPLATE_MATRIX_IMAGE_NAME);
inputStreams.add(matrixTemplateInput);
BufferedImage matrixTemplate = CaptchaImageUtils.wrapFile2BufferedImage(matrixTemplateInput);
// BufferedImage cutTemplate = warpFile2BufferedImage(getTemplateFile(templateImages, CUT_IMAGE_NAME));
// 获取随机的 X 和 Y 轴位置,在选取范围内生成一个位置点作为起点
int randomX = ThreadLocalRandom.current().nextInt(fixedTemplate.getWidth() + 5, targetBackground.getWidth() - fixedTemplate.getWidth() - 10);
int randomY = ThreadLocalRandom.current().nextInt(targetBackground.getHeight() - fixedTemplate.getHeight());
// 将固定模板绘制在目标 Image 上
CaptchaImageUtils.overlayImage(targetBackground, fixedTemplate, randomX, randomY);
if (obfuscate) {
// 加入混淆滑块
int obfuscateX = randomObfuscateX(randomX, fixedTemplate.getWidth(), targetBackground.getWidth());
CaptchaImageUtils.overlayImage(targetBackground, fixedTemplate, obfuscateX, randomY);
}
// 将裁剪后的背景图与活动模板进行合并,获取滑动块
BufferedImage cutImage = CaptchaImageUtils.cutImage(cutBackground, fixedTemplate, randomX, randomY);
CaptchaImageUtils.overlayImage(cutImage, activeTemplate, 0, 0);
// 将滑块拼接到矩阵模板上
CaptchaImageUtils.overlayImage(matrixTemplate, cutImage, 0, randomY);
return wrapSliderCaptchaInfo(randomX, randomY, targetBackground, matrixTemplate, param, templateImages, resourceImage);
} finally {
// 使用完后关闭流
for (InputStream inputStream : inputStreams) {
try {
inputStream.close();
} catch (IOException e) {
// ignore
}
}
}
}
/**
* 包装成 SliderCaptchaInfo
*
* @param randomX 随机生成的 x轴
* @param randomY 随机生成的 y轴
* @param backgroundImage 背景图片
* @param sliderImage 滑块图片
* @param param 接口传入参数
* @return SliderCaptchaInfo
*/
@SneakyThrows
public SliderImageCaptchaInfo wrapSliderCaptchaInfo(int randomX,
int randomY,
BufferedImage backgroundImage,
BufferedImage sliderImage,
GenerateParam param,
ResourceMap templateResource,
Resource resourceImage) {
ImageTransformData transform = getImageTransform().transform(param, backgroundImage, sliderImage, resourceImage, templateResource);
return SliderImageCaptchaInfo.of(randomX, randomY,
transform.getBackgroundImageUrl(),
transform.getTemplateImageUrl(),
resourceImage.getTag(),
templateResource.getTag(),
backgroundImage.getWidth(), backgroundImage.getHeight(),
sliderImage.getWidth(), sliderImage.getHeight()
);
}
这段代码主要是实现了验证码图片的生成及其相关处理流程,具体包括:
1.通过 requiredRandomGetResource 方法获取指定类型和 tag 的随机背景图片资源。
2.将获取到的背景图片流转换为 BufferedImage 对象,并通过拷贝一份作为目标 Image。
3.获取固定、活动和滑块矩阵等模板图片资源,并将其加入输入流队列中,待使用。
4.在选取范围内获取随机的 X 和 Y 轴位置,在起始点处绘制固定模板。
5.混淆滑块模块,如果需要则在随机位置再挑选一个滑块绘制上去。
6.将裁剪后的背景图与活动模板进行合并,获取滑动块,然后将滑块拼接到矩阵模板上
7.包装成 SliderCaptchaInfo
以上就是生成验证码的过程; 至于验证期待下一篇文章吧~