一.背景
图形验证码的生成与使用(防止机器恶意攻击)
二.代码(使用)
1.添加Maven依赖
<!--验证码 -->
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
<exclusions>
<exclusion>
<artifactId>javax.servlet-api</artifactId>
<groupId>javax.servlet</groupId>
</exclusion>
</exclusions>
</dependency>
2. 验证码配置类
package com.provider.auth.config;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.google.code.kaptcha.util.Config;
import java.util.Properties;
/**
* 验证码配置
*
*/
@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", "105,179,90");
// 验证码文本字符颜色 默认为Color.BLACK
properties.setProperty("kaptcha.textproducer.font.color", "blue");
// 验证码图片宽度 默认为200
properties.setProperty("kaptcha.image.width", "160");
// 验证码图片高度 默认为50
properties.setProperty("kaptcha.image.height", "60");
// 验证码文本字符大小 默认为40
properties.setProperty("kaptcha.textproducer.font.size", "30");
// KAPTCHA_SESSION_KEY
properties.setProperty("kaptcha.session.key", "kaptchaCode");
// 验证码文本字符间距 默认为2
properties.setProperty("kaptcha.textproducer.char.space", "3");
// 验证码文本字符长度 默认为5
properties.setProperty("kaptcha.textproducer.char.length", "5");
// 验证码文本字体样式 默认为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", "white");
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
@Bean(name = "captchaProducerMath")
public DefaultKaptcha getKaptchaBeanMath() {
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = new Properties();
// 是否有边框 默认为true 我们可以自己设置yes,no
properties.setProperty("kaptcha.border", "yes");
// 边框颜色 默认为Color.BLACK
properties.setProperty("kaptcha.border.color", "105,179,90");
// 验证码文本字符颜色 默认为Color.BLACK
properties.setProperty("kaptcha.textproducer.font.color", "blue");
// 验证码图片宽度 默认为200
properties.setProperty("kaptcha.image.width", "160");
// 验证码图片高度 默认为50
properties.setProperty("kaptcha.image.height", "60");
// 验证码文本字符大小 默认为40
properties.setProperty("kaptcha.textproducer.font.size", "35");
// KAPTCHA_SESSION_KEY
properties.setProperty("kaptcha.session.key", "kaptchaCodeMath");
// 验证码文本生成器 【com.provider.auth.config.KaptchaTextCreator 自定义的验证码创建类 后面有附上】
properties.setProperty("kaptcha.textproducer.impl", "com.provider.auth.config.KaptchaTextCreator");
// 验证码文本字符间距 默认为2
properties.setProperty("kaptcha.textproducer.char.space", "3");
// 验证码文本字符长度 默认为5
properties.setProperty("kaptcha.textproducer.char.length", "6");
// 验证码文本字体样式 默认为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", "white");
// 干扰实现类
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;
}
}
3. 验证码文本生成器 KaptchaTextCreator
package com.provider.auth.config;
import java.util.Random;
import com.google.code.kaptcha.text.impl.DefaultTextCreator;
/**
* 验证码文本生成器
*
*/
public class KaptchaTextCreator extends DefaultTextCreator {
private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(",");
@Override
public String getText() {
Integer result = 0;
Random random = new Random();
int x = random.nextInt(10);
int y = random.nextInt(10);
StringBuilder suChinese = new StringBuilder();
int randomoperands = (int) Math.round(Math.random() * 2);
if (randomoperands == 0) {
result = x * y;
suChinese.append(CNUMBERS[x]);
suChinese.append("*");
suChinese.append(CNUMBERS[y]);
} else if (randomoperands == 1) {
if (!(x == 0) && y % x == 0) {
result = y / x;
suChinese.append(CNUMBERS[y]);
suChinese.append("/");
suChinese.append(CNUMBERS[x]);
} else {
result = x + y;
suChinese.append(CNUMBERS[x]);
suChinese.append("+");
suChinese.append(CNUMBERS[y]);
}
} else if (randomoperands == 2) {
if (x >= y) {
result = x - y;
suChinese.append(CNUMBERS[x]);
suChinese.append("-");
suChinese.append(CNUMBERS[y]);
} else {
result = y - x;
suChinese.append(CNUMBERS[y]);
suChinese.append("-");
suChinese.append(CNUMBERS[x]);
}
} else {
result = x + y;
suChinese.append(CNUMBERS[x]);
suChinese.append("+");
suChinese.append(CNUMBERS[y]);
}
suChinese.append("[email protected]" + result);
return suChinese.toString();
}
}
4.接口的使用Controller 本controller是把生成的code放到redis里面并设置过期时间,每次请求前端携带UUID作为键
来标识那个验证码,还有一种方式是直接用session来储存这里不做过多的演示
package com.provider.auth.web;
import java.awt.image.BufferedImage;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.config.redis.RedisHelper;
import com.core.base.web.AjaxResult;
import com.core.base.web.BaseController;
import com.core.constant.GlobalConstant;
import com.provider.auth.config.TokenDefaultTimeConfig;
import com.utils.common.ResponseUtils;
import com.utils.verify.StringUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.google.code.kaptcha.Producer;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 图片验证码(支持算术形式)
*
*/
@RestController
@RequestMapping("/captcha")
@Api(tags = "图片验证码接口", description = "图片验证码接口")
public class CaptchaController extends BaseController {
@Resource(name = "captchaProducer")
private Producer captchaProducer;
@Resource(name = "captchaProducerMath")
private Producer captchaProducerMath;
@Autowired
private RedisHelper redisHelper;
@Autowired
private TokenDefaultTimeConfig tokenDefaultTimeConfig;
/**
* 验证码生成
*
* @param request HttpServletRequest
* @param response HttpServletResponse
* @param type captcha type
* @param identifier identifier
* @return AjaxResult
* @author fangyi
* @date 2019/5/17
*/
@ApiOperation(value = "验证码生成")
@GetMapping(value = "/captchaImage")
public AjaxResult getKaptchaImage(HttpServletRequest request,
HttpServletResponse response,
@RequestParam(value = "type") String type,
@RequestParam("identifier") String identifier) {
try {
String capStr;
String code = null;
BufferedImage bufferedImage = null;
// GlobalConstant.CAPTCHA_MATH 类型文尾有附
if (GlobalConstant.CAPTCHA_MATH.equals(type)) {
String capText = captchaProducerMath.createText();
capStr = capText.substring(0, capText.lastIndexOf("@"));
code = capText.substring(capText.lastIndexOf("@") + 1);
bufferedImage = captchaProducerMath.createImage(capStr);
} else if (GlobalConstant.CAPTCHA_CHAR.equals(type)) {
capStr = code = captchaProducer.createText();
bufferedImage = captchaProducer.createImage(capStr);
}
if (bufferedImage == null) {
return AjaxResult.error("0401011");
}
// 放入redis中,并设置失效时间(默认10分钟)
// 这里也可采用放入session中的方案
redisHelper.set(identifier, code, tokenDefaultTimeConfig.getCaptchaValidity());
// ResponseUtils工具类文尾有附
ResponseUtils.responseBufferedImage(request, response, bufferedImage);
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error("500");
}
return null;
}
/**
* 验证码验证
*
* @param code captcha code
* @param identifier identifier
* @return AjaxResult
* @author fangyi
* @date 2019/5/17
*/
@ApiOperation(value = "验证码验证")
@GetMapping(value = "/checkCaptchaImage")
public AjaxResult checkCaptchaImage(@RequestParam(value = "code") String code,
@RequestParam("identifier") String identifier) {
String redisCode = (String) redisHelper.get(identifier);
if (StringUtils.isBlank(redisCode)) {
return AjaxResult.error("0401012");
}
if (!redisCode.equals(code)) {
return AjaxResult.error("0401012");
}
return AjaxResult.success();
}
}
5.结果展示
type 有以下两种方式(上面代码的常量如下)
/*** 验证码生成数学算术类型*/ String CAPTCHA_MATH = "math";
/*** 验证码生成字符串类型*/ String CAPTCHA_CHAR = "char";
type=math如图
http://localhost:8096/captcha/captchaImage?identifier=your UUID &type=math
type=char如图:
http://localhost:8096/captcha/captchaImage?identifier=your UUID &type=char
附:ResponseUtils 响应工具类
package com.utils.common;
import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import static java.lang.System.out;
/**
* 响应工具类
*
*/
public class ResponseUtils {
public ResponseUtils() {
}
public static void setResponseFileHeader(HttpServletRequest request, HttpServletResponse response, String filename, String type, Long size) throws UnsupportedEncodingException {
String userAgent = request.getHeader("User-Agent");
if (!userAgent.contains("MSIE") && !userAgent.contains("Trident")) {
filename = new String(filename.getBytes("UTF-8"), "ISO-8859-1");
} else {
filename = URLEncoder.encode(filename, "UTF-8");
}
response.addHeader("Content-Disposition", "attachment;filename=" + new String(filename.getBytes(), "UTF-8"));
response.addHeader("Content-Length", "" + size);
response.setContentType("application/octet-stream");
}
public static void responseBufferedImage(HttpServletRequest request, HttpServletResponse response, BufferedImage bufferedImage) throws IOException {
if (bufferedImage != null) {
// 将bufferedImage写入HttpServletResponse OutputStream中
ServletOutputStream out = null;
try {
response.setDateHeader("Expires", 0);
response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
response.addHeader("Cache-Control", "post-check=0, pre-check=0");
response.setHeader("Pragma", "no-cache");
response.setContentType("image/jpeg");
out = response.getOutputStream();
ImageIO.write(bufferedImage, "jpg", out);
out.flush();
} finally {
if (out != null) {
out.close();
}
}
}
}
}
AjaxResult为自定义返回结果类,可以自行自定义 如 code msg data 等