软件测试入门到精通

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

2019-06-07  本文已影响42人  乐老师TestPro

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

先说一下原理:

直接验证码的原理

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取出进行校验。

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

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

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

上一篇下一篇

猜你喜欢

热点阅读