Java实现滑块拼图验证码校验

最近有个需求,需要添加滑块拼图验证码,网上了解了一些生成校验方式,下面写个 demo实现一下。

一、滑块拼图验证码生成

1、生成思路

滑块拼图验证码生成思路:

  1. 在若干原图中随机一张原图,然后改变原图大小为规范的大图对象
  2. 随机生成(X,Y)坐标
  3. 创建小图对象
  4. 随机生成拼图轮廓数据
  5. 从大图中裁剪拼图。抠原图,裁剪拼图
  6. 返回滑块拼图验证码信息:两个Base64字符串图片信息和(X,Y)坐标。

注意:

  • 1、随机生成拼图轮廓数据是重点,然后裁剪拼图时根据需要它来抠原图,裁剪拼图。
  • 2、拼图的凹凸信息,有两种处理方式
    • 以小图的四边为边缘,外加凸圆弧或者内挖凹圆弧。
    • 以小图的四边为最终边界,内加凸圆弧或者内挖凹圆弧,并且处理掉凹凸之外的颜色。

2、生成代码

这里使用 以小图的四边为最终边界,上凹下凸,左无由凸,左边框高亮阴暗简单处理。

定义一个滑块拼图信息实体类:

@Data
public class SliderPuzzleInfo {
	/**
	 * 大图宽度
	 */
	private Integer bigWidth;

	/**
	 * 大图高度
	 */
	private Integer bigHeight;

	/**
	 * 大图转BASE64字符串
	 */
	private String bigImageBase64;

	/**
	 * 大图
	 */
	private BufferedImage bigImage;

	/**
	 * 随机坐标Y
	 */
	private Integer posY;
	/**
	 * 随机坐标X
	 */
	private Integer posX;

	/**
	 * 小图宽度
	 */
	private Integer smallWidth;
	/**
	 * 小图高度
	 */
	private Integer smallHeight;

	/**
	 * 小图转BASE64字符串
	 */
	private String smallImageBase64;

	/**
	 * 小图
	 */
	private BufferedImage smallImage;
	
}

生成具体代码如下:

public class SlidePuzzleUtil {

	static Logger logger = LoggerFactory.getLogger(SlidePuzzleUtil.class);

	// 大图宽度(原图裁剪拼图后的背景图)
	private static final Integer bigWidth = 320;
	// 大图高度
	private static final Integer bigHeight = 160;
	// 小图宽度(滑块拼图)
	private static int smallWidth = 40;
	// 小图高度
	private static int smallHeight = 40;
	// 小圆半径,即拼图上的凹凸轮廓半径
	private static final Integer smallCircle = 8;
	// 小圆距离点
	private static int smallCircleR1 = smallCircle / 2;

	public static void main(String[] args) throws IOException {
		int i = 3;
		File file = new File("D:/TempFiles/slide/slidebase" + i + ".png");
		SliderPuzzleInfo sliderPuzzleInfo = SlidePuzzleUtil.createImage(new FileInputStream(file));

		if (sliderPuzzleInfo == null) {
			System.out.println("图片验证码生成失败");
		}

		File file1 = new File("D:/TempFiles/slide/demo2BigImage.png");
		File file2 = new File("D:/TempFiles/slide/demo2SmallImage.png");
		ImageIO.write(sliderPuzzleInfo.getBigImage(), "png", file1);
		ImageIO.write(sliderPuzzleInfo.getSmallImage(), "png", file2);

	}

	/**
	 * 生成滑块拼图验证码
	 * 
	 * @param input
	 * @return 返回null,表示生成滑块拼图验证码异常
	 */
	public static SliderPuzzleInfo createImage(InputStream input) {
		SliderPuzzleInfo sliderPuzzleInfo = new SliderPuzzleInfo();
		try {
			// 1.获取原图对象
			BufferedImage originalImage = ImageIO.read(input);
			// 规范原图的大小
			BufferedImage bigImage = resizeImage(originalImage, bigWidth, bigHeight, true);

			// 2.随机生成离左上角的(X,Y)坐标,上限为 [bigWidth-smallWidth, bigHeight-smallHeight]。最好离大图左边远一点,上限不要紧挨着大图边界
			Random random = new Random();
			int randomX = random.nextInt(bigWidth - 4 * smallWidth - smallCircle) + 2 * smallWidth; // X范围:[2*smallWidth, bigWidth - 2*smallWidth - smallCircle)
			int randomY = random.nextInt(bigHeight - smallHeight - 2 * smallCircle) + smallCircle; // Y范围:[smallCircle, bigHeight - smallHeight - smallCircle)
			logger.info("原图大小:{} x {},大图大小:{} x {},随机生成的坐标:(X,Y)=({},{})", originalImage.getWidth(), originalImage.getHeight(), bigImage.getWidth(), bigImage.getHeight(),
					randomX, randomY);

			// 3.创建小图对象
			BufferedImage smallImage = new BufferedImage(smallWidth, smallHeight, BufferedImage.TYPE_4BYTE_ABGR);

			// 4.随机生成拼图轮廓数据
			int[][] slideTemplateData = getSlideTemplateData(smallWidth, smallHeight, smallCircle, smallCircleR1);

			// 5.从大图中裁剪拼图。抠原图,裁剪拼图
			cutByTemplate(bigImage, smallImage, slideTemplateData, randomX, randomY);

			sliderPuzzleInfo.setPosX(randomX);
			sliderPuzzleInfo.setPosY(randomY);
			sliderPuzzleInfo.setBigWidth(bigWidth);
			sliderPuzzleInfo.setBigHeight(bigHeight);
			sliderPuzzleInfo.setBigImage(bigImage);
			sliderPuzzleInfo.setBigImageBase64(getImageBASE64(bigImage));
			sliderPuzzleInfo.setSmallWidth(smallWidth);
			sliderPuzzleInfo.setSmallHeight(smallHeight);
			sliderPuzzleInfo.setSmallImage(smallImage);
			sliderPuzzleInfo.setSmallImageBase64(getImageBASE64(smallImage));
		} catch (Exception e) {
			sliderPuzzleInfo = null;
			logger.info("创建生成滑块拼图验证码异常,e=", e);
		} finally {
			return sliderPuzzleInfo;
		}
	}

	/**
	 * 获取拼图图轮廓数据
	 * @param smallWidth
	 * @param smallHeight
	 * @param smallCircle
	 * @param r1
	 * @return 0和1,其中0表示没有颜色,1有颜色
	 */
	private static int[][] getSlideTemplateData(int smallWidth, int smallHeight, int smallCircle, int r1) {
		// 拼图轮廓数据
		int[][] data = new int[smallWidth][smallHeight];

		//拼图去掉凹凸的白色距离
		int xBlank = smallWidth - smallCircle - smallCircleR1; // 不写smallCircleR1时,凹凸为半圆
		int yBlank = smallHeight - smallCircle - smallCircleR1;

		// 圆的位置
		int rxa = xBlank / 2;
		int ryb = smallHeight - smallCircle;
		double rPow = Math.pow(smallCircle, 2);

		/**
		 * 计算需要的拼图轮廓(方块和凹凸),用二维数组来表示,二维数组有两张值,0和1,其中0表示没有颜色,1有颜色
		 * 圆的标准方程 (x-a)²+(y-b)²=r²,标识圆心(a,b),半径为r的圆
		 */
		for (int i = 0; i < smallWidth; i++) {
			for (int j = 0; j < smallHeight; j++) {
				// 圆在拼图下方内
				double topR = Math.pow(i - rxa, 2) + Math.pow(j - 2, 2);
				// 圆在拼图下方外
				double downR = Math.pow(i - rxa, 2) + Math.pow(j - ryb, 2);
				// 圆在拼图左侧内 || (i <= xBlank && leftR <= rPow)
				//double leftR = Math.pow(i - 2, 2) + Math.pow(j - rxa, 2);
				// 圆在拼图右侧外
				double rightR = Math.pow(i - ryb, 2) + Math.pow(j - rxa, 2);
				if ((j <= yBlank && topR <= rPow) || (j >= yBlank && downR >= rPow)
						|| (i >= xBlank && rightR >= rPow)) {
					data[i][j] = 0;
				} else {
					data[i][j] = 1;
				}
			}
		}
		return data;
	}


	/**
	 * 裁剪拼图
	 * @param bigImage - 原图规范大小之后的大图
	 * @param smallImage - 小图
	 * @param slideTemplateData - 拼图轮廓数据
	 * @param x - 坐标x
	 * @param y - 坐标y
	 */
	private static void cutByTemplate(BufferedImage bigImage, BufferedImage smallImage, int[][] slideTemplateData, int x, int y) {
		int[][] martrix = new int[3][3];
		int[] values = new int[9];
		//拼图去掉凹凸的白色距离
		int xBlank = smallWidth - smallCircle - smallCircleR1; // 不写smallCircleR1时,凹凸为半圆
		int yBlank = smallHeight - smallCircle - smallCircleR1;

		// 创建shape区域,即原图抠图区域模糊和抠出小图
		/**
		 * 遍历小图轮廓数据,创建shape区域。即原图抠图处模糊和抠出小图
		 */
		for (int i = 0; i < smallImage.getWidth(); i++) {
			for (int j = 0; j < smallImage.getHeight(); j++) {
				// 获取大图中对应位置变色
				//logger.info("随机生成的坐标:(X,Y)=({},{}),(i,j=({},{}),获取原图大小:{} x {}", x, y, i, j, x + i, y + j);
				int rgb_ori = bigImage.getRGB(x + i, y + j);

				//0和1,其中0表示没有颜色,1有颜色
				int rgb = slideTemplateData[i][j];
				if (rgb == 1) {
					// 设置小图中对应位置变色
					smallImage.setRGB(i, j, rgb_ori);

					// 大图抠图区域高斯模糊
					readPixel(bigImage, x + i, y + j, values);
					fillMatrix(martrix, values);
					bigImage.setRGB(x + i, y + j, avgMatrix(martrix));

					//边框颜色
					Color white = new Color(230,230,230);
					Color black = new Color(20,20,20);
					//左侧边界,加重高亮阴暗
					if (j < yBlank) {
						bigImage.setRGB(x, y + j, black.getRGB());
						//smallImage.setRGB(0, j, white.getRGB());
					}
				} else {
					// 这里把背景设为透明
					smallImage.setRGB(i, j, rgb_ori & 0x00ffffff);
				}
			}
		}
	}

	/**
	 * 图片转BASE64
	 *
	 * @param image
	 * @return
	 * @throws IOException
	 */
	public static String getImageBASE64(BufferedImage image) throws IOException {
		byte[] imagedata = null;
		ByteArrayOutputStream bao = new ByteArrayOutputStream();
		ImageIO.write(image, "png", bao);
		imagedata = bao.toByteArray();
		String BASE64IMAGE = Base64.getEncoder().encodeToString(imagedata);
		return BASE64IMAGE;
	}

	/**
	 * 改变图片大小
	 *
	 * @param image
	 *            原图
	 * @param width
	 *            目标宽度
	 * @param height
	 *            目标高度
	 * @return 目标图
	 */
	public static BufferedImage resizeImage(final Image image, int width, int height, boolean type) {
		BufferedImage bufferedImage;
		if (type) {
			bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		} else {
			bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
		}

		final Graphics2D graphics2D = bufferedImage.createGraphics();
		graphics2D.setComposite(AlphaComposite.Src);
		// below three lines are for RenderingHints for better image quality at cost of
		// higher processing time
		graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
		graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		graphics2D.drawImage(image, 0, 0, width, height, null);
		graphics2D.dispose();
		return bufferedImage;
	}


	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 i = 0; i < matrix.length; i++) {
			int[] x = matrix[i];
			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 i = 0; i < matrix.length; i++) {
			int[] x = matrix[i];
			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();
	}

}

二、Java实现滑块拼图验证码校验

1、校验思路

Java实现滑块拼图验证码校验思路:

1、后端-获取滑块拼图验证码接口

  1. 在若干原图中随机一张原图,生成滑块拼图验证码信息。
  2. 随机一个token,把(X,Y)坐标值保存到 Redis中。
  3. 返回前端,两个Base64字符串图片信息,Y坐标值和 token。

2、后端-校验滑块拼图验证码接口

  1. 获取前端传入的 moveX坐标值和 token。
  2. 通过token,获取 Redis中的 (X,Y)坐标值,注意有效期。
  3. 校验 moveX与X的差的绝对值是否在阈值误差范围内(比如阈值误差为5)。
  4. 在阈值误差范围内,返回前端验证通过,否则返回前端验证不通过。

3、 前端

  1. 调用获取滑块拼图验证码接口,展示图片信息。
  2. 滑动拼图结束后,调用校验滑块拼图验证码接口。

2、Java接口代码

简单实现代码如下:

	/**
	 * 生成滑块拼图验证码
	 * 
	 * @param index
	 *            - 本地照片
	 */
	@RequestMapping(value = "/getImageCode.json", method = RequestMethod.GET)
	@ResponseBody
	public SliderPuzzleInfo getImageCode(int index, HttpServletRequest request) throws Exception {
		int i = index <= 0 ? 1 : index;
		File file = new File("D:/TempFiles/slide/slidebase" + i + ".png");
		SliderPuzzleInfo sliderPuzzleInfo = SlidePuzzleUtil.createImage(new FileInputStream(file));
		if (sliderPuzzleInfo == null) {
			System.out.println("图片验证码生成失败");
			return sliderPuzzleInfo;
		}

		File file1 = new File("D:/TempFiles/slide/demo2BigImage.png");
		File file2 = new File("D:/TempFiles/slide/demo2SmallImage.png");
		ImageIO.write(sliderPuzzleInfo.getBigImage(), "png", file1);
		ImageIO.write(sliderPuzzleInfo.getSmallImage(), "png", file2);

		HttpSession session = request.getSession();
		// 保存到Redis,这里临时存
		session.setAttribute("posX", sliderPuzzleInfo.getPosX());
		sliderPuzzleInfo.setBigImage(null);
		sliderPuzzleInfo.setSmallImage(null);
		return sliderPuzzleInfo;
	}

	/**
	 * 校验滑块拼图验证码
	 *
	 * @param movePosX
	 *            移动距离
	 */
	@ResponseBody
	@RequestMapping(value = "/verifyImageCode.json", method = RequestMethod.GET)
	public Map<String, Object> verifyImageCode(@RequestParam(value = "movePosX") Integer movePosX, HttpServletRequest request) {
		Map<String, Object> resultMap = new HashMap<>();
		HttpSession session = request.getSession();
		try {
			if (movePosX == null) {
				resultMap.put("errcode", 1);
				resultMap.put("errmsg", "参数缺失");
				return resultMap;
			}
			Integer posX = (Integer) session.getAttribute("posX");
			if (posX == null) {
				resultMap.put("errcode", 1);
				resultMap.put("errmsg", "验证过期,请重试");
				return resultMap;
			}
			if (Math.abs(posX - movePosX) > 5) {
				resultMap.put("errcode", 1);
				resultMap.put("errmsg", "验证不通过");
			} else {
				resultMap.put("errcode", 0);
				resultMap.put("errmsg", "验证通过");
			}
		} catch (Exception e) {
			throw new RuntimeException(e.getMessage());
		} finally {
			session.removeAttribute("posX");
		}
		return resultMap;
	}

Java实现滑块拼图验证码校验_第1张图片

– 求知若饥,虚心若愚。

你可能感兴趣的:(Common,Java实现滑块拼图验证码校验)