图形验证码+短信验证码【Java应用实例】

短信验证码 同时被 2 个专栏收录
8 篇文章 0 订阅

一、图形验证码的实现

1.1 简介

常在网上晃悠的人,对下面这张图都不会陌生。特别是在注册新账号、确认交易时,它们都会频繁出现,要求我们输入正确的验证码,那这些看上去跟我们要做的事情完全无关的验证码到底有何作用呢?

其实,验证码的校验即是一个直接的图灵测试,简单的逻辑是:服务提供者向用户方提出一个较为复杂的问题,能正确回答的即是人类,回答不出的即是机器或者程序代码。这个概念基于这样一个重要假设:提出的问题要容易被人类解答,并且让机器无法解答。在当前的条件下,准确识别扭曲的图形,对于机器来说还是一个比较艰难的任务,而对于人来说,则相对可以接受。yahoo则在自己的邮箱登过程中全世界首创的应用了图形化验证码这个产品,很快解决了yahoo邮箱上的垃圾邮件问题,从此图形类验证码开始在互联网世界无处不在。

1.2 图形验证码实现

图形验证码前后台交互流程比较简单,主要分为以下三步:

  • 客户端请求到页面的同时,向服务端发起请求,服务端生成验证码将验证码字符,存入到session中以备客户端的校验。与此同时,服务端将生成的验证码图形给到前端;
  • 前端获取到验证码图形,渲染展示到页面。用户识别图形验证码后,提交验证码的字符到服务端;
  • 服务端接收到验证码校验的请求,将接收到的字符与session中存储的验证码字符进行比对,并将比对结果返回到前端。

大致流程如下图所示:

实现流程如下:

1.展示验证码已经提交验证码字符的页面简单结构

<html>
 <head></head>
 <body>
  <div class="form-group col-lg-6"> 
   <label for="id" class="col-sm-4 control-label"> 验证码: </label> 
   <div class="col-sm-8"> 
    <input type="text" id="code" name="code" class="form-control" style="width:250px;" /> 
    <img id="imgObj" alt="验证码" src="/article/getValidateCode" onclick="changeImg()" /> 
    <a href="#" onclick="changeImg()">换一张</a> 
   </div> 
  </div> 
 </body>
</html>

2.前端请求获取验证码图片的js逻辑

< script type = "text/javascript" >
// 刷新图片 
function changeImg() {
	var imgSrc = $("#imgObj");
	var src = imgSrc.attr("src");
	imgSrc.attr("src", changeUrl(src));
}
//为了使每次生成图片不一致,即不让浏览器读缓存,所以需要加上时间戳 
function changeUrl(url) {
	var timestamp = (new Date()).valueOf();
	var index = url.indexOf("?", url);
	if (index > 0) {
		url = url.substring(0, url.indexOf(url, "?"));
	}
	if ((url.indexOf("&") >= 0)) {
		url = url + "×tamp=" + timestamp;
	} else {
		url = url + "?timestamp=" + timestamp;
	}
	return url;
} 
< /script>

3.后端接收前端获取验证码图片的请求,生成验证码并响应到前端

/**
   * 响应验证码页面
   * 
   * @author https://www.veesing.com
   */
  @RequestMapping(value = "/getValidateCode")
  public String getValidateCode(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // 设置响应的类型格式为图片格式
    response.setContentType("image/jpeg");
    // 禁止图像缓存。
    response.setHeader("Pragma", "no-cache");
    response.setHeader("Cache-Control", "no-cache");
    response.setDateHeader("Expires", 0);

    HttpSession session = request.getSession();

    ValidateCodeUtils vCode = new ValidateCodeUtils (120, 40, 5, 100);
    session.setAttribute("code", vCode.getCode());
    vCode.write(response.getOutputStream());
    return null;
  }

4.生成验证码的工具类ValidateCodeUtils

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Date;
import java.util.Random;
import javax.imageio.ImageIO;


/**
 * 验证码生成器
 *
 * @author https://www.veesing.com
 */
public class ValidateCodeUtils {
    // 图片的宽度。 
    private int width = 160;

    // 图片的高度。 
    private int height = 40;

    // 验证码字符个数 
    private int codeCount = 5;

    // 验证码干扰线数 
    private int lineCount = 150;

    // 验证码 
    private String code = null;

    // 验证码图片Buffer 
    private BufferedImage buffImg = null;

    // 验证码范围,去掉0(数字)和O(拼音)容易混淆的(小写的1和L也可以去掉,大写不用了) 
    private char[] codeSequence = {
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
            'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '1', '2', '3',
            '4', '5', '6', '7', '8', '9'
        };

    /**
     * 默认构造函数,设置默认参数
     */
    public ValidateCodeUtils () {
        this.createCode();
    }

    /**
     * @param width  图片宽
     * @param height 图片高
     */
    public ValidateCodeUtils (int width, int height) {
        this.width = width;
        this.height = height;
        this.createCode();
    }

    /**
     * @param width     图片宽
     * @param height    图片高
     * @param codeCount 字符个数
     * @param lineCount 干扰线条数
     */
    public ValidateCodeUtils (int width, int height, int codeCount, int lineCount) {
        this.width = width;
        this.height = height;
        this.codeCount = codeCount;
        this.lineCount = lineCount;
        this.createCode();
    }

    public void createCode() {
        int x = 0;
        int fontHeight = 0;
        int codeY = 0;
        int red = 0;
        int green = 0;
        int blue = 0;

        x = width / (codeCount + 2); // 每个字符的宽度(左右各空出一个字符)
        fontHeight = height - 2; // 字体的高度
        codeY = height - 4;

        // 图像buffer 
        buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

        Graphics2D g = buffImg.createGraphics();

        // 生成随机数 
        Random random = new Random();
        // 将图像填充为白色 
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, width, height);

        // 创建字体,可以修改为其它的 
        Font font = new Font("Fixedsys", Font.PLAIN, fontHeight);
        // Font font = new Font("Times New Roman", Font.ROMAN_BASELINE, fontHeight); 
        g.setFont(font);

        for (int i = 0; i < lineCount; i++) {
            // 设置随机开始和结束坐标 
            int xs = random.nextInt(width); // x坐标开始
            int ys = random.nextInt(height); // y坐标开始
            int xe = xs + random.nextInt(width / 8); // x坐标结束
            int ye = ys + random.nextInt(height / 8); // y坐标结束

            // 产生随机的颜色值,让输出的每个干扰线的颜色值都将不同。 
            red = random.nextInt(255);
            green = random.nextInt(255);
            blue = random.nextInt(255);
            g.setColor(new Color(red, green, blue));
            g.drawLine(xs, ys, xe, ye);
        }

        // randomCode记录随机产生的验证码 
        StringBuffer randomCode = new StringBuffer();

        // 随机产生codeCount个字符的验证码。 
        for (int i = 0; i < codeCount; i++) {
            String strRand = String.valueOf(codeSequence[random.nextInt(
                        codeSequence.length)]);
            // 产生随机的颜色值,让输出的每个字符的颜色值都将不同。 
            red = random.nextInt(255);
            green = random.nextInt(255);
            blue = random.nextInt(255);
            g.setColor(new Color(red, green, blue));
            g.drawString(strRand, (i + 1) * x, codeY);
            // 将产生的四个随机数组合在一起。 
            randomCode.append(strRand);
        }

        // 将四位数字的验证码保存到Session中。 
        code = randomCode.toString();
    }

    public void write(String path) throws IOException {
        OutputStream sos = new FileOutputStream(path);
        this.write(sos);
    }

    public void write(OutputStream sos) throws IOException {
        ImageIO.write(buffImg, "png", sos);
        sos.close();
    }

    public BufferedImage getBuffImg() {
        return buffImg;
    }

    public String getCode() {
        return code;
    }

    /**
     * 测试函数,默认生成到d盘
     *
     * @param args
     */
    public static void main(String[] args) {
        ValidateCodeUtils vCode = new ValidateCodeUtils (160, 40, 5, 150);

        try {
            String path = "D:/" + new Date().getTime() + ".png";
            System.out.println(vCode.getCode() + " >" + path);
            vCode.write(path);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

5.校验前端输入的验证码与服务端生成的验证码是否一致

String code = request.getParameter("code"); 
HttpSession session = request.getSession(); 
String sessionCode = (String) session.getAttribute("code"); 

//忽略验证码大小写 
if (!StringUtils.equalsIgnoreCase(code, sessionCode)) { 
    throw new RuntimeException("验证码对应不上code=" + code + " sessionCode=" + sessionCode); 
}

以上就是以Java SpringMVC的架构为例,说明Java代码如何生成图形验证码,如有疑问请访问 https://www.veesing.com 咨询。

二、短信验证码的实现

2.1 简介

图形验证码通常只能防止机器人脚本恶意攻击网站或App,如何识别到是本人操作的,还需要结合短信验证码进一步进行身份识别。本文以注册为例,在SpringMVC+Spring+Mybatis框架的基础上完成该短信验证码功能。

发送短信验证码的原理是:随机生成一个4-6位数字,将该4-6位数字保存到session当中,客户端通过sessionid判断对应的session,用户输入的验证码再与session记录的验证码进行比较。

一般的第三方短信平台都会有他们自己的短信接口,只要读懂他们的接口稍作稍作改变就能满足自己的需求。

首先将短信平台接口代码列出:这里需要依赖的三个通用jar包 commons-logging-1.1.1.jar,commons-httpclient-3.1.jar,commons-codec-1.4.jar。

2.2 短信服务商接入

开发短信验证码功能通常是采用第三方短信服务商的服务,作为短信下发渠道,以这个网站短信验证码为例:https://www.veesing.com。获取试用验证码条数和验证码接口文档,进行接口对接前需首先进行准备工作:

短信签名报备

短信验证码需预先设置短信签名,签名会经过平台审核,审核通过后才可作为接口参数使用。

短信验证码模板报备

短信验证码需预先设置短信模板,签名会经过平台审核,审核通过后才可作为接口参数使用。

获取appId和appKey 

为保障接口安全,短信验证码接口,使用多重加密的appId和appKey进行身份鉴权和校验,这两个字段作为验证码接口必填项,建议预先获取到,获取appKey时需要对注册人进行身份校验。

至此,短信服务商验证码接口已经准备完毕,接下来可以进行业务场景,进行短信验证码服务的开发,下面展示通用的短信验证码接入流程。

2.3 短信验证码代码实现

短信验证码前端较为简单,主要就是发送验证码和校验验证码两个ajax请求,结合上面的滑动验证可实现双重验证。前端页面结构如下:

<html>
<head>
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=0">
    <!-- 国内使用 -->
    <link rel="stylesheet" href="https://cdn.staticfile.org/amazeui/2.7.2/css/amazeui.min.css">
    <script type="text/javascript" charset="utf-8" src="https://cdn.staticfile.org/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
 
<div class="am-form">
    <div class="am-form-group">
        <label for="tel">请输入手机号</label>
        <input type="text" class="" id="tel" placeholder="请输入手机号">
    </div>
 
    <div id="__nc" style="height: 70px">
        <div id="nc"></div>
    </div>
 
    <div class="am-form-group">
        <label for="code">请输入验证码</label>
        <input id="code" type="text" placeholder="请输入验证码">
    </div>
 
    <button type="button" class="am-btn am-btn-primary">提交</button>
</div>
 
<script>
    var nc_token = ["CF_APP_1", (new Date()).getTime(), Math.random()].join(':');
    var nc=NoCaptcha.init({
        renderTo: '#nc',
        appkey: 'CF_APP_1',
        scene: 'register',
        token: nc_token,
        trans: {"key1": "code200"},
        elementID: ["usernameID"],
        is_Opt: 0,
        language: "cn",
        timeout: 10000,
        retryTimes: 5,
        errorTimes: 5,
        inline:false,
        apimap: {
// 'analyze': '//a.com/nocaptcha/analyze.jsonp',
// 'uab_Url': '//aeu.alicdn.com/js/uac/909.js',
        },
        bannerHidden:false,
        initHidden:false,
        callback: function (data) {
            window.console && console.log(nc_token)
            window.console && console.log(data.csessionid)
            window.console && console.log(data.sig);
            var tel = $('#tel').val();
            $.ajax({
                url: "sendCode",
                type: "post",
                data: {
                    tel:tel
                },
                dataType: "json",
                success: function (result) {
                    if (result.code == 0) {
                        alert("验证码已发送!", "green")
                    } else {
                        alert("发送失败,请稍后再试!");
                        nc.reset()
                    }
                },
                error: function () {
                    alert("系统繁忙,请稍后再试!", "red")
                }
            })
        },
        error: function (s) {
        }
    });
    NoCaptcha.setEnabled(true);
    nc.reset();//请务必确保这里调用一次reset()方法
 
    NoCaptcha.upLang('cn', {
        'LOADING':"加载中...",//加载
        'SLIDER_LABEL': "请向右滑动验证",//等待滑动
        'CHECK_Y':"验证通过",//通过
        'ERROR_TITLE':"非常抱歉,这出错了...",//拦截
        'CHECK_N':"验证未通过", //准备唤醒二次验证
        'OVERLAY_INFORM':"经检测你当前操作环境存在风险,请输入验证码",//二次验证
        'TIPS_TITLE':"验证码错误,请重新输入"//验证码输错时的提示
    });
</script>
</body>
</html>

后端代码主要职责是两方面:1)接收生成验证码请求,生成验证码存入session中;2)接收校验验证码的请求,将存入session的验证码去除,与前端输入的验证码进行比对,比对一致则通过并进行视图转发,比对不一致则告知前端错误原因。主要实现代码如下:

HttpSession session = req.getSession();
// 验证码有效时间
session.setMaxInactiveInterval(600);
try {
    Integer num = RandNumber.getNum();
    //  发送验证码通道
    Sendsms.Send(num, phone);
    session.setAttribute(phone, num);
    return R.ok();
} catch (Exception e) {
    e.printStackTrace();
    logger.error(e.getMessage());
    return R.error("fasle");
}
import java.io.Exception;
 
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.PostMethod;
 
public class Sendsms {
 
  private static String Url = "https://vip.veesing.com/smsApi/verifyCode";
 
  // 发送短信验证码
  public static void Send(Integer num, String mobile) {
    try {
    HttpClient client = new HttpClient();
    PostMethod method = new PostMethod(Url);
    client.getParams().setContentCharset("UTF-8");
    method.setRequestHeader("ContentType", "application/x-www-form-urlencoded;charset=UTF-8");
    NameValuePair[] data = { 
        new NameValuePair("appId", "*********"),
        new NameValuePair("appKey", "**********"), 
        new NameValuePair("templateId", "*******"), 
        new NameValuePair("mobile", "*******"),
        new NameValuePair("variables", "*******") 
        };
    method.setRequestBody(data);
    client.executeMethod(method);
    String submitResult = method.getResponseBodyAsString();
    System.out.println(submitResult);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
 
}
HttpSession session = req.getSession();
String yzm = String.valueOf(session.getAttribute(username));
logger.info(yzm);
if (yzm == null) {
    return R.error("验证码错误");
}
if (yzm != null && !verifycode.equals(yzm)) {
    return R.error("验证码错误");
}

以上就是Java实现图形验证码与短信验证码的全部实现,基于此案例,可实现安全性高、用户体验好的登录注册+图形验证码+短信验证码功能。

如有问题可在留言区展开讨论哦,欢迎转发,如有改动请站内信通知本文作者,谢谢!

  • 2
    点赞
  • 2
    评论
  • 29
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

veesing

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值