Java中很轻松可以实现验证码功能,在原生AWT图形化工具包中写一点简单的逻辑就能轻松完成验证码功能。
本文,同时将google的kaptcha验证码一同讲解。
项目地址:https://gitee.com/gester/captcha.git
同时,推一下Java滑动验证码的原理与实现文章。文章地址:https://blog.csdn.net/YTenderness/article/details/99969355
原创不易!如果有帮到您,请给作者小星星鼓励一下
相关依赖
com.github.penggle
kaptcha
2.3.2
####一. AWT实现验证码
字符验证码:
/**
* 生成字符验证码
* @return 字符验证码
*/
public static Map 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 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();
verificationCodeMap.put("verificationCodeImage", bufferedImage);
verificationCodeMap.put("verificationCode", verificationCode);
return verificationCodeMap;
}
public static void main(String[] args) throws IOException {
Map 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();
}
运算验证码:
static StringBuilder result = new StringBuilder(); // 运算验证码结果
/**
* 生成运算验证码
* @return 运算验证码
*/
public static Map generatorOperationVerificationCode() {
// 验证码图片边框宽度
final int WIDTH = 185;
// 验证码图片边框高度
final int HEIGHT = 50;
// 验证码字体高度
int FONT_HEIGHT = HEIGHT - 12;
// 验证码干扰线条数
int INTERFERENCE_LINE = 4;
Map 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();
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 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();
}
总的来说,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;
}
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;
}
}
自定义运算器,同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;
}
####后话
这里的验证码样式、字体等都有点丑,原谅作者的艺术水平太高,大大们可自行调整。kapcha验证码与手动编写的验证码可以明显看出kaptcha验证码的优势,推荐大家使用kaptcha来做验证码。
有了这些验证码为什么会有新的验证码?值得大家思考的一个问题。
项目地址:https://gitee.com/gester/captcha.git
如果这篇文章有帮您,可以给个star,谢谢大大。
同时,推荐一波当下流行的滑动验证码,验证码实现和京东的滑动验证码没有多大区别。下面给个传送门,给个预览图哈。
Java滑动验证码的原理与实现文章地址:https://blog.csdn.net/YTenderness/article/details/99969355