• springboot实现滑动验证码(redis+token+base64)


    实现

    graph LR A[前端发起验证码图片请求] --> B[后端接收到请求,对图片进行处理,生成一个随机token] C[后端将处理后的图片坐标和token存入redis,再将处理后的图片和token返回给前端] --> D[前端接收到用户处理用户滑动验证码后的坐标,返回给后端] --> E[后端从redis中读取出坐标,和前端回传的坐标进行比较,返回相应结果]

    代码实现

    图片处理工具类

    package com.example.demo.util;
    
    import org.springframework.stereotype.Component;
    import sun.misc.BASE64Decoder;
    import sun.misc.BASE64Encoder;
    
    import javax.imageio.ImageIO;
    import java.awt.*;
    import java.awt.image.BufferedImage;
    import java.io.*;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Random;
    
    @Component
    public class VerifyImageUtil {
    
        /**
         * 源文件宽度
         */
        private static int ORI_WIDTH = 300;
        /**
         * 源文件高度
         */
        private static int ORI_HEIGHT = 150;
        /**
         * 模板图宽度
         */
        private static int CUT_WIDTH = 50;
        /**
         * 模板图高度
         */
        private static int CUT_HEIGHT = 50;
        /**
         * 抠图凸起圆心
         */
        private static int circleR = 5;
        /**
         * 抠图内部矩形填充大小
         */
        private static int RECTANGLE_PADDING = 8;
        /**
         * 抠图的边框宽度
         */
        private static int SLIDER_IMG_OUT_PADDING = 1;
    
    
        /**
         * 根据传入的路径生成指定验证码图片
         *
         * @param filePath
         * @return
         * @throws IOException
         */
        public  VerifyImage getVerifyImage(String filePath) throws IOException {
            BufferedImage srcImage = ImageIO.read(new File(filePath));
            int locationX = CUT_WIDTH + new Random().nextInt(srcImage.getWidth() - CUT_WIDTH * 3);
            int locationY = CUT_HEIGHT + new Random().nextInt(srcImage.getHeight() - CUT_HEIGHT) / 2;
            BufferedImage markImage = new BufferedImage(CUT_WIDTH,CUT_HEIGHT,BufferedImage.TYPE_4BYTE_ABGR);
            int[][] data = getBlockData();
            cutImgByTemplate(srcImage, markImage, data, locationX, locationY);
            return new VerifyImage(getImageBASE64(srcImage),getImageBASE64(markImage),locationX,locationY);
        }
    
    
        /**
         * 生成随机滑块形状
         * <p>
         * 0 透明像素
         * 1 滑块像素
         * 2 阴影像素
         * @return int[][]
         */
        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;
        }
    
        /**
         * 裁剪区块
         * 根据生成的滑块形状,对原图和裁剪块进行变色处理
         * @param oriImage    原图
         * @param targetImage 裁剪图
         * @param blockImage  滑块
         * @param x           裁剪点x
         * @param y           裁剪点y
         */
        private static void cutImgByTemplate(BufferedImage oriImage, BufferedImage targetImage, int[][] blockImage, int x, int y) {
            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());
                    } 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);
                    }
                }
    
            }
        }
    
    
        /**
         * 随机获取一张图片对象
         * @param path
         * @return
         * @throws IOException
         */
        public static BufferedImage getRandomImage(String path) throws IOException {
            File files = new File(path);
            File[] fileList = files.listFiles();
            List<String> fileNameList = new ArrayList<>();
            if (fileList!=null && fileList.length!=0){
                for (File tempFile:fileList){
                    if (tempFile.isFile() && tempFile.getName().endsWith(".jpg")){
                        fileNameList.add(tempFile.getAbsolutePath().trim());
                    }
                }
            }
            Random random = new Random();
            File imageFile = new File(fileNameList.get(random.nextInt(fileNameList.size())));
            return ImageIO.read(imageFile);
        }
    
        /**
         * 将IMG输出为文件
         * @param image
         * @param file
         * @throws Exception
         */
        public static void writeImg(BufferedImage image, String file) throws Exception {
            byte[] imagedata = null;
            ByteArrayOutputStream bao=new ByteArrayOutputStream();
            ImageIO.write(image,"png",bao);
            imagedata = bao.toByteArray();
            FileOutputStream out = new FileOutputStream(new File(file));
            out.write(imagedata);
            out.close();
        }
    
        /**
         * 将图片转换为BASE64
         * @param image
         * @return
         * @throws IOException
         */
        public static String getImageBASE64(BufferedImage image) throws IOException {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ImageIO.write(image,"png",out);
            //转成byte数组
            byte[] bytes = out.toByteArray();
            BASE64Encoder encoder = new BASE64Encoder();
            //生成BASE64编码
            return encoder.encode(bytes);
        }
    
        /**
         * 将BASE64字符串转换为图片
         * @param base64String
         * @return
         */
        public static BufferedImage base64StringToImage(String base64String) {
            try {
                BASE64Decoder decoder=new BASE64Decoder();
                byte[] bytes1 = decoder.decodeBuffer(base64String);
                ByteArrayInputStream bais = new ByteArrayInputStream(bytes1);
                return ImageIO.read(bais);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    
    

    图片属性类

    package com.example.demo.util;
    
    
    
    import lombok.Data;
    
    
    @Data
    public class VerifyImage{
        //    原图
        String srcImage;
        //    抠图后的图
        String cutImage;
        //    滑块坐标点
        Integer XPosition;
        Integer YPosition;
    
        public VerifyImage(String srcImage, String cutImage, Integer XPosition, Integer YPosition) {
            this.srcImage = srcImage;
            this.cutImage = cutImage;
            this.XPosition = XPosition;
            this.YPosition = YPosition;
        }
    
    
    }
    

    redis处理的业务类

    package com.example.demo.service;
    
    import com.example.demo.util.VerifyImage;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.core.ValueOperations;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    import org.springframework.stereotype.Service;
    
    import java.util.concurrent.TimeUnit;
    
    @Service
    public class RedisService {
    
        @Autowired
        RedisTemplate redisTemplate;
    
        public void set(String key, Object value) {
            //更改在redis里面查看key编码问题
            RedisSerializer redisSerializer = new StringRedisSerializer();
            redisTemplate.setKeySerializer(redisSerializer);
            ValueOperations<String, Object> vo = redisTemplate.opsForValue();
            vo.set(key, value);
        }
    
        //    为指定的key和value指定过期时间
        public void setex(String key, Object value, int seconds) {
            ValueOperations<String, Object> vo = redisTemplate.opsForValue();
            vo.set(key, value, seconds, TimeUnit.SECONDS);
        }
    
        public Object get(String key) {
            ValueOperations<String, Object> vo = redisTemplate.opsForValue();
            return vo.get(key);
        }
    
    
        public boolean delete(String key) {
            return redisTemplate.delete(key);
        }
    
    }
    
    

    图片处理和图片验证的业务类

    package com.example.demo.service;
    
    import com.example.demo.util.VerifyImage;
    import com.example.demo.util.VerifyImageUtil;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.core.io.Resource;
    import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
    import org.springframework.core.io.support.ResourcePatternResolver;
    import org.springframework.stereotype.Service;
    import org.springframework.util.StringUtils;
    
    import javax.servlet.http.HttpServletRequest;
    import java.io.IOException;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Random;
    import java.util.UUID;
    import java.util.logging.Logger;
    
    /**
     * @author lyd
     * @Description: 一个接口返回两个图片
     * @date 2021/6/1718:13
     */
    @Service
    public class VerifyServiceOneInterface {
    
    	Logger logger = Logger.getLogger("日志");
    
    	@Autowired
    	VerifyImageUtil verifyImageUtil;
    
    	@Autowired
    	RedisService redisService;
    
    	//    定义一个Ant模式通配符的Resource查找器,可以用来查找类路径下或者文件系统中的资源。
    	ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
    
    
    	public Object getImage() throws IOException {
    
    		// 生成token
    		String token = UUID.randomUUID().toString();
    		redisService.setex("AUTH:RAND:" + token, token, 60);
    		logger.info("token:" + token);
    
    		// 得到图片文件夹中所有路径
    		Resource[] resources = resolver.getResources("classpath*:/verifyImg/*");
    		int ranNum = new Random().nextInt(resources.length);
    
    		// 通过随机的图片路径得到验证码图片
    		VerifyImage verifyImage = verifyImageUtil.getVerifyImage(String.valueOf(resources[ranNum].getFile()));
    
    		// 将裁剪后的图片坐标存入redis中
    		redisService.set("getXPosition", verifyImage.getXPosition());
    		redisService.set("getYPosition", verifyImage.getYPosition());
    		logger.info("ImageX:" + verifyImage.getXPosition());
    		logger.info("ImageY:" + verifyImage.getYPosition());
    
    		Map<String, Object> resultMap = new HashMap<>();
    		resultMap.put("token", token);
    		// 去除base64中的特殊字符
    		String strSrcImage = verifyImage.getSrcImage().replaceAll("[\s*	
    
    ]", "");
    		String strCutImage = verifyImage.getCutImage().replaceAll("[\s*	
    
    ]", "");
    		resultMap.put("srcImage", strSrcImage);
    		resultMap.put("cutImage", strCutImage);
    
    		return resultMap;
    	}
    
    	/**
    	 * 得到图片验证的坐标,进行有效性校对
    	 *
    	 * @return
    	 */
    	public String verifyImage(String x, String y, HttpServletRequest httpServletRequest) {
    
    		// 得到客户端token
    		String token = httpServletRequest.getHeader("token");
    		// 得到redis中token
    		String redisToken = (String) redisService.get("AUTH:RAND:" + token);
    
    		if (redisToken == null) {
    			return "验证码过期";
    		}
    
    		if(StringUtils.isEmpty(x) ||StringUtils.isEmpty(y) ){
    			return "验证码为空";
    		}
    
    		Integer ImageX = (Integer) redisService.get("getXPosition");
    		Integer ImageY = (Integer) redisService.get("getYPosition");
    
           // 计算验证图片坐标的误差值
    		int absX = Math.abs(Integer.parseInt(x) - ImageX);
    		int absY = Math.abs(Integer.parseInt(y) - ImageY);
    
    		if (absX < 6 && absY < 6) {
    			return "验证码正确";
    		}
    
    		return "验证码错误";
    	}
    
    
    
    
    }
    

    实现一个controller类,负责和前端页面进行交互

    package com.example.demo.controller;
    
    import com.example.demo.service.VerifyService;
    import com.example.demo.service.VerifyServiceOneInterface;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * @author lyd
     * @Description:
     * @date 2021/6/1718:16
     */
    @RestController
    @RequestMapping("VerifyOneInterfaceController")
    public class VerifyOneInterfaceController {
    
    	@Autowired
    	VerifyServiceOneInterface verifyServiceOneInterface;
    
    	@RequestMapping("getImage")
    	@ResponseBody
    	public Object getImage() throws IOException {
    		return verifyServiceOneInterface.getImage();
    	}
    
    	@RequestMapping("verifyImage")
    	@ResponseBody
    	public String verifyImage(String x, String y, HttpServletRequest httpServletRequest) {
    		return verifyServiceOneInterface.verifyImage(x, y, httpServletRequest);
    	}
    
    
    }
    

    这里如果没有前端页面,使用postman或者浏览器进行测试的时候,记得要给heard中添加一个token值,token是在后台生成的

  • 相关阅读:
    编译Openmv固件&增加串口
    边缘 AI 平台的比较
    CVPR2021 | 重新思考BatchNorm中的Batch
    ICCV2021 |重新思考人群中的计数和定位:一个纯粹基于点的框架
    ICCV2021 | 重新思考视觉transformers的空间维度
    CVPR2021 | Transformer用于End-to-End视频实例分割
    漫谈CUDA优化
    AAAI 2021 最佳论文公布
    综述专栏 | 姿态估计综述
    为什么GEMM是深度学习的核心
  • 原文地址:https://www.cnblogs.com/lyd447113735/p/14898489.html
Copyright © 2020-2023  润新知