美文网首页软件测试入门到精通
乐搏自动化测试 - 知己知彼之验证码原理

乐搏自动化测试 - 知己知彼之验证码原理

作者: 乐老师TestPro | 来源:发表于2019-06-07 00:24 被阅读42次

    前篇文讲到了实际工作中验证码的处理方法,接下来一起看一下验证码的生成原理,以及验证码是如何在后台进行验证的。

    先说一下原理:

    直接验证码的原理

    image

    短信验证码的原理:

    image

    无论是哪种验证码的生成,过程都是:

    1、客户端访问了需要验证码的页面

    2、后台调用验证码生成代码先生成一个验证码,先将生成的验证码存放起来以便后续校验,然后再将这个验证码和用户的请求页面一起发给客户

    3、客户填写了必要信息及验证码后,再将页面信息一并提交给服务器,服务器在后台将用户填写的验证码与2中存储的验证码进行对比

    问题是这个生成的验证码如何存储?

    对于后台的验证码存储,一般是存放到Seesion域中,下面请看简单代码演示传统Servlet代码:

    生成验证码的工具类定义:

    @WebServlet(name = "VerifyCodeServlet", urlPatterns = "/code")
    
    public class VerifyCodeServlet extends HttpServlet {
    
        //创建一个随机类
        private Random ran = new Random();
    
        //写一个方法随机生成一种颜色
        private Color getRandomColor() {
    
            //随机生成0~255之间的数
            int red = ran.nextInt(256);
    
            int green = ran.nextInt(256);
    
            int blue = ran.nextInt(256);
    
            return new Color(red, green, blue);
        }
    
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
            //1\. 创建缓存图片
            int width = 90, height = 30;
    
            BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    
            //2\. 获取画笔对象
            Graphics graphics = img.getGraphics();
    
            //3\. 设置画笔颜色
            graphics.setColor(Color.WHITE);
    
            //4\. 填充矩形区域
            graphics.fillRect(0, 0, width, height);
    
            //5\. 从字符数组中随机得到字符
            char[] arr = { 'A', 'B', 'C', 'D', 'N', 'E', 'W', 'b', 'o', 'y', '1', '2', '3', '4','5','6' };
    
            //6\. 循环4次,画4个字符
            StringBuilder sb = new StringBuilder();
    
            for (int i = 0; i < 4; i++) {
    
                //7\. 设置字的颜色为随机
                graphics.setColor(getRandomColor());
    
                //8\. 设置字体,大小为19
                graphics.setFont(new Font(Font.SANS_SERIF,Font.BOLD+Font.ITALIC,19));
    
                //随机得到下标
                int index = ran.nextInt(arr.length);
    
                char c = arr[index];
    
                //将循环得到的字符拼接成字符串
                sb.append(c);
    
                //9\. 将每个字符画到图片,x增加,y不变。
                graphics.drawString(String.valueOf(c),10+(i*20), 20);
            }
    
            //将验证码以字符串的方式放到会话域中
            HttpSession session = request.getSession();
    
            System.out.println("验证码:"+sb.toString());
    
            session.setAttribute("code",sb.toString());
    
            //11\. 画8条干扰线,每条线的颜色不同
            for (int i = 0; i < 8; i++) {
    
                //10\. 线的位置是随机的,x范围在width之中,y的范围在height之中。
                int x1 = ran.nextInt(width);
    
                int y1 = ran.nextInt(height);
    
                int x2 = ran.nextInt(width);
    
                int y2 = ran.nextInt(height);
    
                graphics.setColor(getRandomColor());
    
                graphics.drawLine(x1,y1,x2,y2);
            }
    
            //12\. 将缓存的图片输出到响应输出流中
            ImageIO.write(img,"jpg",response.getOutputStream());
        }
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.doPost(request, response);
        }
    }
    

    需要验证码的用户登录代码:

    @WebServlet(name = "LoginServlet", urlPatterns = "/login")
    
    public class LoginServlet extends HttpServlet {
    
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
    response.setContentType("application/json");
    
    // 得到Session对象
    HttpSession session = request.getSession();
    
    // 得到session中的验证码,该码和UI上显示的一致
    String sessionCode = (String) session.getAttribute("code");
    
    // 得到登陆页面上输入的验证码
    String requesCode = request.getParameter("code");
    
    // 用户输入的验证码与session中的验证码进行比较
    if ((! requesCode.equalsIgnoreCase(sessionCode))) {
    
    //用户输入的验证码也seesion中的不一致,将错误信息记录到session中,再重定向回登录页面,提示错误
    session.setAttribute("errorMsg", "验证码错误!");
    
    session.setAttribute("username", username);
    
    response.sendRedirect("login.jsp");
    
    return;
    }
    

    如果要设置万能验证码,只需将“if ((! requesCode.equalsIgnoreCase(sessionCode)))” 这一行代码改为以下即可:

    if ((! requesCode.equalsIgnoreCase(sessionCode)) **|| ("123456".equals(sessionCode))**){ ....}
    

    通过以上的简单后台代码,我们就可以完全理解了一般的验证码生成与验证原理,以及如何设置“万能验证码”,从而方便我们测试
    前端代码:

    <%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
    
    <!DOCTYPE html>
      <html>
      <head>
      <meta charset="utf-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <title>会员登录</title>
      <link rel="stylesheet" href="css/bootstrap.min.css" type="text/css" />
      <script src="js/jquery-1.11.3.min.js" type="text/javascript"></script>
      <script src="js/bootstrap.min.js" type="text/javascript"></script>
      <!-- 引入自定义css文件 style.css -->
      <link rel="stylesheet" href="css/style.css" type="text/css" />
      <style>
      body {
              margin-top: 20px;
              margin: 0 auto;
    }
    .carousel-inner .item img {
              width: 100%;
              height: 300px;
    }
    font {
              color: #666;
              font-size: 22px;
              font-weight: normal;
              padding-right: 17px;
      }
      </style>
    </head>
    <body>
    <c:if test="${not empty errorMsg  }">
    <script type="text/javascript">
    alert("${errorMsg}")
    </script>
    </c:if>
    <div class="container-fluid">
    <div class="col-md-4">
    <img src="./img/lebo-logo.png" />
    <div class="line"></div>
    </div>
    <div class="col-md-3" style="padding-top:20px">
    <ol class="list-inline">
    <!-- <li><a href="login.htm">登录</a> </li>
    <li><a href="register.htm">注册</a> </li> -->
    </ol>
    </div>
    </div>
    <div class="container-fluid">
    <nav class="navbar navbar-inverse">
    <div class="container-fluid">
    <!-- Brand and toggle get grouped for better mobile display -->
    <div class="navbar-header">
    <button type="button" class="navbar-toggle collapsed"
    data-toggle="collapse" data-target="#bs-example-navbar-collapse-1"
    aria-expanded="false">
    <span class="sr-only">Toggle navigation</span> 
    <span class="icon-bar"></span> <span class="icon-bar"></span> 
    <span class="icon-bar"></span>
    </button>
    <a class="navbar-brand" href="#">首页</a>
    </div>
    <!-- Collect the nav links, forms, and other content for toggling -->
    <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
    <ul class="nav navbar-nav">
    <li class="active"><a href="#">班次信息<span class="sr-only">(current)</span>
    </a>
    </li>
    <li><a href="#">师资力量</a>
    </li>
    </ul>
    <form class="navbar-form navbar-right" role="search">
    <div class="form-group">
    <input type="text" class="form-control" placeholder="Search">
    </div>
    <button type="submit" class="btn btn-default">Submit</button>
    </form>
    </div>
    <!-- /.navbar-collapse -->
    </div>
    <!-- /.container-fluid -->
    </nav>
    </div>
    <div class="container" style="width:100%;height:460px;background:#FF2C4C url('img/loginbg.jpg') no-repeat;">
    <div class="row">
    <div class="col-md-7">
    </div>
    <div class="col-md-5">
    <div style="width:440px;border:1px solid #E7E7E7;padding:20px 0 20px 30px;border-radius:5px;margin-top:60px;background:#fff;">
    <font>学员登录</font>
    <div>&nbsp;</div>
    <form class="form-horizontal" action="${pageContext.request.contextPath }/login" method="post">
    <div class="form-group">
    <label for="username" class="col-sm-2 control-label">用户名</label>
    <div class="col-sm-6">
    <input type="text" class="form-control" name="username" value="${username}" id="username" placeholder="请输入用户名">
    </div>
    </div>
    <div class="form-group">
    <label for="password" class="col-sm-2 control-label">密&nbsp;&nbsp 码</label>
    <div class="col-sm-6">
    <input type="password" class="form-control" name="password" id="password" placeholder="请输入密码">
    </div>
    </div>
    <div class="form-group">
    <label for="code" class="col-sm-2 control-label">验证码</label>
    <div class="col-sm-3">
    <input type="text" name="code" class="form-control" id="code" placeholder="输入验证码">
    </div>
    <div class="col-sm-3">
    <img src="${pageContext.request.contextPath }/verifyCode" title="换一张" style="cursor: pointer" id="imgcode" />
    <script type="text/javascript">
    document.getElementById("imgcode").onclick = function(
    ev) {
    this.src = "${pageContext.request.contextPath }/verifyCode?t=" + Math.random();
    }
    </script>
    </div>
    </div>
    <div class="form-group">
    <div class="col-sm-offset-2 col-sm-10">
    <div class="checkbox">
    <label> <input type="checkbox" name="autoLogin" value="yes"> 自动登录 </label>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <label>
    <input type="checkbox" name="remember" value="yes"> 记住用户名 </label>
    </div>
    </div>
    </div>
    <div class="form-group">
    <div class="col-sm-offset-2 col-sm-10">
    <input type="submit" width="100" value="" border="0"
    style="background: url('./img/login.png') no-repeat scroll 0 0 rgba(0, 0, 0, 0); height:41px;width:120px;color:white;">
    </div>
    </div>
    </form>
    </div>
    </div>
    </div>
    </div>
    <div style="text-align: center;margin-top: 5px;">
    <ul class="list-inline">
    <li><a>关于我们</a>
    </li>
    <li><a>联系我们</a>
    </li>
    <li><a>招贤纳士</a>
    </li>
    <li><a>法律声明</a>
    </li>
    <li><a>友情链接</a>
    </li>
    <li><a target="_blank">支付方式</a>
    </li>
    <li><a>服务声明</a>
    </li>
    <li><a>广告声明</a>
    </li>
    </ul>
    </div>
    <div style="text-align: center;margin-top: 5px;margin-bottom:20px;">
    Copyright &copy; 2005-2019</div>
    </body>
    </html>
    

    对于比较复杂的场景,如,在分布式和集群的环境中,具体的处理方式不尽相同,分布式环境中一般将在后台生成的验证码以一个Key-Vlaue的形式进行处理,Key是一个随机生成的“token”,Value是真正生成的验证码,然后将验证码的Key-Value放入Redis缓存中,再将Key传递给客户端。

    进行后台校验时,先根据客户端传递的Key得到客户端填写的验证码,再从Redis中将token对应的Value取出进行校验。

    无论哪个方法,其原理都是相通的,在我们测试的过程中,如果需要设置万能验证码,或是城将验证码暂时去掉,我们做为测试人员,在和开发沟通的过程中,应该做到心中有数,进行有效的沟通。

    每天持续更新,软件测试知识!

    [如有转载,请联系博主!]

    相关文章

      网友评论

        本文标题:乐搏自动化测试 - 知己知彼之验证码原理

        本文链接:https://www.haomeiwen.com/subject/yiipxctx.html