一.背景
图形验证码的生成与使用(防止机器恶意攻击)
二.代码(使用)
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,noproperties.setProperty("kaptcha.border", "yes");// 边框颜色 默认为Color.BLACKproperties.setProperty("kaptcha.border.color", "105,179,90");// 验证码文本字符颜色 默认为Color.BLACKproperties.setProperty("kaptcha.textproducer.font.color", "blue");// 验证码图片宽度 默认为200properties.setProperty("kaptcha.image.width", "160");// 验证码图片高度 默认为50properties.setProperty("kaptcha.image.height", "60");// 验证码文本字符大小 默认为40properties.setProperty("kaptcha.textproducer.font.size", "30");// KAPTCHA_SESSION_KEYproperties.setProperty("kaptcha.session.key", "kaptchaCode");// 验证码文本字符间距 默认为2properties.setProperty("kaptcha.textproducer.char.space", "3");// 验证码文本字符长度 默认为5properties.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.BLACKproperties.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,noproperties.setProperty("kaptcha.border", "yes");// 边框颜色 默认为Color.BLACKproperties.setProperty("kaptcha.border.color", "105,179,90");// 验证码文本字符颜色 默认为Color.BLACKproperties.setProperty("kaptcha.textproducer.font.color", "blue");// 验证码图片宽度 默认为200properties.setProperty("kaptcha.image.width", "160");// 验证码图片高度 默认为50properties.setProperty("kaptcha.image.height", "60");// 验证码文本字符大小 默认为40properties.setProperty("kaptcha.textproducer.font.size", "35");// KAPTCHA_SESSION_KEYproperties.setProperty("kaptcha.session.key", "kaptchaCodeMath");// 验证码文本生成器 【com.provider.auth.config.KaptchaTextCreator 自定义的验证码创建类 后面有附上】properties.setProperty("kaptcha.textproducer.impl", "com.provider.auth.config.KaptchaTextCreator");// 验证码文本字符间距 默认为2properties.setProperty("kaptcha.textproducer.char.space", "3");// 验证码文本字符长度 默认为5properties.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.BLACKproperties.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.ShadowGimpyproperties.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(",");@Overridepublic 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("=?@" + 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;@Autowiredprivate RedisHelper redisHelper;@Autowiredprivate 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 等