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

本文详细介绍了如何使用Java生成滑块拼图验证码,包括生成思路、核心代码及校验方法。首先,通过随机选择原图、设定坐标、创建小图并处理拼图轮廓来生成验证码;然后,提供了校验滑块拼图验证码的接口实现,包括验证坐标差值和清理会话。
摘要由CSDN通过智能技术生成

最近有个需求,需要添加滑块拼图验证码,网上了解了一些生成校验方式,下面写个 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. 创建一个JPanel作为验证码的容器,设置布局为BorderLayout。 2. 在JPanel的中央区域添加一个JPanel,作为滑块验证码的背景区域,设置布局为FlowLayout。 3. 在背景区域添加一个JLabel,用于显示验证码的文字。 4. 在背景区域添加一个JPanel,作为滑块区域,设置布局为FlowLayout。 5. 在滑块区域添加一个JLabel,用于显示滑块图片。 6. 在滑块区域添加一个JPanel,作为滑块拖动区域,设置布局为FlowLayout。 7. 在滑块拖动区域添加一个JLabel,用于显示滑块拖动图片。 8. 在滑块区域添加鼠标监听器,实现滑块的拖动。 9. 在JPanel中添加一个按钮,用于重置验证码。 10. 将验证码的字符串和拖动距离等信息保存到Session中,方便后续验证。 示例代码如下: ```java import java.awt.*; import java.awt.event.*; import javax.swing.*; public class SliderCaptcha extends JPanel { private String captchaText; private int dragDistance; public SliderCaptcha() { setLayout(new BorderLayout()); // 创建背景区域 JPanel backgroundPanel = new JPanel(new FlowLayout()); add(backgroundPanel, BorderLayout.CENTER); // 创建验证码文字 captchaText = "ABCD"; JLabel captchaLabel = new JLabel(captchaText); captchaLabel.setFont(new Font("Arial", Font.BOLD, 24)); backgroundPanel.add(captchaLabel); // 创建滑块区域 JPanel sliderPanel = new JPanel(new FlowLayout()); backgroundPanel.add(sliderPanel); // 创建滑块图片 ImageIcon sliderIcon = new ImageIcon("slider.png"); JLabel sliderLabel = new JLabel(sliderIcon); sliderPanel.add(sliderLabel); // 创建滑块拖动区域 JPanel dragPanel = new JPanel(new FlowLayout()); dragPanel.setPreferredSize(new Dimension(sliderIcon.getIconWidth(), sliderIcon.getIconHeight())); sliderPanel.add(dragPanel); // 创建滑块拖动图片 ImageIcon dragIcon = new ImageIcon("drag.png"); JLabel dragLabel = new JLabel(dragIcon); dragPanel.add(dragLabel); // 添加鼠标监听器,实现滑块的拖动 sliderPanel.addMouseListener(new MouseAdapter() { private Point dragStartPoint; public void mousePressed(MouseEvent e) { dragStartPoint = e.getPoint(); } public void mouseReleased(MouseEvent e) { int x = e.getX() + sliderPanel.getX() - dragStartPoint.x; if (x > dragDistance) { JOptionPane.showMessageDialog(null, "验证成功!"); } else { JOptionPane.showMessageDialog(null, "验证失败,请重试!"); } } }); // 创建重置按钮 JButton resetButton = new JButton("重置"); resetButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { captchaText = "ABCD"; dragDistance = (int) (Math.random() * 100 + 50); dragLabel.setLocation(dragDistance, dragLabel.getY()); } }); add(resetButton, BorderLayout.SOUTH); // 初始化滑块拖动距离 dragDistance = (int) (Math.random() * 100 + 50); dragLabel.setLocation(dragDistance, dragLabel.getY()); } public static void main(String[] args) { JFrame frame = new JFrame("滑块验证码"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(400, 200); frame.setLocationRelativeTo(null); frame.setContentPane(new SliderCaptcha()); frame.setVisible(true); } } ```
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值