滑动拼图验证码服务端实现


记录一次滑动拼图验证码的实现流程

由于资源有限,实现原理比较简单。没有使用任何的第三方jar。只是用了原生的 Java api。

验证是否通过的原理是,判断小图的结束坐标,与抠图位置的坐标是否吻合。

主要难点就是,小图的生成,目前只对小图增加了白色的边框,没有做阴影及边缘模糊处理。

 

1、随机获取一张地图

2、在一定范围内随机生成一个坐标点用于抠图

3、构建小图方形矩阵,随机选取两个边添加凸起,创建图层

4、将底图的颜色拷贝到小图中

5、给底图添加灰色水印

6、返回小图的随机位置,以及地图和小图的base64图片

准备工作: 10张地图、redis(抠图位置的坐标和验证码的其他用户验证的相关信息放到了redis中)。

 

缺点:

图片效果有待优化,抠图的代码自于网络,比较简单;后期应考虑用开源的api来处理,替代原生api, 小图应该增加阴影和边缘虚化处理,让客户看起来更容易。

验证算法过于简单。适用于项目前期。算法是验证码的核心,网络上也有很多验证算法,但是安全性不高实现,方式比较复杂。有算法工程师的可以考虑加入识别算法。

 

入口方法:

public DubboResult<JigsawImageDTO> getJigsawImage() {

String fileName = getBaseImageFileName();
String jigsawId = UUID.randomUUID().toString();
logger.info("fileName={}", fileName);
Map<String, String> result = generateJigsaw(fileName);

//放入缓存
CoordinateDTO slideImageTargetCoordinate = new CoordinateDTO();
slideImageTargetCoordinate.setX(Integer.valueOf(result.get("location_x")));
slideImageTargetCoordinate.setY(Integer.valueOf(result.get("location_y")));
JigsawImageRedisInfoDTO jigsawImageRedisInfoDTO = new JigsawImageRedisInfoDTO();
jigsawImageRedisInfoDTO.setSlideImageCoordinate(slideImageTargetCoordinate);
jigsawImageRedisInfoDTO.setJigsawId(jigsawId);
jigsawImageRedisInfoDTO.setSuccess(Boolean.FALSE);
RedisUtil.set(getRedieKey(jigsawId), JSON.toJSONString(jigsawImageRedisInfoDTO), JIGSAW_VALID_TIME);

//拼接返回值
JigsawImageDTO jigsawImageDTO = new JigsawImageDTO();
jigsawImageDTO.setJigsawId(jigsawId);
jigsawImageDTO.setBaseImage(result.get("bigImgBase64Str"));
jigsawImageDTO.setSlideImage(result.get("smallImgBase46Str"));
//int randomX = new Random().nextInt(Integer.valueOf(slideImageTargetCoordinate.getX())/3);
slideImageTargetCoordinate.setX(20);
jigsawImageDTO.setSlideImageCoordinate(slideImageTargetCoordinate);

return DubboResult.correct(jigsawImageDTO);
}

private String getBaseImageFileName() {
String fileName1 = this.getClass().getClassLoader().getResource("").toString();
logger.info("filename1={}", fileName1);
String fileName = this.getClass().getResource("/").toString()
+ BASE_IMAGE_DIRECTORY
+ (new Random().nextInt(BASE_IMAGE_COUNT) + 1)
+ BASE_IMAGE_TYPE;
return fileName.substring(5, fileName.length());
}

/**
** 随机选取一个地图
*/
private Map<String, String> generateJigsaw(String fileName) {
try {
BufferedImage image = ImageIO.read(new File(fileName));
ByteArrayOutputStream out = new ByteArrayOutputStream();
ImageIO.write(image, "png", out);
Map<String, String> result = CaptchaUtils.createImage(new File(fileName));
logger.info("result:" + result);
return result;
} catch (Exception e) {
logger.error("获取图片失败", e);
}
return null;
}

图片处理工具类:

 

package com.isesol.jigsaw.util;


import org.apache.commons.lang.math.RandomUtils;
import sun.misc.BASE64Encoder;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

public class CaptchaUtils {

static int targetWidth = 50;//小图长
static int targetHeight = 50;//小图高
static int circleR = 7;//半径

private static int[][] getBlockDataV2() {
int[][] data = new int[targetWidth][targetHeight];

double po = Math.pow(circleR, 2);

//随机生成两个圆的坐标
//在4个方向上 随机找到2个方向添加凸出
//凸出1
int face1 = RandomUtils.nextInt(3);
//凸出2
int face2;
//使凸出1 与 凸出2不在同一个方向
while (true) {
face2 = RandomUtils.nextInt(3);
if (face1 != face2) {
break;
}
}

int center1X = getCenterLocation(face1).get("x");
int center1Y = getCenterLocation(face1).get("y");
int center2X = getCenterLocation(face2).get("x");
int center2Y = getCenterLocation(face2).get("y");

//圆的标准方程 (x-a)²+(y-b)²=r²,标识圆心(a,b),半径为r的圆
//计算需要的小图轮廓,用二维数组来表示,二维数组有两张值,0和1,其中0表示没有颜色,1有颜色
for (int i = 0; i < targetWidth; i++) {
for (int j = 0; j < targetHeight; j++) {
data[i][j] = 0;

double d1 = Math.pow(i - center1X, 2) + Math.pow(j - center1Y, 2);
double d2 = Math.pow(i - center2X, 2) + Math.pow(j - center2Y, 2);

//创建中间的方形区域
if ((i >= circleR && i <= targetWidth - circleR
&& j >= circleR
&& j <= targetHeight - circleR)) {
data[i][j] = 1;
}
//创建两个凸起
if (d1 <= po || d2 <= po) {
data[i][j] = 1;
}

//优化 凸起顶点部分效果
if(i == (targetWidth/2- 1) && j == 0){
data[i][j] = 0;
}
if(i == (targetWidth - 1) && j == (targetHeight/2 - 1)){
data[i][j] = 0;
}
if(i == (targetWidth/2- 1) && j == targetHeight - 1){
data[i][j] = 0;
}
if(i == 0 && j == (targetHeight/2 - 1)){
data[i][j] = 0;
}
}
}
return data;
}

/**
* 根据圆的方向获取圆心坐标
*
* @param face
* @return
*/
private static Map<String, Integer> getCenterLocation(int face) {
Map<String, Integer> map = new HashMap<>(2);
//上
if (0 == face) {
map.put("x", targetWidth / 2 - 1);
map.put("y", circleR);
//左
} else if (1 == face) {
map.put("x", circleR);
map.put("y", targetHeight / 2 - 1);
//下
} else if (2 == face) {
map.put("x", targetWidth / 2 - 1);
map.put("y", targetHeight - circleR - 1);
//右
} else if (3 == face) {
map.put("x", targetWidth - circleR - 1);
map.put("y", targetHeight / 2 - 1);
}
return map;
}

private static void cutByTemplate1(BufferedImage oriImage, BufferedImage targetImage, int[][] templateImage, int x, int y) {
int[][] martrix = new int[3][3];
int[] values = new int[9];

BufferedImage waterImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_4BYTE_ABGR);
//创建shape区域
for (int i = 0; i < targetWidth; i++) {
for (int j = 0; j < targetHeight; j++) {
int rgb = templateImage[i][j];
// 原图中对应位置变色处理
int rgb_ori = oriImage.getRGB(x + i, y + j);

if (rgb == 1) {
waterImage.setRGB(i, j, Color.BLACK.getRGB());
targetImage.setRGB(i, j, rgb_ori);
} else {
//这里把背景设为透明
targetImage.setRGB(i, j, rgb_ori & 0x00ffffff);
waterImage.setRGB(i, j, rgb_ori & 0x00ffffff);
}
int xLength = templateImage[1].length;
int yLength = templateImage.length;
//防止数组越界判断
if (i == (xLength - 1) || j == (yLength - 1)) {
continue;
}

//描边处理,,取带像素和无像素的界点,判断该点是不是临界轮廓点,如果是设置该坐标像素是白色
if ((templateImage[i][j] == 1 && templateImage[i + 1][j] == 0) || (templateImage[i][j] == 1 && templateImage[i][j + 1] == 0)
|| (templateImage[i][j] == 1 && templateImage[i - 1][j] == 0) || (templateImage[i][j] == 1 && templateImage[i][j - 1] == 0)) {
targetImage.setRGB(i, j, Color.white.getRGB());
waterImage.setRGB(i, j, Color.white.getRGB());
//oriImage.setRGB(x + i, y + j, Color.white.getRGB());
}
}
}

addWatermark(oriImage, waterImage, x, y, 0.8F);
}

private static BufferedImage addWatermark(BufferedImage imageBuffer, BufferedImage smallImage, int x, int y, float alpha){
Graphics2D graphics2D = imageBuffer.createGraphics();
graphics2D.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
graphics2D.drawImage(smallImage, x, y, null);
graphics2D.dispose(); //释放
return imageBuffer;
}

public static Map<String, String> createImage(File file) {
Map<String, String> resultMap = new HashMap<>();
try {
BufferedImage oriImage = ImageIO.read(file);
Random random = new Random();
//X轴距离右端targetWidth Y轴距离底部targetHeight以上
int widthRandom = random.nextInt(targetWidth / 2) + oriImage.getWidth() / 2 + targetWidth;
int heightRandom = random.nextInt(targetHeight) + targetHeight / 4;

BufferedImage targetImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_4BYTE_ABGR);
cutByTemplate1(oriImage, targetImage, getBlockDataV2(), widthRandom, heightRandom);

//BufferedImage aaa = addaa(targetImage, 20, 20, Color.red, 0.3f);

resultMap.put("bigImgBase64Str", getImageBASE64(oriImage));//大图
//System.out.println(getImageBASE64(oriImage));
resultMap.put("smallImgBase46Str", getImageBASE64(targetImage));//小图
//System.out.println(getImageBASE64(targetImage));
resultMap.put("location_x", String.valueOf(widthRandom));
resultMap.put("location_y", String.valueOf(heightRandom));
} catch (Exception e) {
e.printStackTrace();
} finally {
return resultMap;
}
}


/**
* @param image
*/
public static String getImageBASE64(BufferedImage image) throws IOException {
byte[] imagedata = null;
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ImageIO.write(image, "png", bao);
imagedata = bao.toByteArray();
BASE64Encoder encoder = new BASE64Encoder();
String BASE64IMAGE = encoder.encodeBuffer(imagedata).trim();
BASE64IMAGE = BASE64IMAGE.replaceAll("\r|\n", ""); //删除 \r\n
return BASE64IMAGE;
}

public static void main(String[] args) {
Map<String, Object> resultMap = new HashMap<>();
//读取本地路径下的图片,随机选一条
File file = new File("D:\\intellj_text_prj\\boot-test\\src\\main\\resources\\static\\captcha_image_01.png");
createImage(file);
resultMap.remove("xWidth");
resultMap.put("errcode", 0);
resultMap.put("errmsg", "success");
}

}

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM