生成滑块验证码学习使用
【代码】生成滑块验证码学习使用。
·
生成验证码工具类
public class VerifyCodeUtils {
/**
* 入参校验设置默认值
**/
public static void checkCaptcha(Captcha captcha) {
//设置画布宽度默认值
if (captcha.getCanvasWidth() == null) {
captcha.setCanvasWidth(320);
}
//设置画布高度默认值
if (captcha.getCanvasHeight() == null) {
captcha.setCanvasHeight(50);
}
//设置阻塞块宽度默认值
if (captcha.getBlockWidth() == null) {
captcha.setBlockWidth(40);
}
//设置阻塞块高度默认值
if (captcha.getBlockHeight() == null) {
captcha.setBlockHeight(50);
}
//设置阻塞块凹凸半径默认值
if (captcha.getBlockRadius() == null) {
captcha.setBlockRadius(-1);
}
}
/**
* 获取指定范围内的随机数
**/
public static int getNonceByRange2(int canvasWidth,int i) {
return new BigDecimal(i).divide(new BigDecimal(100)).multiply(new BigDecimal(canvasWidth)).intValue();
}
/**
* 获取指定范围内的随机数
**/
public static int getNonceByRange(int start, int end) {
Random random = new Random();
return random.nextInt(Math.abs(end - start + 1)) + start;
}
/**
* 获取验证码资源图
**/
public static BufferedImage getBufferedImage(InputStream inputStream) {
try {
return ImageIO.read(inputStream);
} catch (Exception e) {
System.out.println("获取拼图资源失败");
//异常处理
return null;
}
}
/**
* 调整图片大小
**/
public static BufferedImage imageResize(BufferedImage bufferedImage, int width, int height) {
Image image = bufferedImage.getScaledInstance(width, height, Image.SCALE_SMOOTH);
BufferedImage resultImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics2D = resultImage.createGraphics();
graphics2D.drawImage(image, 0, 0, null);
graphics2D.dispose();
return resultImage;
}
/**
* 抠图,并生成阻塞块
**/
public static void cutByTemplate(BufferedImage canvasImage, BufferedImage blockImage, int blockWidth, int blockHeight, int blockRadius, int blockX, int blockY) {
BufferedImage waterImage = new BufferedImage(blockWidth, blockHeight, BufferedImage.TYPE_4BYTE_ABGR);
//阻塞块的轮廓图
int[][] blockData = getBlockData(blockWidth, blockHeight, blockRadius);
//创建阻塞块具体形状
for (int i = 0; i < blockWidth; i++) {
for (int j = 0; j < blockHeight; j++) {
try {
//原图中对应位置变色处理
if (blockData[i][j] == 1) {
//背景设置为绿色
waterImage.setRGB(i, j, new Color(0,255,0).getRGB());
blockImage.setRGB(i, j, canvasImage.getRGB(blockX + i, blockY + j));
//轮廓设置为白色,取带像素和无像素的界点,判断该点是不是临界轮廓点
if (blockData[i + 1][j] == 0 || blockData[i][j + 1] == 0 || blockData[i - 1][j] == 0 || blockData[i][j - 1] == 0) {
blockImage.setRGB(i, j, Color.WHITE.getRGB());
waterImage.setRGB(i, j, Color.WHITE.getRGB());
}
}
//这里把背景设为透明
else {
blockImage.setRGB(i, j, Color.TRANSLUCENT);
waterImage.setRGB(i, j, Color.TRANSLUCENT);
}
} catch (ArrayIndexOutOfBoundsException e) {
//防止数组下标越界异常
}
}
}
//在画布上添加阻塞块水印
addBlockWatermark(canvasImage, waterImage, blockX, blockY);
}
/**
* 构建拼图轮廓轨迹
**/
private static int[][] getBlockData(int blockWidth, int blockHeight, int blockRadius) {
int[][] data = new int[blockWidth][blockHeight];
double po = Math.pow(blockRadius, 2);
//随机生成两个圆的坐标,在4个方向上 随机找到2个方向添加凸/凹
//凸/凹1
int face1 = ThreadLocalRandom.current().nextInt(0,4);
//凸/凹2
int face2;
//保证两个凸/凹不在同一位置
do {
face2 = ThreadLocalRandom.current().nextInt(0,4);
} while (face1 == face2);
//获取凸/凹起位置坐标
int[] circle1 = getCircleCoords(face1, blockWidth, blockHeight, blockRadius);
int[] circle2 = getCircleCoords(face2, blockWidth, blockHeight, blockRadius);
//随机凸/凹类型
int shape = getNonceByRange(0, 1);
//圆的标准方程 (x-a)²+(y-b)²=r²,标识圆心(a,b),半径为r的圆
//计算需要的小图轮廓,用二维数组来表示,二维数组有两张值,0和1,其中0表示没有颜色,1有颜色
for (int i = 0; i < blockWidth; i++) {
for (int j = 0; j < blockHeight; j++) {
data[i][j] = 0;
//创建中间的方形区域
if ((i >= blockRadius && i <= blockWidth - blockRadius && j >= blockRadius && j <= blockHeight - blockRadius)) {
data[i][j] = 1;
}
double d1 = Math.pow(i - Objects.requireNonNull(circle1)[0], 2) + Math.pow(j - circle1[1], 2);
double d2 = Math.pow(i - Objects.requireNonNull(circle2)[0], 2) + Math.pow(j - circle2[1], 2);
//创建两个凸/凹 不需要
/* if (d1 <= po || d2 <= po) {
data[i][j] = shape;
}*/
}
}
return data;
}
/**
* 根据朝向获取圆心坐标
*/
private static int[] getCircleCoords(int face, int blockWidth, int blockHeight, int blockRadius) {
//上
if (0 == face) {
return new int[]{blockWidth / 2 - 1, blockRadius};
}
//左
else if (1 == face) {
return new int[]{blockRadius, blockHeight / 2 - 1};
}
//下
else if (2 == face) {
return new int[]{blockWidth / 2 - 1, blockHeight - blockRadius - 1};
}
//右
else if (3 == face) {
return new int[]{blockWidth - blockRadius - 1, blockHeight / 2 - 1};
}
return null;
}
/**
* 在画布上添加阻塞块水印
*/
private static void addBlockWatermark(BufferedImage canvasImage, BufferedImage blockImage, int x, int y) {
Graphics2D graphics2D = canvasImage.createGraphics();
graphics2D.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.8f));
graphics2D.drawImage(blockImage, x, y, null);
graphics2D.dispose();
}
/**
* BufferedImage转BASE64
*/
public static String toBase64(BufferedImage bufferedImage, String type) {
try {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, type, byteArrayOutputStream);
String base64 = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
return String.format("data:image/%s;base64,%s", type, base64);
} catch (IOException e) {
System.out.println("图片资源转换BASE64失败");
//异常处理
return null;
}
}
}
生成滑块验证码接口
@ApiOperation(value="请求生成滑块验证信息", httpMethod="POST")
@ApiImplicitParams({
@ApiImplicitParam(name="cw",value="画布宽 默认320"),
@ApiImplicitParam(name="ch",value="画布搞 默认50"),
@ApiImplicitParam(name="bw",value="阻塞块宽 默认40"),
@ApiImplicitParam(name="bh",value="阻塞块高 默认50"),
@ApiImplicitParam(name="br",value="阻塞块凹凸半径 默认0"),
@ApiImplicitParam(name="aliveMinutes",value="存活分钟 默认30分钟",required=false)
})
@RequestMapping(value="/slider/generate", method= RequestMethod.POST)
public ApiResultDTO<Object> generateSliderVerifyCode(
@RequestParam(name="cw",required=false)Integer cw,
@RequestParam(name="ch",required=false)Integer ch,
@RequestParam(name="bw",required=false)Integer bw,
@RequestParam(name="bh",required=false)Integer bh,
@RequestParam(name="br",required=false)Integer br,
@RequestParam(name="aliveMinutes",required=false)Integer aliveMinutes,
HttpServletRequest request
) {
return RestAPITemplate.restapi(()->{
Captcha captcha = new Captcha(cw,ch,bw,bh,br);
return verifyCodeService.generateSliderCaptcha(captcha,aliveMinutes);
});
}
生成滑块验证码具体实现
public Object generateSliderCaptcha(Captcha captcha,Integer aliveMinutes) {
//参数校验
VerifyCodeUtils.checkCaptcha(captcha);
//获取画布的宽高
int canvasWidth = captcha.getCanvasWidth();
int canvasHeight = captcha.getCanvasHeight();
//获取阻塞块的宽高/半径
int blockWidth = captcha.getBlockWidth();
int blockHeight = captcha.getBlockHeight();
int blockRadius = captcha.getBlockRadius();
//获取资源图
BufferedImage canvasImage = VerifyCodeUtils.getBufferedImage(this.getClass().getClassLoader().getResourceAsStream("img/slider.jpg"));
//调整原图到指定大小
canvasImage = VerifyCodeUtils.imageResize(canvasImage, canvasWidth, canvasHeight);
//随机生成阻塞块坐标 随机数/100*画布宽度
Random random = new Random();
int randomNum = random.nextInt(100);
if(randomNum<40) randomNum=40;
else if(randomNum>80) randomNum=80;
int blockX = VerifyCodeUtils.getNonceByRange2(canvasWidth,randomNum);
int blockY = VerifyCodeUtils.getNonceByRange(0, canvasHeight - blockHeight + 1);
//阻塞块
BufferedImage blockImage = new BufferedImage(blockWidth, blockHeight, BufferedImage.TYPE_4BYTE_ABGR);
//新建的图像根据轮廓图颜色赋值,源图生成遮罩
VerifyCodeUtils.cutByTemplate(canvasImage, blockImage, blockWidth, blockHeight, blockRadius, blockX, blockY);
//创建 verifyCodeKey
String verifyCodeKey;
try {
//业务逻辑
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("生成图片验证码失败");
}
//设置返回参数
captcha.setVerifyCodeKey(verifyCodeKey);
captcha.setBlockSrc(VerifyCodeUtils.toBase64(blockImage, "png"));
captcha.setCanvasSrc(VerifyCodeUtils.toBase64(canvasImage, "png"));
return captcha;
}
滑块模型
public class Captcha {
/**
* 验证码
**/
private String verifyCodeKey;
/**
* 生成的画布的base64
**/
private String canvasSrc;
/**
* 画布宽度
**/
private Integer canvasWidth;
/**
* 画布高度
**/
private Integer canvasHeight;
/**
* 生成的阻塞块的base64
**/
private String blockSrc;
/**
* 阻塞块宽度
**/
private Integer blockWidth;
/**
* 阻塞块高度
**/
private Integer blockHeight;
/**
* 阻塞块凸凹半径
**/
private Integer blockRadius;
public Captcha() {
}
public Captcha(Integer canvasWidth, Integer canvasHeight, Integer blockWidth, Integer blockHeight, Integer blockRadius) {
this.canvasWidth = canvasWidth;
this.canvasHeight = canvasHeight;
this.blockWidth = blockWidth;
this.blockHeight = blockHeight;
this.blockRadius = blockRadius;
}
public String getVerifyCodeKey() {
return verifyCodeKey;
}
public void setVerifyCodeKey(String nonceStr) {
this.verifyCodeKey = nonceStr;
}
public String getCanvasSrc() {
return canvasSrc;
}
public void setCanvasSrc(String canvasSrc) {
this.canvasSrc = canvasSrc;
}
public Integer getCanvasWidth() {
return canvasWidth;
}
public void setCanvasWidth(Integer canvasWidth) {
this.canvasWidth = canvasWidth;
}
public Integer getCanvasHeight() {
return canvasHeight;
}
public void setCanvasHeight(Integer canvasHeight) {
this.canvasHeight = canvasHeight;
}
public String getBlockSrc() {
return blockSrc;
}
public void setBlockSrc(String blockSrc) {
this.blockSrc = blockSrc;
}
public Integer getBlockWidth() {
return blockWidth;
}
public void setBlockWidth(Integer blockWidth) {
this.blockWidth = blockWidth;
}
public Integer getBlockHeight() {
return blockHeight;
}
public void setBlockHeight(Integer blockHeight) {
this.blockHeight = blockHeight;
}
public Integer getBlockRadius() {
return blockRadius;
}
public void setBlockRadius(Integer blockRadius) {
this.blockRadius = blockRadius;
}
//获取阻塞块宽度占比值
public int getBlokWidthProp(){
if(this.blockWidth==null) this.blockWidth = 50;
if(this.canvasWidth==null)this.canvasWidth = 320;
return (this.blockWidth*100)/this.canvasWidth;
}
}
验证滑块验证码接口
@ApiOperation(value="验证滑块验证码", httpMethod="POST")
@ApiImplicitParams({
@ApiImplicitParam(name="verifyCodeKey", value="验证码标识"),
@ApiImplicitParam(name="verifyCode", value="输入的验证码")
})
@RequestMapping(value="/slider/verify", method= RequestMethod.POST)
public ApiResultDTO<String> verifySliderVerifyCode(
@RequestParam("verifyCodeKey")String verifyCodeKey,
@RequestParam("verifyCode")String verifyCode,
HttpServletRequest request
) {
return RestAPITemplate.restapi(()->{
verifyCodeService.checkSliderVerifyCode(verifyCodeKey, verifyCode);
return null;
});
}
//校验滑块验证码
public String checkSliderVerifyCode(String id, String code) {
if(StringUtils.isBlank(code) || StringUtils.isBlank(id)) return "验证码滑块请滑动到指定区域";
VerifyCode verifyCode = this.verifyCodeRepository.getVerifyCodeById(id);
if(verifyCode==null) return "验证已过期";
String errorMsg=null;
if(!VerifyCode.FROMTYPE_SLIDER_CODE.equals(verifyCode.getFromType())) errorMsg="非法生成的验证码";
if(verifyCode.isExpired(null)) errorMsg="验证已过期";
Integer vCode = Integer.valueOf(verifyCode.getVerifyCode());//生成阻塞块的位置
Integer inputCode = Integer.valueOf(code);//用户滑动到阻塞块的位置
Captcha captcha = JsonConverter.jsonStrToObject(verifyCode.getAttr("captcha"),Captcha.class);
int blokWidthPropHalf = (int)(captcha.getBlokWidthProp() * 0.4);//必須划到60%算成功
if(inputCode>vCode || inputCode<vCode-blokWidthPropHalf){
errorMsg="验证失败,请拖动到绿色区域内";
}
if(StringUtils.isNotBlank(errorMsg)){
verifyCode.fail();
if(verifyCode.obtainFailCount()>2 && verifyCode.obtainFailCount()<10){
this.verifyCodeRepository.deleteVerifyCode(verifyCode);//连续失败超过3次删掉
return errorMsg;
}
this.verifyCodeRepository.createVerifyCode(verifyCode);
}else{
if(verifyCode.obtainFailCount()>12){
this.verifyCodeRepository.deleteVerifyCode(verifyCode);//连续成功超过3次删掉
return errorMsg;
}
verifyCode.sucess();
this.verifyCodeRepository.createVerifyCode(verifyCode);
}
return errorMsg;
}
网易易盾是国内领先的数字内容风控服务商,依托网易二十余年的先进技术和一线实践经验沉淀,为客户提供专业可靠的安全服务,涵盖内容安全、业务安全、应用安全、安全专家服务四大领域,全方位保障客户业务合规、稳健和安全运营。
更多推荐



所有评论(0)