乐搏自动化测试 - 知己知彼之验证码原理
前篇文讲到了实际工作中验证码的处理方法,接下来一起看一下验证码的生成原理,以及验证码是如何在后台进行验证的。
先说一下原理:
直接验证码的原理
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> </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">密   码</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> <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 © 2005-2019</div>
</body>
</html>
对于比较复杂的场景,如,在分布式和集群的环境中,具体的处理方式不尽相同,分布式环境中一般将在后台生成的验证码以一个Key-Vlaue的形式进行处理,Key是一个随机生成的“token”,Value是真正生成的验证码,然后将验证码的Key-Value放入Redis缓存中,再将Key传递给客户端。
进行后台校验时,先根据客户端传递的Key得到客户端填写的验证码,再从Redis中将token对应的Value取出进行校验。
无论哪个方法,其原理都是相通的,在我们测试的过程中,如果需要设置万能验证码,或是城将验证码暂时去掉,我们做为测试人员,在和开发沟通的过程中,应该做到心中有数,进行有效的沟通。
每天持续更新,软件测试知识!
[如有转载,请联系博主!]