后台生成验证码的两种方式

     现在所有的系统,只要有个登录页面,就要有验证码验证,验证码也是安全验证的一个环节,验证码可以防止恶意破解密码、刷票、du论坛灌水,有效防止某个黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登陆尝试,实际上用验证码是现在很多网站通行的方式,利用比较简易的方式实现了这个功能。这个问题可以由计算机生成并评判,但是必须只有人类才能解答。由于计算机无法解答验证码的问题,所以回答出问题的用户就可以被认为是人类。所以大多数验证码都是要进行加密或者直接是以图片的方式展示的。

今天本篇博客介绍两种比较安全的生成验证码的方式,

(1)后台直接生成验证码图片返回给页面使用

(2)后台随机生成数字加密返回给前端,再由前端组装成想要的格式的图片。

第一种:后台直接生成验证码的方式:

controller层:

package com.boss.sso.util;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;

/**
 * Created by 2020/1/9 17:20.
 */
public class CreateValidateCode {

    private BufferedImage image;// 图像
    private String str;// 验证码
    private char[] code = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789".toCharArray();

    public static final String SESSION_CODE_NAME = "code";

    public CreateValidateCode() {
        init();// 初始化属性
    }

    /*
     * 取得RandomNumUtil实例
     */
    public static CreateValidateCode Instance() {
        return new CreateValidateCode();
    }

    /*
     * 取得验证码图片
     */
    public BufferedImage getImage() {
        return this.image;
    }

    /*
     * 取得图片的验证码
     */
    public String getString() {
        return str.toLowerCase();
    }

    private void init() {
        // 在内存中创建图象
        int width = 60, height = 20;
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        // 获取图形上下文
        Graphics g = image.getGraphics();
        // 生成随机类
        Random random = new Random();
        // 设定背景色
        g.setColor(getRandColor(200, 250));
        g.fillRect(0, 0, width, height);
        // 设定字体
        g.setFont(new Font("Times New Roman", Font.PLAIN, 18));
        // 随机产生155条干扰线,使图象中的认证码不易被其它程序探测到
        g.setColor(getRandColor(160, 200));
        for (int i = 0; i < 155; i++) {
            int x = random.nextInt(width);
            int y = random.nextInt(height);
            int xl = random.nextInt(12);
            int yl = random.nextInt(12);
            g.drawLine(x, y, x + xl, y + yl);
        }
        // 取随机产生的认证码(4位数字)
        String sRand = "";
        for (int i = 0; i < 4; i++) {
            String rand = String.valueOf(code[random.nextInt(code.length)]);
            sRand += rand;
            // 将认证码显示到图象中
            g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110)));
            // 调用函数出来的颜色相同,可能是因为种子太接近,所以只能直接生成
            g.drawString(rand, 13 * i + 6, 16);
        }
        // 赋值验证码

        this.str = sRand;


        // 图象生效
        g.dispose();

        /* 赋值图像 */
        this.image = image;
    }

    /**
     * @param fc
     * @param bc
     * @return 给定范围获得随机颜色
     */
    private Color getRandColor(int fc, int bc) {
        Random random = new Random();
        if (fc > 255) {
            fc = 255;
        }
        if (bc > 255) {
            bc = 255;
        }
        int r = fc + random.nextInt(bc - fc);
        int g = fc + random.nextInt(bc - fc);
        int b = fc + random.nextInt(bc - fc);
        return new Color(r, g, b);
    }
}

生成验证码的工具类:

package com.boss.sso.controller;
import com.boss.sso.util.CreateValidateCode;
import org.apache.servicecomb.provider.rest.common.RestSchema;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.ws.rs.core.MediaType;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Random;

/**
 * @ClassName VerificationCodeController
 * @Description: TODO
 * @Date 2020/6/29
 **/

@RestController
@RequestMapping(path = "/", produces = MediaType.APPLICATION_JSON)
public class VerificationCodeController {

    private static Logger log = LoggerFactory.getLogger(VerificationCodeController.class);

    /**
     * <br>Description:生成登录页面的图片验证码
     * <br>Date: 2020年06月30日
     * @param response
     * @param request
     * @throw
     */
    @GetMapping("v1/verifycode")
    public void getVerificationCode(HttpServletResponse response, HttpServletRequest request,String timestamp) {
        OutputStream outputStream =null;
        try {
            //创建输出流
            outputStream = response.getOutputStream();
            //获取session
            HttpSession session = request.getSession();
            //获取验证码
            CreateValidateCode createValidateCode = new CreateValidateCode();
            String generateVerifyCode = createValidateCode.getString();
            //将验证码存入session,做登录验证
            session.setAttribute("captcha", generateVerifyCode);
            log.info("start get verification code sessionid is {}, verification code is {}", session.getId(), generateVerifyCode);
            //获取验证码图片
            BufferedImage image = createValidateCode.getImage();
            ImageIO.write(image, "png", outputStream);
            outputStream.flush();
        } catch (IOException e) {
            log.error("获取验证码图片异常!", e);
        }finally {
            if(outputStream!=null){
                //关流
                try {
                    outputStream.close();
                } catch (IOException e) {
                    log.error("关闭流失败!", e);
                }
            }
        }
    }

    /*对图片进行处理的类和方法*/
    public class VerifyCode {
        public  String drawRandomText(int width,int height,BufferedImage verifyImg) {
            Graphics2D graphics = (Graphics2D)verifyImg.getGraphics();
            graphics.setColor(Color.WHITE);//设置画笔颜色-验证码背景色
            graphics.fillRect(0, 0, width, height);//填充背景
            graphics.setFont(new Font("微软雅黑", Font.BOLD, 40));
            //数字和字母的组合
            String baseNumLetter = "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ";
            StringBuffer sBuffer = new StringBuffer();
            int x = 10;  //旋转原点的 x 坐标
            String ch = "";
            Random random = new Random();
            for(int i = 0;i < 4;i++){
                graphics.setColor(getRandomColor());
                //设置字体旋转角度
                int degree = random.nextInt() % 30;  //角度小于30度
                int dot = random.nextInt(baseNumLetter.length());
                ch = baseNumLetter.charAt(dot) + "";
                sBuffer.append(ch);
                //正向旋转
                graphics.rotate(degree * Math.PI / 180, x, 45);
                graphics.drawString(ch, x, 45);
                //反向旋转
                graphics.rotate(-degree * Math.PI / 180, x, 45);
                x += 48;
            }
            //画干扰线
            for (int i = 0; i <6; i++) {
                // 设置随机颜色
                graphics.setColor(getRandomColor());
                // 随机画线
                graphics.drawLine(random.nextInt(width), random.nextInt(height),
                        random.nextInt(width), random.nextInt(height));
            }
            //添加噪点
            for(int i=0;i<30;i++){
                int x1 = random.nextInt(width);
                int y1 = random.nextInt(height);
                graphics.setColor(getRandomColor());
                graphics.fillRect(x1, y1, 2,2);
            }
            return sBuffer.toString();
        }
        /**
         * 随机取色
         */
        private Color getRandomColor() {
            Random ran = new Random();
            Color color = new Color(ran.nextInt(256),
                    ran.nextInt(256), ran.nextInt(256));
            return color;
        }
    }
}

接口调用返回结果:

 

 

 

 前端页面使用:

html代码:(此处代码只是例子)

<input type="text" placeholder="验证码" class="ipt user_pass_ipt" id="yzm">
<div id="user_pass" style="cursor:pointer;">
    <img id="captchaImage" onclick="changeImage()" style="height: 45px;width: 105px" class="captchaImage">
</div>

js展示验证码代码:

function changeImage() {
     //后台生成验证码方式  
    $("#captchaImage").attr({"src": gateway.uri+"/missLogin/sso/v1/verifycode?timestamp=" + new Date().getTime()});
 }

第二种:后台随机生成数字加密返回给前端,再由前端组装成想要的格式的图片

Controller层代码:

@Autowired
private ICaptchaBussiness captchaBussinessImpl;
/**
 * 根据用户编码获取验证码
 * @return String
 * @author
 * @since
 */
@GetMapping(value = "v2/captchas")
public ApiResultDTO getCaptcha(@RequestParam String timestamp) {
    //生成一个随机数
    //此随机数是用来作为放缓存的key,验证的时候,带着这个随机数,
    //从缓存里拿出生成的验证码进行验证码验证。
    String random = SysUtils.generateGuid(); 
    String captcha = captchaBussinessImpl.getCaptchaByRandom(random);
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.append(captcha).append("#").append(random);
    //base64加密
    byte[] bytes = stringBuilder.toString().getBytes();
    //返回值用base64加密,安全
    String encoded = Base64.getEncoder().encodeToString(bytes);
    log.info("Base 64 加密后的验证码:" + encoded);
    return ApiResultDTO.success(encoded,"获取成功!");
}

Service层生成验证码:

   /**
     * 直接获取验证码
     * @return
     */
    @Override
    public String getCaptchaByRandom(String randomGuid){

        String str = "abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ0123456789";
        SecureRandom random = new SecureRandom();
//        Random r = new Random();
        String arr[] = new String[4];
        //生成的验证码
        String captcha = "";
        for (int i = 0; i < 4; i++) {
            int n = random.nextInt(62);

            arr[i] = str.substring(n, n + 1);
            captcha += arr[i];
        }
        String captchaKey="captchaKey#" + randomGuid;
        //放入缓存  key:usercode  value:4位验证码
        redisUtil.delete(captchaKey);
        redisUtil.set(captchaKey,captcha);
//        System.out.println("############################################# get  "+captchaKey+"    captcha:"+captcha);
        System.out.println("返回验证码:"+captcha);
        return captcha;
    }

后台返回值示例:

{
"result":"获取成功!",
"data":"YkxnMCNCNjkwQTkwQ0ZBOTY0Njk2OEZDQTdEREI4QzJDNkRGQQ==",
"rscode":"100000"
}

前端js根据返回值,解密,获取生成的验证码,生成图片显示在页面上。

HTML:

<input type="text" placeholder="验证码" class="ipt user_pass_ipt" id="yzm">
<a href="#" id="user_pass">
    <canvas class="show-captcha" onclick="changeImage()" style="height: 45px;width: 105px"
            id="captchaImage" width="150" height="40"></canvas>
</a>

js代码:

function changeImage() {
    //后台生成验证码数字
    captchaCode = getCaptchaCode();
    //前端前端利用生成的验证码生成图片
    drawPicWithCode("captchaImage", captchaCode);
   }

//后台获取验证码
function getCaptchaCode() {
    var vCode;
    $.ajax({
        type: 'get',
        url: "/v2/captchas?timestamp=" + new Date().getTime(),
        contentType: "application/x-www-form-urlencoded",
        async: false,
        data: {"idcardno": $("#idcardno").val()},
        success: function a(result) {
            if (typeof (result) == 'string') {
                if (result != "") {
                    result = eval("(" + result + ")");
                }
            }
            if (result.rscode == "100000") {
                vCode = Base64.decode(result.data).split("#")[0];
                captchaUUID = Base64.decode(result.data).split("#")[1];
            } else {
                window.alert("验证码生成失败!");
            }
        },
        error: function a(data) {
            window.alert("验证码生成失败!");
        }
    });
    return vCode;
}

自己通过百度封装的一个生成验证码的工具类js,可以参考

/**生成一个随机数**/
function randomNum(min, max) {
    return Math.floor(Math.random() * (max - min) + min);
}
/**生成一个随机色**/
function randomColor(min, max) {
    var r = randomNum(min, max);
    var g = randomNum(min, max);
    var b= randomNum(min, max);
    return "rgb(" + r + "," + g + "," + b + ")";
}
var validateCode;
var validateCodeCA;
/**绘制验证码图片**/
function drawPic(canvasId,vcode) {
    var canvas = document.getElementById(canvasId);
    var width = canvas.width;
    var height = canvas.height;
    //获取该canvas的2D绘图环境
    var ctx = canvas.getContext('2d');
    ctx.textBaseline ='bottom';
    /**绘制背景色**/
    // ctx.fillStyle = randomColor(180,240);
    ctx.fillStyle = '#00A6FF';
    //颜色若太深可能导致看不清
    ctx.fillRect(0,0,width,height);
    /**绘制文字**/
    var str ='ABCEFGHJKLMNPQRSTWXY123456789abcefghjklmnpqrstwxy';
    var code="";
    //生成四个验证码
    for(var i=1;i<=4;i++) {
        var txt = str[randomNum(0,str.length)];
        code=code+txt;
        ctx.fillStyle = randomColor(255,255);
        //随机生成字体颜色
        // ctx.font = randomNum(15,40) +'px SimHei';
        ctx.font = 30 +'px SimHei';  //设置字体大小
        //随机生成字体大小
        var x =0 +i *25; //x坐标
        var y = randomNum(25,35); // y坐标
        y = 30;
        // var deg = randomNum(-45,45);
        var deg= 30;
        //修改坐标原点和旋转角度
        ctx.translate(x, y);
        ctx.rotate(deg * Math.PI /180);  //设置字体旋转角度
        // ctx.rotate(deg * Math.PI /180);
        ctx.fillText(txt,0,0);
        //恢复坐标原点和旋转角度
        ctx.rotate(-deg * Math.PI /180);
        ctx.translate(-x, -y);
    }

    /**绘制干扰线**/
    // for(var i=0;i<3;i++) {
    //     ctx.strokeStyle = randomColor(40, 180);
    //     ctx.beginPath();
    //     ctx.moveTo(randomNum(0,width/2), randomNum(0,height/2));
    //     ctx.lineTo(randomNum(0,width/2), randomNum(0,height));
    //     ctx.stroke();
    // }
    /**绘制干扰点**/
    // for(var i=0;i <50;i++) {
    //     ctx.fillStyle = randomColor(255);
    //     ctx.beginPath();
    //     ctx.arc(randomNum(0, width), randomNum(0, height),1,0,2* Math.PI);
    //     ctx.fill();
    // }
    if (canvasId.indexOf("1")!=-1){
        validateCodeCA=code;
    }else{
        validateCode = code;
    }
    return code;
}

function drawPicWithCode(canvasId,vcode) {
    var canvas = document.getElementById(canvasId);
    var width = canvas.width;
    var height = canvas.height;
    //获取该canvas的2D绘图环境
    var ctx = canvas.getContext('2d');
    ctx.textBaseline ='bottom';
    /**绘制背景色**/
    // ctx.fillStyle = randomColor(180,240);
    ctx.fillStyle = '#00A6FF';
    //颜色若太深可能导致看不清
    ctx.fillRect(0,0,width,height);
    /**绘制文字**/
    var str ='ABCEFGHJKLMNPQRSTWXY123456789abcefghjklmnpqrstwxy';
    //生成四个验证码
    var array=vcode.split("")
    for(var i=0;i<=(array.length-1);i++) {
        ctx.fillStyle = randomColor(255,255);
        //随机生成字体颜色
        // ctx.font = randomNum(15,40) +'px SimHei';
        ctx.font = 30 +'px SimHei';  //设置字体大小
        //随机生成字体大小
        var x =15 +i *25; //x坐标
        var y = randomNum(25,35); // y坐标
        y = 30;
        // var deg = randomNum(-45,45);
        var deg= 30;
        //修改坐标原点和旋转角度
        ctx.translate(x, y);
        ctx.rotate(deg * Math.PI /180);  //设置字体旋转角度
        // ctx.rotate(deg * Math.PI /180);
        ctx.fillText(array[i],0,0);
        //恢复坐标原点和旋转角度
        ctx.rotate(-deg * Math.PI /180);
        ctx.translate(-x, -y);
    }
    return vcode;
}
//检查验证码是否正确
function checkValidateCode(inputCodeId)
{
    //获取输入的验证码
    var inputCode = document.getElementById(inputCodeId).value;
    if (inputCode.toUpperCase() == validateCodeCA.toUpperCase() || inputCode.toUpperCase() == validateCode.toUpperCase() )
    {
       return true;
    }
    return false;
}

效果:

 

 


版权声明:本文为m15732622413原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。