0
点赞
收藏
分享

微信扫一扫

一种前后端滑动验证码方案


概述

实际开发中,很常见的一种需求,即拦截机器人登陆,增加略微复杂的滑动验证码(相对于静态验证码而言)。

网络上有很多的实现,并且封装成maven可以检索的jar包。

实现

先给出后端的接口:

后端

@RequestMapping("checkcaptcha")
public String checkCaptcha(@RequestBody JSONObject jsonObject) {
String captchaToken = jsonObject.getString("captchtoken");
Integer moveLength = jsonObject.getInteger("moveLength");
if (StringUtils.isEmpty(captchaToken)) {
return JSONObject.toJSONString("验证不通过");
}
try {
String token = RedisTool.getValueByKey(jedisCluster, captchaToken);
if (StringUtils.isEmpty(token)) {
return JSONObject.toJSONString("验证过期,请重试");
}
Integer xWidth = Integer.parseInt(RedisTool.getValueByKey(jedisCluster, captchaToken).trim());
// 精度控制
if (Math.abs(xWidth - moveLength) > 10) {
RedisTool.delKey(jedisCluster, captchaToken);
return JSONObject.toJSONString("验证不通过");
}
} catch (Exception e) {
RedisTool.delKey(jedisCluster, captchaToken);
return JSONObject.toJSONString(e.getMessage());
}
return JSONObject.toJSONString("success");
}

@RequestMapping(value = "/captcha")
public String getCaptchaImage() {
VerificationCodePlace vcPlace = VerificationCodeAdapter.getRandomVerificationCodePlace(this.getClass().getResource("/static/captcha/after/").getPath(),
this.getClass().getResource("/static/captcha/image/").getPath());
try {
String token = UUID.randomUUID().toString();
RedisTool.setKeyValueExpire(jedisCluster, token, vcPlace.getXLocation().toString(), 60 * 2);
vcPlace.setXLocation(0);
vcPlace.setCaptchtoken(token);
return JSONObject.toJSONString(vcPlace);
} catch (Exception e) {
return JSONObject.toJSONString(e.getMessage());
}
}

POJO

@Data
public class VerificationCodePlace {
private String backName;
private String markName;
private Integer xLocation;
private Integer yLocation;
private String captchtoken;

public VerificationCodePlace(String backName, String markName, Integer xLocation, Integer yLocation) {
this.backName = backName;
this.markName = markName;
this.xLocation = xLocation;
this.yLocation = yLocation;
}

}

@Slf4j
public class VerificationCodeAdapter {
/**
* 模板图宽度
*/
private static final int CUT_WIDTH = 50;
/**
* 模板图高度
*/
private static final int CUT_HEIGHT = 50;
/**
* 抠图凸起圆心
*/
private static final int circleR = 5;
/**
* 抠图内部矩形填充大小
*/
private static final int RECTANGLE_PADDING = 8;
/**
* 抠图的边框宽度
*/
private static final int SLIDER_IMG_OUT_PADDING = 1;

// 生成拼图样式
private static int[][] getBlockData() {
int[][] data = new int[CUT_WIDTH][CUT_HEIGHT];
Random random = new Random();
// (x-a)²+(y-b)²=r²
// x中心位置左右5像素随机
double x1 = RECTANGLE_PADDING + (CUT_WIDTH - 2 * RECTANGLE_PADDING) / 2.0 - 5 + random.nextInt(10);
// y矩形上边界半径-1像素移动
double y1_top = RECTANGLE_PADDING - random.nextInt(3);
double y1_bottom = CUT_HEIGHT - RECTANGLE_PADDING + random.nextInt(3);
double y1 = random.nextInt(2) == 1 ? y1_top : y1_bottom;

double x2_right = CUT_WIDTH - RECTANGLE_PADDING - circleR + random.nextInt(2 * circleR - 4);
double x2_left = RECTANGLE_PADDING + circleR - 2 - random.nextInt(2 * circleR - 4);
double x2 = random.nextInt(2) == 1 ? x2_right : x2_left;
double y2 = RECTANGLE_PADDING + (CUT_HEIGHT - 2 * RECTANGLE_PADDING) / 2.0 - 4 + random.nextInt(10);

double po = Math.pow(circleR, 2);
for (int i = 0; i < CUT_WIDTH; i++) {
for (int j = 0; j < CUT_HEIGHT; j++) {
// 矩形区域
boolean fill;
if ((i >= RECTANGLE_PADDING && i < CUT_WIDTH - RECTANGLE_PADDING)
&& (j >= RECTANGLE_PADDING && j < CUT_HEIGHT - RECTANGLE_PADDING)) {
data[i][j] = 1;
fill = true;
} else {
data[i][j] = 0;
fill = false;
}
// 凸出区域
double d3 = Math.pow(i - x1, 2) + Math.pow(j - y1, 2);
if (d3 < po) {
data[i][j] = 1;
} else {
if (!fill) {
data[i][j] = 0;
}
}
// 凹进区域
double d4 = Math.pow(i - x2, 2) + Math.pow(j - y2, 2);
if (d4 < po) {
data[i][j] = 0;
}
}
}
// 边界阴影
for (int i = 0; i < CUT_WIDTH; i++) {
for (int j = 0; j < CUT_HEIGHT; j++) {
// 四个正方形边角处理
for (int k = 1; k <= SLIDER_IMG_OUT_PADDING; k++) {
// 左上、右上
if (i >= RECTANGLE_PADDING - k && i < RECTANGLE_PADDING
&& ((j >= RECTANGLE_PADDING - k && j < RECTANGLE_PADDING)
|| (j >= CUT_HEIGHT - RECTANGLE_PADDING - k && j < CUT_HEIGHT - RECTANGLE_PADDING + 1))) {
data[i][j] = 2;
}
// 左下、右下
if (i >= CUT_WIDTH - RECTANGLE_PADDING + k - 1 && i < CUT_WIDTH - RECTANGLE_PADDING + 1) {
for (int n = 1; n <= SLIDER_IMG_OUT_PADDING; n++) {
if (((j >= RECTANGLE_PADDING - n && j < RECTANGLE_PADDING)
|| (j >= CUT_HEIGHT - RECTANGLE_PADDING - n && j <= CUT_HEIGHT - RECTANGLE_PADDING))) {
data[i][j] = 2;
}
}
}
}
if (data[i][j] == 1 && j - SLIDER_IMG_OUT_PADDING > 0 && data[i][j - SLIDER_IMG_OUT_PADDING] == 0) {
data[i][j - SLIDER_IMG_OUT_PADDING] = 2;
}
if (data[i][j] == 1 && j + SLIDER_IMG_OUT_PADDING > 0 && j + SLIDER_IMG_OUT_PADDING < CUT_HEIGHT && data[i][j + SLIDER_IMG_OUT_PADDING] == 0) {
data[i][j + SLIDER_IMG_OUT_PADDING] = 2;
}
if (data[i][j] == 1 && i - SLIDER_IMG_OUT_PADDING > 0 && data[i - SLIDER_IMG_OUT_PADDING][j] == 0) {
data[i - SLIDER_IMG_OUT_PADDING][j] = 2;
}
if (data[i][j] == 1 && i + SLIDER_IMG_OUT_PADDING > 0 && i + SLIDER_IMG_OUT_PADDING < CUT_WIDTH && data[i + SLIDER_IMG_OUT_PADDING][j] == 0) {
data[i + SLIDER_IMG_OUT_PADDING][j] = 2;
}
}
}
return data;
}

// 抠出拼图
private static void cutImgByTemplate(BufferedImage oriImage, BufferedImage targetImage, int[][] blockImage, int x, int y) {
int[][] martrix = new int[3][3];
int[] values = new int[9];
for (int i = 0; i < CUT_WIDTH; i++) {
for (int j = 0; j < CUT_HEIGHT; j++) {
int _x = x + i;
int _y = y + j;
int rgbFlg = blockImage[i][j];
int rgb_ori = oriImage.getRGB(_x, _y);
// 原图中对应位置变色处理
if (rgbFlg == 1) {
// 抠图上复制对应颜色值
targetImage.setRGB(i, j, rgb_ori);
//原图对应位置颜色变化
// oriImage.setRGB(_x, _y, Color.LIGHT_GRAY.getRGB());

// 抠图区域高斯模糊
readPixel(oriImage, _x, _y, values);
fillMatrix(martrix, values);
oriImage.setRGB(_x, _y, avgMatrix(martrix));
} else if (rgbFlg == 2) {
targetImage.setRGB(i, j, Color.WHITE.getRGB());
oriImage.setRGB(_x, _y, Color.GRAY.getRGB());
} else if (rgbFlg == 0) {
//int alpha = 0;
// targetImage.setRGB(i, j, rgb_ori & 0x00ffffff);
}
}
}
}

private static void readPixel(BufferedImage img, int x, int y, int[] pixels) {
int xStart = x - 1;
int yStart = y - 1;
int current = 0;
for (int i = xStart; i < 3 + xStart; i++) {
for (int j = yStart; j < 3 + yStart; j++) {
int tx = i;
if (tx < 0) {
tx = -tx;

} else if (tx >= img.getWidth()) {
tx = x;
}
int ty = j;
if (ty < 0) {
ty = -ty;
} else if (ty >= img.getHeight()) {
ty = y;
}
pixels[current++] = img.getRGB(tx, ty);
}
}
}

private static void fillMatrix(int[][] matrix, int[] values) {
int filled = 0;
for (int[] x : matrix) {
for (int j = 0; j < x.length; j++) {
x[j] = values[filled++];
}
}
}

private static int avgMatrix(int[][] matrix) {
int r = 0;
int g = 0;
int b = 0;
for (int[] x : matrix) {
for (int j = 0; j < x.length; j++) {
if (j == 1) {
continue;
}
Color c = new Color(x[j]);
r += c.getRed();
g += c.getGreen();
b += c.getBlue();
}
}
return new Color(r / 8, g / 8, b / 8).getRGB();
}

/**
* 获取图片
*/
private static BufferedImage getBufferedImage(String path) throws IOException {
File file = new File(path);
if (file.isFile()) {
return ImageIO.read(file);
}
return null;
}

// 处理存放
private static VerificationCodePlace cutAndSave(String imgName, String path, int[][] data, String headPath) throws Exception {
VerificationCodePlace vcPlace =
new VerificationCodePlace("sample_after.png", "sample_after_mark.png", 112, 50);

// 进行图片处理
BufferedImage originImage = getBufferedImage(path);
if (originImage != null) {
int locationX = 90 + new Random().nextInt(originImage.getWidth() - CUT_WIDTH * 3);
int locationY = new Random().nextInt(originImage.getHeight() - CUT_HEIGHT) / 2;
BufferedImage markImage = new BufferedImage(CUT_WIDTH, CUT_HEIGHT, BufferedImage.TYPE_4BYTE_ABGR);
cutImgByTemplate(originImage, markImage, data, locationX, locationY);
vcPlace = new VerificationCodePlace(getImageBASE64(originImage), getImageBASE64(markImage), locationX, locationY);
}
return vcPlace;
}

/**
* 图片转BASE64
*/
private static String getImageBASE64(BufferedImage image) throws IOException {
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ImageIO.write(image, "png", bao);
byte[] imagedata = bao.toByteArray();
BASE64Encoder encoder = new BASE64Encoder();
String base64Image = encoder.encodeBuffer(imagedata).trim();
// 删除 \r\n
base64Image = base64Image.replaceAll("\r|\n", "");
return base64Image;
}

/**
* 获取文件夹下所有文件名
*/
private static ArrayList<String> getFileNamesFromDic(String dicPath) {
File dic = new File(dicPath);
ArrayList<String> imageFileNames = new ArrayList<>();
File[] dicFileList = dic.listFiles();
for (File f : dicFileList) {
imageFileNames.add(f.getName());
}
return imageFileNames;
}

/**
* 总流程,随机获取图片并处理,将拼图和对应图片存放至after_img
* 出错则返回sample
* headPath为存放生成图片的文件夹地址
*/
public static VerificationCodePlace getRandomVerificationCodePlace(String headPath, String imageUrl) {
VerificationCodePlace vcPlace = new VerificationCodePlace("sample_after.png", "sample_mark_after.png", 112, 50);

// 从文件夹中读取所有待选择文件
ArrayList<String> imageFileNames = getFileNamesFromDic(imageUrl);

// 随机获取
int r = (int) Math.round(Math.random() * (imageFileNames.size() - 1));
String imgName = imageFileNames.get(r);
String path = imageUrl + imgName;
int[][] data = VerificationCodeAdapter.getBlockData();

// 进行图片处理
try {
vcPlace = cutAndSave(imgName, path, data, headPath);
} catch (Exception e) {
log.error("getRandomVerificationCodePlace failed: " + e.getMessage());
return vcPlace;
}
return vcPlace;
}
}

前端

优化

一种前后端滑动验证码方案_ide

参考


举报

相关推荐

0 条评论