Java滑块验证码原理和实现


滑块验证码引入

当前互联网流行使用滑块验证码,如下图是网易严选的登录验证部分。
在这里插入图片描述

滑块验证码原理

很多网站使用滑块验证码提高网站安全性,为了做到真正的验证,必须要走后台服务器。
下面是java实现滑块验证的核心步骤:

  1. 从服务器随机取一张图片,并对图片上的随机x,y坐标和宽高一块区域抠图;
  2. 根据步骤一的坐标和宽高,使用二维数组保存原图上抠图区域的像素点坐标;
  3. 根据步骤二的坐标点,对原图的抠图区域的颜色进行处理。
  4. 完成以上步骤之后得到两张图(扣下来的方块图,带有抠图区域阴影的原图),将这两张图和抠图区域的y坐标传到前台,前端在移动方块验证时,将移动后的x坐标传递到后台与原来的x坐标作比较,如果在阈值内则验证通过。
  5. 请求验证的步骤:前台向后台发起请求,后台随机一张图片做处理将处理完的两张图片的base64,抠图y坐标和token(token为后台缓存验证码的唯一token,可以用缓存和分布式缓存)返回给前台。
  6. 前台滑动图片将x坐标和token作为参数请求后台验证,服务器根据token取出x坐标与参数的x进行比较。

滑块验证码实现

  1. 说明
    实现是基于springmvc的实现,前端为freemarker模板。
  2. 工具类
package com.sikong.ms.web.shiro;


import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import lombok.extern.slf4j.Slf4j;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * @Auther herb
 * @Description 本地缓存:用户图形验证码资源
 * @Date: create in ${Time} ${Date}
 * @Modified By: herb
 */
@Slf4j
public class ValidateCache {

    private static LoadingCache<String,List<byte[]>> cache = CacheBuilder.newBuilder()
            .maximumSize(2)
            .expireAfterWrite(365, TimeUnit.DAYS)
            .build(createCacheLoader());

    private static CacheLoader<String, List<byte[]>> createCacheLoader() {
        return new CacheLoader<String, List<byte[]>>(){
            @Override
            public List<byte[]> load(String key) throws Exception {
                return null;
            }
        };
    }

    public static void set(String key,List<byte[]> value){
        cache.put(key,value);
    }

    public static List<byte[]> get(String key){
        List<byte[]> value = null;
        try {
            value = cache.get(key);
        } catch (Exception e) {
            log.info("ValidateCache 缓存未查询到图片或模板!");
            value = null;
        }
        return value;
    }
    public static long size(){
        return cache.size();
    }

    public static void main(String[] args) throws UnsupportedEncodingException {
        List<byte[]> value = new ArrayList<>();
        value.add("ok".getBytes("utf-8"));
        ValidateCache.set("121", value);
        ValidateCache.set("111",value);
        ValidateCache.set("12",value);
        ValidateCache.set("113",value);
        System.out.println(cache.size());
        //注意缓存的最大容量
        List<byte[]> rv = ValidateCache.get("113");
        System.out.println(new String(rv.get(0),"utf-8"));
        System.out.println(ValidateCache.size());
        while (cache.size() > 0){
            try {
                Thread.currentThread().sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            List<byte[]> rv1 = ValidateCache.get("113");
            System.out.println(new String(rv1.get(0),"utf-8"));
        }

    }
}


package com.sikong.ms.common.captcha.validate;

import lombok.extern.slf4j.Slf4j;

import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.text.NumberFormat;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;

/**
 * 滑块验证工具类
 *
 * @author: herb
 * @Description:
 * @Date: Created in 10:57 2018/6/25
 * @Modified By:
 */
@Slf4j
public class VerifyImageUtil {

    private static int ORI_WIDTH = 350;  //源文件宽度
    private static int ORI_HEIGHT = 213;  //源文件高度
    private static int X;  //抠图坐标x
    private static int Y;  //抠图坐标y
    private static int WIDTH;  //模板图宽度
    private static int HEIGHT;  //模板图高度
    private static float xPercent;  //X位置移动百分比
    private static float yPercent;  //Y位置移动百分比

    public static int getX() {
        return X;
    }

    public static int getY() {
        return Y;
    }

    public static float getxPercent() {
        return xPercent;
    }

    public static float getyPercent() {
        return yPercent;
    }
    /**
     * 根据模板切图
     *
     * @param templateFile
     * @param targetFile
     * @param templateType
     * @param targetType
     * @return
     * @throws Exception
     */
    public static Map<String, Object> pictureTemplatesCut(byte[] templateFile, byte[] targetFile, String templateType, String targetType) throws Exception {
        Map<String, Object> pictureMap = new HashMap<>();
        // 文件类型
        String templateFiletype = templateType;
        String targetFiletype = targetType;
        if (StringUtils.isEmpty(templateFiletype) || StringUtils.isEmpty(targetFiletype)) {
            throw new RuntimeException("file type is empty");
        }
        // 源文件流
        //File Orifile = targetFile;
        //InputStream oriis = new FileInputStream(Orifile);

        // 模板图
        BufferedImage imageTemplate = ImageIO.read(new ByteArrayInputStream(templateFile));
        WIDTH = imageTemplate.getWidth();
        HEIGHT = imageTemplate.getHeight();
        // 模板图
        BufferedImage imageTarget = ImageIO.read(new ByteArrayInputStream(targetFile));
        ORI_WIDTH = imageTarget.getWidth();
        ORI_HEIGHT = imageTarget.getHeight();

        generateCutoutCoordinates();
        // 最终图像
        BufferedImage newImage = new BufferedImage(WIDTH, HEIGHT, imageTemplate.getType());
        Graphics2D graphics = newImage.createGraphics();
        graphics.setBackground(Color.white);

        int bold = 5;
        // 获取感兴趣的目标区域
        BufferedImage targetImageNoDeal = getTargetArea(X, Y, WIDTH, HEIGHT, new ByteArrayInputStream(targetFile), targetFiletype);


        // 根据模板图片抠图
        newImage = DealCutPictureByTemplate(targetImageNoDeal, imageTemplate, newImage);

        // 设置“抗锯齿”的属性
        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        graphics.setStroke(new BasicStroke(bold, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
        graphics.drawImage(newImage, 0, 0, null);
        graphics.dispose();

        ByteArrayOutputStream os = new ByteArrayOutputStream();//新建流。
        ImageIO.write(newImage, templateFiletype, os);//利用ImageIO类提供的write方法,将bi以png图片的数据模式写入流。
        byte[] newImages = os.toByteArray();
        pictureMap.put("newImage", newImages);
        // 源图生成遮罩
        BufferedImage oriImage = ImageIO.read(new ByteArrayInputStream(targetFile));
        byte[] oriCopyImages = DealOriPictureByTemplate(oriImage, imageTemplate, X, Y);
        pictureMap.put("oriCopyImage", oriCopyImages);
        pictureMap.put("X",X);
        pictureMap.put("Y",Y);
        return pictureMap;
    }

    /**
     * 根据模板切图
     *
     * @param templateFile
     * @param targetFile
     * @param templateType
     * @param targetType
     * @return
     * @throws Exception
     */
    public static Map<String, Object> pictureTemplatesCut(File templateFile, File targetFile, String templateType, String targetType) throws Exception {
        Map<String, Object> pictureMap = new HashMap<>();
        // 文件类型
        String templateFiletype = templateType;
        String targetFiletype = targetType;
        if (StringUtils.isEmpty(templateFiletype) || StringUtils.isEmpty(targetFiletype)) {
            throw new RuntimeException("file type is empty");
        }
        // 源文件流
        File Orifile = targetFile;
        InputStream oriis = new FileInputStream(Orifile);

        // 模板图
        BufferedImage imageTemplate = ImageIO.read(templateFile);
        WIDTH = imageTemplate.getWidth();
        HEIGHT = imageTemplate.getHeight();
        // 模板图
        BufferedImage imageTarget = ImageIO.read(Orifile);
        ORI_WIDTH = imageTarget.getWidth();
        ORI_HEIGHT = imageTarget.getHeight();

        generateCutoutCoordinates();
        // 最终图像
        BufferedImage newImage = new BufferedImage(WIDTH, HEIGHT, imageTemplate.getType());
        Graphics2D graphics = newImage.createGraphics();
        graphics.setBackground(Color.white);

        int bold = 5;
        // 获取感兴趣的目标区域
        BufferedImage targetImageNoDeal = getTargetArea(X, Y, WIDTH, HEIGHT, oriis, targetFiletype);


        // 根据模板图片抠图
        newImage = DealCutPictureByTemplate(targetImageNoDeal, imageTemplate, newImage);

        // 设置“抗锯齿”的属性
        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        graphics.setStroke(new BasicStroke(bold, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
        graphics.drawImage(newImage, 0, 0, null);
        graphics.dispose();

        ByteArrayOutputStream os = new ByteArrayOutputStream();//新建流。
        ImageIO.write(newImage, templateFiletype, os);//利用ImageIO类提供的write方法,将bi以png图片的数据模式写入流。
        byte[] newImages = os.toByteArray();
        pictureMap.put("newImage", newImages);
        // 源图生成遮罩
        BufferedImage oriImage = ImageIO.read(Orifile);
        byte[] oriCopyImages = DealOriPictureByTemplate(oriImage, imageTemplate, X, Y);
        pictureMap.put("oriCopyImage", oriCopyImages);
        pictureMap.put("X",X);
        pictureMap.put("Y",Y);
        return pictureMap;
    }

    /**
     * 抠图后原图生成
     *
     * @param oriImage
     * @param templateImage
     * @param x
     * @param y
     * @return
     * @throws Exception
     */
    private static byte[] DealOriPictureByTemplate(BufferedImage oriImage, BufferedImage templateImage, int x,
                                                   int y) throws Exception {
        // 源文件备份图像矩阵 支持alpha通道的rgb图像
        BufferedImage ori_copy_image = new BufferedImage(oriImage.getWidth(), oriImage.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
        // 源文件图像矩阵
        int[][] oriImageData = getData(oriImage);
        // 模板图像矩阵
        int[][] templateImageData = getData(templateImage);

        //copy 源图做不透明处理
        for (int i = 0; i < oriImageData.length; i++) {
            for (int j = 0; j < oriImageData[0].length; j++) {
                int rgb = oriImage.getRGB(i, j);
                int r = (0xff & rgb);
                int g = (0xff & (rgb >> 8));
                int b = (0xff & (rgb >> 16));
                //无透明处理
                rgb = r + (g << 8) + (b << 16) + (255 << 24);
                ori_copy_image.setRGB(i, j, rgb);
            }
        }

        for (int i = 0; i < templateImageData.length; i++) {
            for (int j = 0; j < templateImageData[0].length - 5; j++) {
                int rgb = templateImage.getRGB(i, j);
                //对源文件备份图像(x+i,y+j)坐标点进行透明处理
                if (rgb != 16777215 && rgb <= 0) {
                    int rgb_ori = ori_copy_image.getRGB(x + i, y + j);
                    int r = (0xff & rgb_ori);
                    int g = (0xff & (rgb_ori >> 8));
                    int b = (0xff & (rgb_ori >> 16));
                    rgb_ori = r + (g << 8) + (b << 16) + (150 << 24);
                    ori_copy_image.setRGB(x + i, y + j, rgb_ori);
                } else {
                    //do nothing
                }
            }
        }
        ByteArrayOutputStream os = new ByteArrayOutputStream();//新建流。
        ImageIO.write(ori_copy_image, "png", os);//利用ImageIO类提供的write方法,将bi以png图片的数据模式写入流。
        byte b[] = os.toByteArray();//从流中获取数据数组。
        return b;
    }


    /**
     * 根据模板图片抠图
     *
     * @param oriImage
     * @param templateImage
     * @return
     */

    private static BufferedImage DealCutPictureByTemplate(BufferedImage oriImage, BufferedImage templateImage,
                                                          BufferedImage targetImage) throws Exception {
        // 源文件图像矩阵
        int[][] oriImageData = getData(oriImage);
        // 模板图像矩阵
        int[][] templateImageData = getData(templateImage);
        // 模板图像宽度
        try {
            for (int i = 0; i < templateImageData.length; i++) {
                // 模板图片高度
                for (int j = 0; j < templateImageData[0].length; j++) {
                    // 如果模板图像当前像素点不是白色 copy源文件信息到目标图片中
                    int rgb = templateImageData[i][j];
                    if (rgb != 16777215 && rgb <= 0) {
                        targetImage.setRGB(i, j, oriImageData[i][j]);
                    }
                }
            }
        } catch (ArrayIndexOutOfBoundsException e) {/*数组越界错误处理,这样页面就不会返回图像问题。*/
            log.error("X:"+X+ "||Y:"+Y,e);
        } catch (Exception e) {
            log.error("X:"+X+ "||Y:"+Y,e);
        }
        return targetImage;
    }


    /**
     * 获取目标区域
     *
     * @param x            随机切图坐标x轴位置
     * @param y            随机切图坐标y轴位置
     * @param targetWidth  切图后目标宽度
     * @param targetHeight 切图后目标高度
     * @param ois          源文件输入流
     * @return
     * @throws Exception
     */
    private static BufferedImage getTargetArea(int x, int y, int targetWidth, int targetHeight, InputStream ois,
                                               String filetype) throws Exception {
        Iterator<ImageReader> imageReaderList = ImageIO.getImageReadersByFormatName(filetype);
        ImageReader imageReader = imageReaderList.next();
        // 获取图片流
        ImageInputStream iis = ImageIO.createImageInputStream(ois);
        // 输入源中的图像将只按顺序读取
        imageReader.setInput(iis, true);

        ImageReadParam param = imageReader.getDefaultReadParam();
        Rectangle rec = new Rectangle(x, y, targetWidth, targetHeight);
        param.setSourceRegion(rec);
        BufferedImage targetImage = imageReader.read(0, param);
        return targetImage;
    }

    /**
     * 生成图像矩阵
     *
     * @param
     * @return
     * @throws Exception
     */
    private static int[][] getData(BufferedImage bimg) throws Exception {
        int[][] data = new int[bimg.getWidth()][bimg.getHeight()];
        for (int i = 0; i < bimg.getWidth(); i++) {
            for (int j = 0; j < bimg.getHeight(); j++) {
                data[i][j] = bimg.getRGB(i, j);
            }
        }
        return data;
    }

    /**
     * 随机生成抠图坐标
     */
    private static void generateCutoutCoordinates() {
        Random random = new Random();
        int widthDifference = ORI_WIDTH - WIDTH;
        int heightDifference = ORI_HEIGHT - HEIGHT;

        if (widthDifference <= 0) {
            X = 5;

        } else {
            X = random.nextInt(ORI_WIDTH - WIDTH);
            if (X < WIDTH) {/*@herb 解决切图相对位置问题*/
                X = WIDTH;
            }
        }

        if (heightDifference <= 0) {
            Y = 5;
        } else {
            Y = random.nextInt(ORI_HEIGHT - HEIGHT);
        }
        NumberFormat numberFormat = NumberFormat.getInstance();
        numberFormat.setMaximumFractionDigits(2);

        xPercent = Float.parseFloat(numberFormat.format((float) X / (float) ORI_WIDTH));
        yPercent = Float.parseFloat(numberFormat.format((float) Y / (float) ORI_HEIGHT));
    }
}
  1. controller类
package com.sikong.ms.web.controller;

import com.google.common.collect.Maps;
import com.sikong.hexuan.entity.welfare.WelEmployee;
import com.sikong.ms.common.auth.shiro.UsernamePasswordToken;
import com.sikong.ms.common.cache.jedis.JedisConfig;
import com.sikong.ms.common.cache.jedis.JedisUtils;
import com.sikong.ms.common.captcha.validate.VerifyImageUtil;
import com.sikong.ms.common.util.Digests;
import com.sikong.ms.common.util.FileUtil;
import com.sikong.ms.web.service.WelEmployeeService;
import com.sikong.ms.web.shiro.AccountAuthorizationRealm;
import com.sikong.ms.web.shiro.CacheUtils;
import com.sikong.ms.web.shiro.ValidateCache;
import com.sikong.ms.web.utils.NumberUtils;
import com.sikong.ms.web.utils.SendMessageUtil;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONObject;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.session.InvalidSessionException;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Controller;
import org.springframework.util.Base64Utils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;
import java.io.*;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;


/**
 1. Created by yansheng on 2014/6/29.
 */
@Slf4j
@Controller
@RequestMapping("/validate")
public class ValidateController {
    @Resource
    private WelEmployeeService employeeService;
    /**
     * 生成验证码
     *
     * @return
     */
    @RequestMapping(value = "/init")
    @ResponseBody
    public JSONObject init() throws IOException {
        JSONObject object = new JSONObject();

        /*redis实现:使用base64编码转化成字符串处理*/
//        List<String> imgList = JedisUtils.getList(JedisConfig.KEY_VALIDATE_IMG);
//        List<String> tpllist = JedisUtils.getList(JedisConfig.KEY_VALIDATE_TPL);
//        if (null == imgList || imgList.size() < 1 || tpllist == null || tpllist.size() < 1) {
//            imgList = new ArrayList<String>();
//            tpllist = new ArrayList<String>();
//            initValidateResources(imgList,tpllist);
//            JedisUtils.setList(JedisConfig.KEY_VALIDATE_IMG,imgList,JedisConfig.JEDIS_EXPIRE*3);
//            JedisUtils.setList(JedisConfig.KEY_VALIDATE_TPL,tpllist,JedisConfig.JEDIS_EXPIRE*3);
//        }

        /*本地缓存实现*/
        List<byte[]> imgList = ValidateCache.get(JedisConfig.KEY_VALIDATE_IMG);
        List<byte[]> tpllist = ValidateCache.get(JedisConfig.KEY_VALIDATE_TPL);
        if (null == imgList || imgList.size() < 1 || tpllist == null || tpllist.size() < 1) {
            imgList = new ArrayList<byte[]>();
            tpllist = new ArrayList<byte[]>();
            initValidateResources(imgList,tpllist);
            ValidateCache.set(JedisConfig.KEY_VALIDATE_IMG,imgList);
            ValidateCache.set(JedisConfig.KEY_VALIDATE_TPL,tpllist);
        }

        byte[] targetIS = null;
        byte[] templateIS = null;
        Random ra = new Random();
        if (null != imgList){
            int rd = ra.nextInt(imgList.size());
            targetIS = imgList.get(rd);
        }
        if (null != tpllist){
            int rd = ra.nextInt(tpllist.size());
            templateIS = tpllist.get(rd);
        }

        Map<String, Object> pictureMap = null;
        try {
            pictureMap = VerifyImageUtil.pictureTemplatesCut(templateIS,targetIS , "png", "jpg");
            String newImage = Base64Utils.encodeToString((byte[]) pictureMap.get("newImage"));
            String sourceImage = Base64Utils.encodeToString((byte[]) pictureMap.get("oriCopyImage"));
            int X = (int) pictureMap.get("X");
            int Y = (int) pictureMap.get("Y");
            object.put("newImage", newImage);
            object.put("sourceImage", sourceImage);
            //object.put("X", X);
            object.put("Y", Y);


            String token = UUID.randomUUID().toString().replaceAll("-", "");
            Map<String, Object> tokenObj = new HashMap<String, Object>();
            tokenObj.put("token", token);
            tokenObj.put("X", X);
            tokenObj.put("Y", Y);
            //token 保存2分钟
            JedisUtils.setObjectMap(JedisConfig.KEY_VALIDATE_TOKEN + ":" + token, tokenObj, 120000);
            object.put("token", token);
        } catch (Exception e) {
            log.error("",e);
        }
        return object;
    }

    /**
     * 初始化验证图形生成资源
     * @param imgList
     * @param tpllist
     */
    private void initValidateResources(List<byte[]> imgList, List<byte[]> tpllist) throws IOException {
        /*加载验证原图*/
        String target = URLDecoder.decode(ValidateController.class.getClassLoader().getResource("static/image/validate/targets").getPath(),"UTF-8");
        byte[] targetIS = null;
        byte[] templateIS = null;
        if (target.indexOf("!/") != -1) {//jar包
            String jarPath = "jar:" + target;
            log.debug(jarPath);
            URL jarURL = new URL(jarPath);
            JarURLConnection jarCon = (JarURLConnection) jarURL.openConnection();
            JarFile jarFile = jarCon.getJarFile();
            Enumeration<JarEntry> jarEntrys = jarFile.entries();
            while (jarEntrys.hasMoreElements()) {
                JarEntry entry = jarEntrys.nextElement();
                String name = entry.getName();
                if (name.startsWith("static/image/validate/targets") && !name.equals("static/image/validate/targets/") && (name.endsWith(".jpg") || name.endsWith(".png"))) {
                    log.debug("targets=" + name);
                    InputStream isTemplates = jarFile.getInputStream(entry);
                    targetIS = IOUtils.toByteArray(jarFile.getInputStream(entry));
                    imgList.add(targetIS);

                } else if (name.startsWith("static/image/validate/templates") && !name.equals("static/image/validate/templates/")  && (name.endsWith(".jpg") || name.endsWith(".png"))) {
                    log.debug("templates=" + name);
                    InputStream isTemplates = jarFile.getInputStream(entry);
                    templateIS = IOUtils.toByteArray(jarFile.getInputStream(entry));
                    tpllist.add(templateIS);
                }
            }
        } else {
            File targetBaseFile = new File(target);
            if (null != targetBaseFile) {
                File[] fs = targetBaseFile.listFiles();
//                Random ra = new Random();
//                if (null != fs && fs.length > 0) {
//                    int random = ra.nextInt(fs.length);
//                    targetIS = IOUtils.toByteArray(new FileInputStream(fs[random]));
//                }
                for (File f : fs){
                    targetIS = IOUtils.toByteArray(new FileInputStream(f));
                    imgList.add(targetIS);
                }
            }
            /*加载切图模板*/
            String template = URLDecoder.decode(ValidateController.class.getClassLoader().getResource("static/image/validate/templates").getFile(),"UTF-8");
            File templateBaseFile = new File(template);
            if (null != templateBaseFile) {
                File[] fs = templateBaseFile.listFiles();
//                Random ra = new Random();
//                if (null != fs && fs.length > 0) {
//                    int random = ra.nextInt(fs.length);
//                    templateIS = IOUtils.toByteArray(new FileInputStream(fs[random]));
//                }
                for (File f : fs){
                    templateIS = IOUtils.toByteArray(new FileInputStream(f));
                    tpllist.add(templateIS);
                }
            }
        }
        log.info("initValidateResources:template size:" + tpllist.size() + "target size:" + imgList.size());
    }


    /**
     * 验证方法 (有验证码的方法提交,有时候也可以带上验证参数,做后端二次验证)
     *
     * @return
     */
    @RequestMapping(value = "check",method = RequestMethod.POST)
    @ResponseBody
    public boolean check(String token, int X, int Y) {
        JSONObject message = new JSONObject();
        message.put("code", 2);
        message.put("massage", "验证不通过,请重试!");
        if (null == token || token.trim().length() < 1) {
            message.put("code", 0);
            message.put("massage", "请求参数错误:token为空!");
        }
        Map<String, Object> tokenObj = JedisUtils.getObjectMap(JedisConfig.KEY_VALIDATE_TOKEN + ":" + token);
        if (null == tokenObj) {
            message.put("code", -1);
            message.put("massage", "验证码超期,请重新请求!");
        } else {
            int sX = (Integer) tokenObj.get("X");
            int sY = (Integer) tokenObj.get("Y");
            if (sY != Y) {
                message.put("code", 0);
                message.put("massage", "请求参数错误:位置信息不正确!");
            } else {
                if (Math.abs(sX - X) <= 2) {
                    message.put("code", 1);
                    message.put("massage", "验证通过!");
                } else {
                    message.put("code", 2);
                    message.put("massage", "验证不通过,请重试!");
                }
            }
        }
        if (message.get("code")==1) {
            return true;
        } else {
            return false;
        }
    }



    /**
     * 根据手机号查询用户是否存在
     */

    @RequestMapping("/checkUserIsHave")
    @ResponseBody
    public String checkUserIsHave(
            @RequestParam(required = false, value = "mobile") String mobile) {
        String isHave = "true";
        WelEmployee welEmployee =  new WelEmployee();
        if (StringUtils.isNotEmpty(mobile)) {
            welEmployee.setMobile(mobile);
        }
        WelEmployee employee = employeeService.findWelEmployee(welEmployee);
        if (employee == null) {
            isHave = "false";
        }
        return isHave;
    }


    /**
     * 获取手机验证码
     */
    @RequestMapping(value = "/getMobileCode", method = RequestMethod.POST)
    @ResponseBody
    public Map<String, String> getMobileCode(
            @RequestParam(required =true, value = "account") String account) {
        Map<String, String> map = Maps.newHashMap();
        try {
            Subject subject = SecurityUtils.getSubject();
            Session session = subject.getSession();
            session.setAttribute("recordTime", System.currentTimeMillis());//设置当前时间点
            log.debug("当前时间戳==" + System.currentTimeMillis());
            Integer valid = NumberUtils.getRandomInt(99999);
            System.out.println(valid);
            session.setAttribute("mobile", account);
            session.setAttribute("validCode", valid);//验证码
            SendMessageUtil sms = new SendMessageUtil();//发送短信
            String str = sms.sendSms(account, " 验证码为 "+valid+",请在页面中输入以完成验证。");
            map.put("success", "true");
            map.put("message", "一分钟后可重新获取");
        } catch (InvalidSessionException e) {
            log.error("获取验证码失败", e);
            map.put("success", "false");
            map.put("message", "获取验证码失败!");
            return map;
        }
        return map;
    }


    /**
     * 校验输入的短信验证码是否正确
     */
    @RequestMapping(value = "/checkMobileCode", method = RequestMethod.POST)
    @ResponseBody
    public  Map<String, String> checkMobileCode(
            @RequestParam(required =true, value = "bound") String bound) {
        bound = bound.trim();
        Subject subject = SecurityUtils.getSubject();
        Session session = subject.getSession();
        Map<String, String> map = Maps.newHashMap();
        Object code = session.getAttribute("validCode");//发送的验证码
        Long recordTime = (Long) session.getAttribute("recordTime");//发送验证码的时候设置的时间点
        if (bound == "") {
            map.put("success", "false");
            map.put("message", "验证码不能为空");
            return map;
        } else if (code == null || recordTime == null){
            map.put("success", "false");
            map.put("message", "未获取验证码");
            return map;
        }
        Long now = System.currentTimeMillis();
        log.debug("1时间==" + (now));
        log.debug("2时间==" + (recordTime));
        log.debug("记录时间==" + (now - recordTime));
        if (!bound.equals(code.toString())) {
            map.put("success", "false");
            map.put("message", "验证码错误");
        } else if ((now - recordTime) > 65000) {
            map.put("success", "false");
            map.put("message", "验证码已经失效");
        } else {
            map.put("success", "true");
        }
        return map;
    }
    /**
     * 验证密码是否正确
     */
    @RequestMapping("/checkPassword")
    @ResponseBody
    public Map<String, String> checkUserIsHave(
            @RequestParam(required = false, value = "password") String password,
            @RequestParam(required = false, value = "employeeId") String employeeId
            ) {
        Map<String, String> map = Maps.newHashMap();
        map.put("success","true");
        WelEmployee welEmployee=employeeService.findWelEmployeeByEmployeeId(employeeId);
        String passwordOld=welEmployee.getPassword();
        boolean res=  Digests.validatePassword(password,passwordOld);
        if(!res){
            map.put("success","false");
        }
        return map;
    }



    public static void main(String[] args) {
        String stream = ValidateController.class.getClassLoader().getResource("static/image/validate/").getFile();
        File validateBaseFile = new File(stream);
        File vf = null;
        if (null != validateBaseFile) {
            File[] fs = validateBaseFile.listFiles();
            Random ra = new Random();
            if (null != fs && fs.length > 0) {
                int random = ra.nextInt(fs.length);
                vf = fs[random];
            }
        }
        System.out.println(vf.getName());
    }
}
  1. freemarker前端实现
<#macro imgValidate>
<#--<script src="${base}/js/jquery-3.1.1.min.js"></script>-->
<style>
    .rightValidate {
        width: 350px;
        margin: 0px auto;
        position: relative;
        line-height: 33px;
        height: 33px;
        text-align: center;
        z-index: 99;
    }

    .v_rightBtn {
        position: absolute;
        left: 0;
        top: 0;
        height: 33px;
        width: 40px;
        background: #ddd;
        cursor: pointer;
    }
    .imgBtn{
        width:50px;
        height: 50px;
        position: absolute;
        left: 0;
        display: none;
    }
    .imgBtn img{
        width:100%
    }
    .imgBg{
        position: relative;
        width: 350px;
        height: 0;
        box-shadow: 0px 4px 8px #3C5476;
    }

    .hkinnerWrap{
        border: 1px solid #eee;
    }
    .green{
        border-color:#34C6C2 !important;
    }
    .green .v_rightBtn{
        background: #34C6C2;
        color: #fff;
    }
    .red{
        border-color:red !important;
    }
    .red .v_rightBtn{
        background: red;
        color: #fff;
    }
    .refresh{
        position: absolute;
        width: 30px;
        height: 30px;
        right: 0;
        top: 0;
        font-size: 12px;
        color: #fff;
        text-shadow: 0px 0px 9px #333;
        cursor: pointer;
        display: none;
    }
    .notSel{
        user-select: none;
        -webkit-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        -webkit-touch-callout: none;
    }
</style>
<div class="comImageValidate rightValidate">
    <div class="hkinnerWrap" style="height: 33px;position: relative">
        <span  class="v_rightBtn "><em class="notSel"></em></span>
        <span class="huakuai"  style="font-size: 12px;line-height: 33px;color: #A9A9A9;">向右滑动滑块填充拼图</span>
        <input type = "hidden" name="validX"/>
    </div>
    <div class="imgBg">
        <div class="imgBtn">
            <img />
        </div>
        <span class="refresh">刷新</span>
    </div>
</div>


<#--<div>
    <img  src="https://img-blog.csdnimg.cn/2022010700374735813.png"/>
    <img  src="https://img-blog.csdnimg.cn/2022010700374754624.jpg" />
</div>-->

<script>
    var tokenId = "";
    var y = "";
    var x = "";
    $(".comImageValidate").ready(function () {
        validateImageInit();
        $(".refresh").click(function () {
            validateImageInit();
        })
        $(".hkinnerWrap").mouseover(function(){
            $(".imgBg").stop(false).animate({"height":"213px"},100,function () {
                $(".imgBtn").css("display","block");
                $(".refresh").css("display","block");
            });
        }).mouseleave(function () {
            $(".imgBg").stop(false).animate({"height":"0"},100,function () {
                $(".imgBtn").css("display","none");
                $(".refresh").css("display","none");
            });
        });
        $(".imgBg").mouseover(function () {
            $(".imgBg").stop(false).animate({"height":"213px"},100,function () {
                $(".imgBtn").css("display","block");
                $(".refresh").css("display","block");
            });
        }).mouseleave(function () {
            $(".imgBg").stop(false).animate({"height":"0"},100,function () {
                $(".imgBtn").css("display","none");
                $(".refresh").css("display","none");
            });
        })
        $('.v_rightBtn').on({
            mousedown: function(e) {
                $(".huakuai").html("");
                $(".hkinnerWrap").removeClass("red green")
                var el = $(this);
                var os = el.offset();
                dx = e.pageX - os.left;
                //$(document)
                $(this).parents(".hkinnerWrap").off('mousemove');
                $(this).parents(".hkinnerWrap").on('mousemove', function(e) {
                    var newLeft=e.pageX - dx;
                    el.offset({
                        left: newLeft
                    });
                    var newL=parseInt($(".v_rightBtn").css("left"));
                    if(newL<=0){
                        newL=0;
                    }else if (newL>=298){
                        newL=306;
                    }
                    $(".v_rightBtn").css("left",newL+"px");
                    $(".imgBtn").offset({
                        left: newLeft
                    });
                    $(".imgBtn").css("left",newL+"px")
                }).on('mouseup', function(e) {
                    //$(document)
                    $(this).off('mousemove');
                })
            }
        }).on("mouseup",function () {
            $(this).parents(".hkinnerWrap").off('mousemove');
            var l=$(this).css("left");
            if(l.indexOf("px")!=-1){
                l=l.substring(0,l.length-2);
            }
            x = l;
            submitDate(l,y,tokenId)
        })

    });
    /*图形验证*/
    function submitDate(x,y,tokenId) {
        $.ajax({
            url:"${base}/validate/check?X="+x+"&Y="+y+"&token="+tokenId,
            dataType:'json',
            type: "POST",
            success:function (data) {
                if(data==true){
                    $(".hkinnerWrap").addClass("green").removeClass("red");
                    $(".hkinnerWrap input[name='validX']").val(x);
                } else {
                    $(".hkinnerWrap").addClass("red").removeClass("green");
                    // setTimeout(function(){
                    //     $(".hkinnerWrap").removeClass("red green");
                    //     $(".v_rightBtn").css("left",0);
                    //     $(".imgBtn").css("left",0);
                    // },500)
                    //validateImageInit();
                }
            }
        })
    }

    /*初始化图形验证码*/
    function validateImageInit() {
        $.ajax({
            url:"${base}/validate/init",
            dataType:'json',
            cache:false,
            type: "get",
            success:function (data) {
                $(".huakuai").html("向右滑动滑块填充拼图");
                $(".imgBg").css("background",'#fff url("data:image/jpg;base64,'+data.sourceImage+'")');
                $(".imgBtn").css('top',data.Y+'px');
                $(".imgBtn").find("img").attr("src","data:image/png;base64,"+data.newImage)
                tokenId=data.token;
                y=data.Y;
                $(".hkinnerWrap").removeClass("red green");
                $(".v_rightBtn").css("left",0);
                $(".imgBtn").css("left",0);
            },error:function(err){
                console.log(err)
            }
        })
    }
    /**
     * 再次进行用户拖动行为验证
     * @returns {boolean}
     */
    function reValidateDeal(){
        var result = false;
        $.ajax({
            url:"${base}/validate/check?X="+x+"&Y="+y+"&token="+tokenId,
            dataType:'json',
            cache : false,
            async : false,
            type: "POST",
            success:function (data) {
                if(data==true){
                    result = true;
                }else {
                    result = false;
                }
            }
        })
        return result;
    }

</script>




<#--<div>
    <img  src="https://img-blog.csdnimg.cn/2022010700374735813.png"/>
    <img  src="https://img-blog.csdnimg.cn/2022010700374754624.jpg" />
</div>-->
</#macro>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
滑块验证码是一种常见的人机交互验证方式,主要用于防止恶意攻击和机器人攻击。下面是一个简单的Java实现滑块验证码的示例代码: 1. 首先,需要在前端页面上实现一个滑块组件,并在后台生成一个随机的验证码图片。 2. 然后,用户需要按住滑块并将其拖动到正确的位置,以验证自己是一个真正的人类用户。 3. 在后台,需要验证用户拖动滑块的位置是否正确,以确保用户通过了验证。 下面是一个基于Spring Boot框架的简单示例代码: 1. 在前端页面中添加如下代码: ```html <div class="slider-container"> <div class="slider-background"></div> <div class="slider-handle"></div> </div> ``` 2. 在后台代码中,需要生成一个随机的验证码图片,并将验证码信息保存在Session中,以便后续验证。以下是一个简单的验证码生成器示例代码: ```java import java.awt.*; import java.awt.image.BufferedImage; import java.util.Random; public class CaptchaGenerator { private static final int IMAGE_WIDTH = 200; private static final int IMAGE_HEIGHT = 80; private static final int LINE_COUNT = 20; private static final int CHAR_COUNT = 4; private static final String CHAR_SET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; private static final int CHAR_SPACE = 20; private static final int CHAR_FONT_SIZE = 50; public static BufferedImage generate(String captcha) { BufferedImage image = new BufferedImage(IMAGE_WIDTH, IMAGE_HEIGHT, BufferedImage.TYPE_INT_RGB); Graphics2D g = image.createGraphics(); g.setColor(Color.WHITE); g.fillRect(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT); g.setColor(Color.BLACK); Random random = new Random(); for (int i = 0; i < LINE_COUNT; i++) { int x1 = random.nextInt(IMAGE_WIDTH); int y1 = random.nextInt(IMAGE_HEIGHT); int x2 = random.nextInt(IMAGE_WIDTH); int y2 = random.nextInt(IMAGE_HEIGHT); g.drawLine(x1, y1, x2, y2); } Font font = new Font("Arial", Font.BOLD, CHAR_FONT_SIZE); g.setFont(font); int x = (IMAGE_WIDTH - CHAR_COUNT * CHAR_FONT_SIZE - (CHAR_COUNT - 1) * CHAR_SPACE) / 2; int y = (IMAGE_HEIGHT - CHAR_FONT_SIZE) / 2 + CHAR_FONT_SIZE; for (int i = 0; i < captcha.length(); i++) { char c = captcha.charAt(i); g.drawString(String.valueOf(c), x, y); x += CHAR_FONT_SIZE + CHAR_SPACE; } g.dispose(); return image; } public static String generateCaptcha() { Random random = new Random(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < CHAR_COUNT; i++) { char c = CHAR_SET.charAt(random.nextInt(CHAR_SET.length())); sb.append(c); } return sb.toString(); } } ``` 3. 在Controller中,需要处理滑块验证请求,并进行验证码验证。以下是一个简单的Controller示例代码: ```java import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.awt.image.BufferedImage; import java.io.IOException; @Controller public class CaptchaController { @GetMapping("/captcha") public void captcha(HttpServletRequest request, HttpServletResponse response) throws IOException { String captcha = CaptchaGenerator.generateCaptcha(); BufferedImage image = CaptchaGenerator.generate(captcha); HttpSession session = request.getSession(); session.setAttribute("captcha", captcha); response.setContentType("image/png"); response.getOutputStream().write(ImageUtil.toByteArray(image)); } @PostMapping("/captcha/verify") @ResponseBody public boolean verify(@RequestParam String captcha, HttpSession session) { String expectedCaptcha = (String) session.getAttribute("captcha"); return captcha.equals(expectedCaptcha); } } ``` 以上是一个简单的Java实现滑块验证码的示例代码。为了实现更好的安全性,实际应用中需要进一步加强验证机制,例如添加时间限制、IP限制等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值