文本验证码
用户需要识别并输入图片中显示的随机字符或数字组合,通常用于验证用户是人类而非自动化程序。
优点
实现简单:技术难度较低,容易在各种网站和应用中部署。
兼容性好:几乎所有的设备和浏览器都能支持,无需额外的插件或特殊设置。
缺点
易被破解:随着图像识别技术的发展,一些自动化程序能够较容易地识别和破解简单的文本验证码。
用户体验差:对于视力不好或有阅读障碍的用户来说,识别扭曲、模糊的字符可能会有困难,影响使用体验。
1.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta http-equiv="Access-Control-Allow-Origin" content="*">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<meta name="format-detection" content="telephone=no">
<link rel="stylesheet" href="/static/lib/layui/css/layui.css" media="all">
<link rel="stylesheet" href="/static/css/verify.css" media="all">
</head>
<body>
<div class="layui-container">
<div class="admin-login-background">
<div class="layui-form login-form">
<form class="layui-form" action="">
<div class="layui-form-item logo-title">
<h1>文本验证码演示</h1>
</div>
<div class="layui-form-item">
<label class="layui-icon layui-icon-username"></label>
<input type="text" name="uname" maxlength="11" lay-verify="required" placeholder="用户名或者手机号"
autocomplete="off" class="layui-input" value="">
</div>
<div class="layui-form-item">
<label class="layui-icon layui-icon-password"></label>
<input type="password" name="ipassword" maxlength="20" lay-verify="password" placeholder="密码"
autocomplete="off" class="layui-input" value="">
</div>
<div class="layui-form-item">
<label class="layui-icon layui-icon-vercode"></label>
<input type="text" id="captcha" name="captcha" lay-verify="captcha" placeholder="图形验证码"
autocomplete="off" class="layui-input verification captcha" value="" maxlength="5">
<div class="captcha-img">
<img id="captchaPic" src="/api/v1/verify/getTextVerify">
</div>
</div>
<div class="layui-form-item">
<button type="button" id="submitBtn" class="layui-btn layui-btn layui-btn-normal layui-btn-fluid"
lay-submit="" lay-filter="login">登 录
</button>
</div>
</form>
</div>
</div>
</div>
<script src="/static/lib/jquery/jquery.min.js" charset="utf-8"></script>
<script src="/static/lib/layui/layui.js" charset="utf-8"></script>
<script src="/static/js/ajax.js"></script>
<script src="/static/api/text_verify.js"></script>
</body>
</html>
2.verify.css
html, body {
width: 100%;
height: 100%;
overflow: hidden;
}
body {
background-image: url("/static/images/loginbg.png");
background-size: cover;
}
.layui-container {
width: 100%;
height: 100%;
overflow: hidden;
background-image: url("/static/images/loginbg.png");
background-repeat: repeat;
}
.admin-login-background {
z-index: 99999;
width: 500px;
height: 350px;
position: fixed;
top: 0px;
left: 0px;
right: 0px;
bottom: 200px;
margin: auto;
}
.logo-title {
text-align: center;
letter-spacing: 2px;
padding: 14px 0;
}
.logo-title h1 {
color: #1E9FFF;
font-size: 25px;
font-weight: bold;
}
.login-form {
background-color: #fff;
border: 1px solid #fff;
border-radius: 3px;
padding: 14px 20px;
box-shadow: 0 0 8px #eeeeee;;
width: 500px;
height: 350px;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
margin: auto;
}
.login-form .layui-form-item {
position: relative;
}
.login-form .layui-form-item label {
position: absolute;
left: 1px;
top: 1px;
width: 38px;
line-height: 36px;
text-align: center;
color: #d2d2d2;
}
.login-form .layui-form-item input {
padding-left: 36px;
font-size: 15px;
}
.captcha {
width: 66%;
display: inline-block;
}
.captcha-img {
display: inline-block;
width: 158px;
float: right;
margin-right: 3px;
}
.captcha-img img {
height: 34px;
border: 1px solid #e6e6e6;
height: 36px;
width: 100%;
cursor: pointer
}
.copyright {
text-align: center;
color: #1E9FFF;
line-height: 50px;
}
text_verify.js
layui.use(['form'], function () {
var form = layui.form;
var layer = layui.layer;
var setter = layui.setter;
var iphone_reg = /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/;//包含最新手机号码的正则表达式验证
var pass_reg = /^\w+$/;//由数字、26个英文字母或者下划线组成的8位以上字符串
var base_reg = /^[A-Za-z0-9]+$/;//字母和数字
// 登录过期的时候,跳出ifram框架
if (top.location !== self.location) top.location = self.location;
//文本框回车提交
$(".layui-input").keydown(function (e) {
if (e.keyCode === 13) {
$("#submitBtn").trigger("click");
}
});
form.verify({
account: function (values) {
if (!iphone_reg.test(values)) {
return "请使用手机号码进行登录";
}
},
password: function (values) {
if (!pass_reg.test(values)) {
return "必须包含大小写字母和数字的组合,不能使用特殊字符,长度不小于8位";
}
},
captcha: function (values) {
if (values.length !== 5) {
return "请输入5位长度的验证码";
}
if (!base_reg.test(values)) {
return "请输入5位长度的验证码";
}
}
})
$("#captchaPic").click(function () {
$(this).attr("src", "/api/v1/verify/getTextVerify?t=" + Math.random());
})
// 进行登录操作
form.on('submit(login)', function (data) {
var td = data.field;
ajax.request({
method: "/user/doTextVerifyLogin",
type: "post",
callback: function (res) {
layer.msg(res.msg, {icon: res.code === 0 ? 1 : 5, time: 1000}, function () {
if (res.code === 0) {
layui.data("webapp", {
key: "data",
value: res.data
});
} else {
$("#captcha").val("");
$("#captchaPic").trigger("click");
}
});
}
}, td);
return false;
});
});
3.text_verify.js
layui.use(['form'], function () {
var form = layui.form;
var layer = layui.layer;
var setter = layui.setter;
var iphone_reg = /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/;//包含最新手机号码的正则表达式验证
var pass_reg = /^\w+$/;//由数字、26个英文字母或者下划线组成的8位以上字符串
var base_reg = /^[A-Za-z0-9]+$/;//字母和数字
// 登录过期的时候,跳出ifram框架
if (top.location !== self.location) top.location = self.location;
//文本框回车提交
$(".layui-input").keydown(function (e) {
if (e.keyCode === 13) {
$("#submitBtn").trigger("click");
}
});
form.verify({
account: function (values) {
if (!iphone_reg.test(values)) {
return "请使用手机号码进行登录";
}
},
password: function (values) {
if (!pass_reg.test(values)) {
return "必须包含大小写字母和数字的组合,不能使用特殊字符,长度不小于8位";
}
},
captcha: function (values) {
if (values.length !== 5) {
return "请输入5位长度的验证码";
}
if (!base_reg.test(values)) {
return "请输入5位长度的验证码";
}
}
})
$("#captchaPic").click(function () {
$(this).attr("src", "/api/v1/verify/getTextVerify?t=" + Math.random());
})
// 进行登录操作
form.on('submit(login)', function (data) {
var td = data.field;
ajax.request({
method: "/user/doTextVerifyLogin",
type: "post",
callback: function (res) {
layer.msg(res.msg, {icon: res.code === 0 ? 1 : 5, time: 1000}, function () {
if (res.code === 0) {
layui.data("webapp", {
key: "data",
value: res.data
});
} else {
$("#captcha").val("");
$("#captchaPic").trigger("click");
}
});
}
}, td);
return false;
});
});
4.java
@RequestMapping(value = "/getTextVerify")
public void getverify(HttpServletRequest request, HttpServletResponse response) {
try {
TextVerifyUtil verifyUtil = new TextVerifyUtil();
response.setContentType("image/jpeg");
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expire", 0);
verifyUtil.getRandomCode(request, response);//输出验证码图片方法
} catch (Exception e) {
e.printStackTrace();
}
}
5.TextVerifyUtil
package com.cn.springboot.utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;
public class TextVerifyUtil {
public static final String RANDOMCODEKEY = "TEXT_VERIFY";//放到session中的key
private static final Logger logger = LoggerFactory.getLogger(TextVerifyUtil.class);
private String randString = "123456789ABCDEFGHIJKLMNPQRSTUVWXYZ";//随机产生数字与字母组合的字符串
private int width = 120;// 图片宽
private int height = 35;// 图片高
private int lineSize = 40;// 干扰线数量
private int stringNum = 5;// 随机产生字符数量
private int fontSize=24;//字号大小
private Random random = new Random();
/**
* 获得字体
*/
public TextVerifyUtil(int width, int height, int lineSize, int stringNum){
this.width=width;
this.height=height;
this.lineSize=lineSize;
this.stringNum=stringNum;
}
public TextVerifyUtil(){
}
/**
* 生成随机图片
*/
public void getRandomCode(HttpServletRequest request, HttpServletResponse response) {
HttpSession session = request.getSession();
// BufferedImage类是具有缓冲区的Image类,Image类是用于描述图像信息的类
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
// 产生Image对象的Graphics对象,改对象可以在图像上进行各种绘制操作
Graphics g = image.getGraphics();
g.fillRect(0, 0, width, height);//图片大小
g.setFont(new Font("Times New Roman", Font.ROMAN_BASELINE, fontSize));//字体大小
g.setColor(getRandColor(110, 133));//字体颜色
// 绘制干扰线
for (int i = 0; i <= lineSize; i++) {
drawLine(g);
}
// 绘制随机字符
String randomString = "";
for (int i = 1; i <= stringNum; i++) {
randomString = drawString(g, randomString, i);
}
//将生成的随机字符串保存到session中
session.removeAttribute(RANDOMCODEKEY);
session.setAttribute(RANDOMCODEKEY, randomString);
g.dispose();
try {
// 将内存中的图片通过流动形式输出到客户端
ImageIO.write(image, "JPEG", response.getOutputStream());
} catch (Exception e) {
logger.error("将内存中的图片通过流动形式输出到客户端失败>>>> ", e);
}
}
/**
* 获得颜色
*/
private Color getRandColor(int fc, int bc) {
if (fc > 255)
fc = 255;
if (bc > 255)
bc = 255;
int r = fc + random.nextInt(bc - fc - 16);
int g = fc + random.nextInt(bc - fc - 14);
int b = fc + random.nextInt(bc - fc - 18);
return new Color(r, g, b);
}
/**
* 绘制字符串
*/
private String drawString(Graphics g, String randomString, int i) {
g.setFont(getFont());
g.setColor(new Color(random.nextInt(101), random.nextInt(111), random.nextInt(121)));
String rand = String.valueOf(getRandomString(random.nextInt(randString.length())));
randomString += rand;
g.translate(random.nextInt(3), random.nextInt(3));
g.drawString(rand, 18 * i, fontSize);
return randomString;
}
private Font getFont() {
return new Font("Fixedsys", Font.CENTER_BASELINE, fontSize);
}
/**
* 绘制干扰线
*/
private void drawLine(Graphics g) {
int x = random.nextInt(width);
int y = random.nextInt(height);
int xl = random.nextInt(13);
int yl = random.nextInt(15);
g.drawLine(x, y, x + xl, y + yl);
}
/**
* 获取随机的字符
*/
public String getRandomString(int num) {
return String.valueOf(randString.charAt(num));
}
}