生成验证码工具类

public class VerifyCodeUtils {

    /**
     * 入参校验设置默认值
     **/
    public static void checkCaptcha(Captcha captcha) {
        //设置画布宽度默认值
        if (captcha.getCanvasWidth() == null) {
            captcha.setCanvasWidth(320);
        }
        //设置画布高度默认值
        if (captcha.getCanvasHeight() == null) {
            captcha.setCanvasHeight(50);
        }
        //设置阻塞块宽度默认值
        if (captcha.getBlockWidth() == null) {
            captcha.setBlockWidth(40);
        }
        //设置阻塞块高度默认值
        if (captcha.getBlockHeight() == null) {
            captcha.setBlockHeight(50);
        }
        //设置阻塞块凹凸半径默认值
        if (captcha.getBlockRadius() == null) {
            captcha.setBlockRadius(-1);
        }
    }
    
    /**
     * 获取指定范围内的随机数
     **/
    public static int getNonceByRange2(int canvasWidth,int i) {
        return new BigDecimal(i).divide(new BigDecimal(100)).multiply(new BigDecimal(canvasWidth)).intValue();
    }
    
    /**
     * 获取指定范围内的随机数
     **/
    public static int getNonceByRange(int start, int end) {
        Random random = new Random();
        return random.nextInt(Math.abs(end - start + 1)) + start;
    }
    
    /**
     * 获取验证码资源图
     **/
    public static BufferedImage getBufferedImage(InputStream inputStream) {
        try {
            return ImageIO.read(inputStream);
        } catch (Exception e) {
            System.out.println("获取拼图资源失败");
            //异常处理
            return null;
        }
    }
    
    /**
     * 调整图片大小
     **/
    public static BufferedImage imageResize(BufferedImage bufferedImage, int width, int height) {
        Image image = bufferedImage.getScaledInstance(width, height, Image.SCALE_SMOOTH);
        BufferedImage resultImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D graphics2D = resultImage.createGraphics();
        graphics2D.drawImage(image, 0, 0, null);
        graphics2D.dispose();
        return resultImage;
    }
    
    /**
     * 抠图,并生成阻塞块
     **/
    public static void cutByTemplate(BufferedImage canvasImage, BufferedImage blockImage, int blockWidth, int blockHeight, int blockRadius, int blockX, int blockY) {
        BufferedImage waterImage = new BufferedImage(blockWidth, blockHeight, BufferedImage.TYPE_4BYTE_ABGR);
        //阻塞块的轮廓图
        int[][] blockData = getBlockData(blockWidth, blockHeight, blockRadius);
        //创建阻塞块具体形状
        for (int i = 0; i < blockWidth; i++) {
            for (int j = 0; j < blockHeight; j++) {
                try {
                    //原图中对应位置变色处理
                    if (blockData[i][j] == 1) {
                        //背景设置为绿色
                        waterImage.setRGB(i, j, new Color(0,255,0).getRGB());
                        blockImage.setRGB(i, j, canvasImage.getRGB(blockX + i, blockY + j));
                        //轮廓设置为白色,取带像素和无像素的界点,判断该点是不是临界轮廓点
                        if (blockData[i + 1][j] == 0 || blockData[i][j + 1] == 0 || blockData[i - 1][j] == 0 || blockData[i][j - 1] == 0) {
                            blockImage.setRGB(i, j, Color.WHITE.getRGB());
                            waterImage.setRGB(i, j, Color.WHITE.getRGB());
                        }
                    }
                    //这里把背景设为透明
                    else {
                        blockImage.setRGB(i, j, Color.TRANSLUCENT);
                        waterImage.setRGB(i, j, Color.TRANSLUCENT);
                    }
                } catch (ArrayIndexOutOfBoundsException e) {
                    //防止数组下标越界异常
                }
            }
        }
        //在画布上添加阻塞块水印
        addBlockWatermark(canvasImage, waterImage, blockX, blockY);
    }
    
    /**
     * 构建拼图轮廓轨迹
     **/
    private static int[][] getBlockData(int blockWidth, int blockHeight, int blockRadius) {
        int[][] data = new int[blockWidth][blockHeight];
        double po = Math.pow(blockRadius, 2);
        //随机生成两个圆的坐标,在4个方向上 随机找到2个方向添加凸/凹
        //凸/凹1
        int face1 = ThreadLocalRandom.current().nextInt(0,4);
        //凸/凹2
        int face2;
        //保证两个凸/凹不在同一位置
        do {
            face2 = ThreadLocalRandom.current().nextInt(0,4);
        } while (face1 == face2);
        //获取凸/凹起位置坐标
        int[] circle1 = getCircleCoords(face1, blockWidth, blockHeight, blockRadius);
        int[] circle2 = getCircleCoords(face2, blockWidth, blockHeight, blockRadius);
        //随机凸/凹类型
        int shape = getNonceByRange(0, 1);
        //圆的标准方程 (x-a)²+(y-b)²=r²,标识圆心(a,b),半径为r的圆
        //计算需要的小图轮廓,用二维数组来表示,二维数组有两张值,0和1,其中0表示没有颜色,1有颜色
        for (int i = 0; i < blockWidth; i++) {
            for (int j = 0; j < blockHeight; j++) {
                data[i][j] = 0;
                //创建中间的方形区域
                if ((i >= blockRadius && i <= blockWidth - blockRadius && j >= blockRadius && j <= blockHeight - blockRadius)) {
                    data[i][j] = 1;
                }
                double d1 = Math.pow(i - Objects.requireNonNull(circle1)[0], 2) + Math.pow(j - circle1[1], 2);
                double d2 = Math.pow(i - Objects.requireNonNull(circle2)[0], 2) + Math.pow(j - circle2[1], 2);
                //创建两个凸/凹  不需要
               /* if (d1 <= po || d2 <= po) {
                    data[i][j] = shape;
                }*/
            }
        }
        return data;
    }
    /**
     * 根据朝向获取圆心坐标
     */
    private static int[] getCircleCoords(int face, int blockWidth, int blockHeight, int blockRadius) {
        //上
        if (0 == face) {
            return new int[]{blockWidth / 2 - 1, blockRadius};
        }
        //左
        else if (1 == face) {
            return new int[]{blockRadius, blockHeight / 2 - 1};
        }
        //下
        else if (2 == face) {
            return new int[]{blockWidth / 2 - 1, blockHeight - blockRadius - 1};
        }
        //右
        else if (3 == face) {
            return new int[]{blockWidth - blockRadius - 1, blockHeight / 2 - 1};
        }
        return null;
    }
    /**
     * 在画布上添加阻塞块水印
     */
    private static void addBlockWatermark(BufferedImage canvasImage, BufferedImage blockImage, int x, int y) {
        Graphics2D graphics2D = canvasImage.createGraphics();
        graphics2D.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.8f));
        graphics2D.drawImage(blockImage, x, y, null);
        graphics2D.dispose();
    }
    /**
     * BufferedImage转BASE64
     */
    public static String toBase64(BufferedImage bufferedImage, String type) {
        try {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ImageIO.write(bufferedImage, type, byteArrayOutputStream);
            String base64 = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
            return String.format("data:image/%s;base64,%s", type, base64);
        } catch (IOException e) {
            System.out.println("图片资源转换BASE64失败");
            //异常处理
            return null;
        }
    } 
}

生成滑块验证码接口

 @ApiOperation(value="请求生成滑块验证信息", httpMethod="POST")
    @ApiImplicitParams({
            @ApiImplicitParam(name="cw",value="画布宽 默认320"),
            @ApiImplicitParam(name="ch",value="画布搞 默认50"),
            @ApiImplicitParam(name="bw",value="阻塞块宽 默认40"),
            @ApiImplicitParam(name="bh",value="阻塞块高 默认50"),
            @ApiImplicitParam(name="br",value="阻塞块凹凸半径 默认0"),
            @ApiImplicitParam(name="aliveMinutes",value="存活分钟 默认30分钟",required=false)
    })
    @RequestMapping(value="/slider/generate", method= RequestMethod.POST)
    public ApiResultDTO<Object> generateSliderVerifyCode(
            @RequestParam(name="cw",required=false)Integer cw,
            @RequestParam(name="ch",required=false)Integer ch,
            @RequestParam(name="bw",required=false)Integer bw,
            @RequestParam(name="bh",required=false)Integer bh,
            @RequestParam(name="br",required=false)Integer br,
            @RequestParam(name="aliveMinutes",required=false)Integer aliveMinutes,
            HttpServletRequest request
    ) {
        return RestAPITemplate.restapi(()->{
            Captcha captcha = new Captcha(cw,ch,bw,bh,br);
            return verifyCodeService.generateSliderCaptcha(captcha,aliveMinutes);
        });
    }

生成滑块验证码具体实现

 public Object generateSliderCaptcha(Captcha captcha,Integer aliveMinutes) {
        //参数校验
        VerifyCodeUtils.checkCaptcha(captcha);
        //获取画布的宽高
        int canvasWidth = captcha.getCanvasWidth();
        int canvasHeight = captcha.getCanvasHeight();
        //获取阻塞块的宽高/半径
        int blockWidth = captcha.getBlockWidth();
        int blockHeight = captcha.getBlockHeight();
        int blockRadius = captcha.getBlockRadius();
        //获取资源图
        BufferedImage canvasImage = VerifyCodeUtils.getBufferedImage(this.getClass().getClassLoader().getResourceAsStream("img/slider.jpg"));
        //调整原图到指定大小
        canvasImage = VerifyCodeUtils.imageResize(canvasImage, canvasWidth, canvasHeight);
        //随机生成阻塞块坐标 随机数/100*画布宽度
        Random random = new Random();
        int randomNum = random.nextInt(100);
        if(randomNum<40) randomNum=40;
        else if(randomNum>80) randomNum=80;
        int blockX = VerifyCodeUtils.getNonceByRange2(canvasWidth,randomNum);
        int blockY = VerifyCodeUtils.getNonceByRange(0, canvasHeight - blockHeight + 1);
        //阻塞块
        BufferedImage blockImage = new BufferedImage(blockWidth, blockHeight, BufferedImage.TYPE_4BYTE_ABGR);
        //新建的图像根据轮廓图颜色赋值,源图生成遮罩
        VerifyCodeUtils.cutByTemplate(canvasImage, blockImage, blockWidth, blockHeight, blockRadius, blockX, blockY);
        //创建 verifyCodeKey
        String verifyCodeKey;
        try {
          //业务逻辑
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("生成图片验证码失败");
        }
        //设置返回参数
        captcha.setVerifyCodeKey(verifyCodeKey);
        captcha.setBlockSrc(VerifyCodeUtils.toBase64(blockImage, "png"));
        captcha.setCanvasSrc(VerifyCodeUtils.toBase64(canvasImage, "png"));
        return captcha;
    }

滑块模型

public class Captcha {

    /**
     * 验证码
     **/
    private String verifyCodeKey;
    /**
     * 生成的画布的base64
     **/
    private String canvasSrc;
    /**
     * 画布宽度
     **/
    private Integer canvasWidth;
    /**
     * 画布高度
     **/
    private Integer canvasHeight;
    /**
     * 生成的阻塞块的base64
     **/
    private String blockSrc;
    /**
     * 阻塞块宽度
     **/
    private Integer blockWidth;
    /**
     * 阻塞块高度
     **/
    private Integer blockHeight;
    /**
     * 阻塞块凸凹半径
     **/
    private Integer blockRadius;


    public Captcha() {
    }
    public Captcha(Integer canvasWidth, Integer canvasHeight, Integer blockWidth, Integer blockHeight, Integer blockRadius) {
        this.canvasWidth = canvasWidth;
        this.canvasHeight = canvasHeight;
        this.blockWidth = blockWidth;
        this.blockHeight = blockHeight;
        this.blockRadius = blockRadius;
    }
    public String getVerifyCodeKey() {
        return verifyCodeKey;
    }

    public void setVerifyCodeKey(String nonceStr) {
        this.verifyCodeKey = nonceStr;
    }

    public String getCanvasSrc() {
        return canvasSrc;
    }

    public void setCanvasSrc(String canvasSrc) {
        this.canvasSrc = canvasSrc;
    }

    public Integer getCanvasWidth() {
        return canvasWidth;
    }

    public void setCanvasWidth(Integer canvasWidth) {
        this.canvasWidth = canvasWidth;
    }

    public Integer getCanvasHeight() {
        return canvasHeight;
    }

    public void setCanvasHeight(Integer canvasHeight) {
        this.canvasHeight = canvasHeight;
    }

    public String getBlockSrc() {
        return blockSrc;
    }

    public void setBlockSrc(String blockSrc) {
        this.blockSrc = blockSrc;
    }

    public Integer getBlockWidth() {
        return blockWidth;
    }

    public void setBlockWidth(Integer blockWidth) {
        this.blockWidth = blockWidth;
    }

    public Integer getBlockHeight() {
        return blockHeight;
    }

    public void setBlockHeight(Integer blockHeight) {
        this.blockHeight = blockHeight;
    }

    public Integer getBlockRadius() {
        return blockRadius;
    }

    public void setBlockRadius(Integer blockRadius) {
        this.blockRadius = blockRadius;
    }

    //获取阻塞块宽度占比值
    public int getBlokWidthProp(){
        if(this.blockWidth==null) this.blockWidth = 50;
        if(this.canvasWidth==null)this.canvasWidth = 320;
        return (this.blockWidth*100)/this.canvasWidth;
    }


}

验证滑块验证码接口

@ApiOperation(value="验证滑块验证码", httpMethod="POST")
    @ApiImplicitParams({
            @ApiImplicitParam(name="verifyCodeKey", value="验证码标识"),
            @ApiImplicitParam(name="verifyCode", value="输入的验证码")
    })
    @RequestMapping(value="/slider/verify", method= RequestMethod.POST)
    public ApiResultDTO<String> verifySliderVerifyCode(
            @RequestParam("verifyCodeKey")String verifyCodeKey,
            @RequestParam("verifyCode")String verifyCode,
            HttpServletRequest request
    ) {
        return RestAPITemplate.restapi(()->{
            verifyCodeService.checkSliderVerifyCode(verifyCodeKey, verifyCode);
            return null;
        });
    }
//校验滑块验证码
    public String checkSliderVerifyCode(String id, String code) {
        if(StringUtils.isBlank(code) || StringUtils.isBlank(id)) return "验证码滑块请滑动到指定区域";

        VerifyCode verifyCode = this.verifyCodeRepository.getVerifyCodeById(id);
        if(verifyCode==null) return "验证已过期";

        String errorMsg=null;
        if(!VerifyCode.FROMTYPE_SLIDER_CODE.equals(verifyCode.getFromType())) errorMsg="非法生成的验证码";
        if(verifyCode.isExpired(null)) errorMsg="验证已过期";
        Integer vCode = Integer.valueOf(verifyCode.getVerifyCode());//生成阻塞块的位置
        Integer inputCode = Integer.valueOf(code);//用户滑动到阻塞块的位置
        Captcha captcha = JsonConverter.jsonStrToObject(verifyCode.getAttr("captcha"),Captcha.class);
        int blokWidthPropHalf = (int)(captcha.getBlokWidthProp() * 0.4);//必須划到60%算成功
        if(inputCode>vCode || inputCode<vCode-blokWidthPropHalf){
            errorMsg="验证失败,请拖动到绿色区域内";
        }

        if(StringUtils.isNotBlank(errorMsg)){
            verifyCode.fail();
            if(verifyCode.obtainFailCount()>2 && verifyCode.obtainFailCount()<10){
                this.verifyCodeRepository.deleteVerifyCode(verifyCode);//连续失败超过3次删掉
                return errorMsg;
            }
            this.verifyCodeRepository.createVerifyCode(verifyCode);
        }else{
            if(verifyCode.obtainFailCount()>12){
                this.verifyCodeRepository.deleteVerifyCode(verifyCode);//连续成功超过3次删掉
                return errorMsg;
            }
            verifyCode.sucess();
            this.verifyCodeRepository.createVerifyCode(verifyCode);
        }
        return errorMsg;
    }
Logo

网易易盾是国内领先的数字内容风控服务商,依托网易二十余年的先进技术和一线实践经验沉淀,为客户提供专业可靠的安全服务,涵盖内容安全、业务安全、应用安全、安全专家服务四大领域,全方位保障客户业务合规、稳健和安全运营。

更多推荐