前言
如今,无论是移动端还是网页端,验证码随处可见,例如:手机号登录会有短信验证码、手机消费支付会有指纹验证、网页端发表评论也有验证、哔哩哔哩弹幕网会有算术验证、京东账号密码登录会有滑块拼图验证、谷歌登录也有图片识别验证、银行业务需要身份证或人脸认证。这众多的验证方式,目的是验证用户是人类还是机器,防止恶意注册、登录、发帖、领优惠券、投票等等;防止用户疯狂占用服务器资源;在用户更改密码、异地登录时,提高账号安全性...
验证码分类
随机字符串验证码
这是最常见的形式,就是随机生成由大写字母或小写字母或数字或汉字组成的字符串。优点:生成方便,识别难易度可调节。缺点:识别度不好掌握,因为可辨识度高了容易被机器识别,可辨识度低了会造成用户体验差(图像识别上机器已经在某种条件下超越了人类)。
实现
这是一个简单的 jsp
页面:
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<title>Java-随机字符串验证码</title>
<style type="text/css">
.code_a {
color: #3498db;
font-size: 12px;
text-decoration: none;
cursor: pointer;
}
#imgCode {
cursor: pointer;
}
</style>
<script type="text/javascript">
function changeCode(){
var imgCode = document.getElementById("imgCode");
// 为了达到刷新的效果,需要每一次访问路径不同
imgCode.src = "randomCodeServlet?" + Math.random();
}
</script>
</head>
<body>
<form action="valiCodeServlet" method="post">
<label>验证码:</label>
<input type="text" id="inCode" name="inCode" />
<%-- 图片引用为 randomCodeServlet 的访问路径 --%>
<img src="randomCodeServlet" align="center" id="imgCode" onclick="changeCode()" />
<a class="code_a" onclick="changeCode()">看不清?换一张</a>
<br />
<input type="submit" value="登录" />
</form>
<!-- 错误信息提示 -->
<div style="color: #e74c3c;">${error}</div>
</body>
</html>
生成验证码字符串的 randomCodeServlet
程序:
import javax.imageio.ImageIO;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
// 使用 @webServlet 注解
@WebServlet(name = "randomCodeServlet", value = "/randomCodeServlet")
public class randomCodeServlet extends HttpServlet {
// 因为会重复使用,所以定义为全局变量
private int width = 80;
private int height =30;
private int fontSize = 12;
private Random random = new Random();
private String str = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
// 随机组合颜色
private Color randomColor(){
// 三基色范围 0-255
int red = random.nextInt(256);
int green = random.nextInt(256);
int blue = random.nextInt(256);
return new Color(red, green, blue);
}
// 随机组合字符串
private String randomStr(int len){
if (len < 4){
len = 4;
}
// 更改图片的宽度以适应字符数量
width = 5+fontSize*len;
String code = "";
// 增加难度-最少 4个
for (int i = 0; i < len; i++){
code += str.charAt(random.nextInt(str.length()));
}
return code;
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 创建画板 参数:高度,宽度,图片样式
BufferedImage bufImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// 2. 创建画笔
Graphics2D pen = (Graphics2D) bufImg.getGraphics();
// 3. 生成随机内容
String codeStr = randomStr(4);
// 3.1. 保存生成的随机码
request.getSession().setAttribute("valiCode", codeStr);
// 4. 绘制内容
// 4.1. 设置绘制区域
pen.fillRect(0, 0, width, height);
// 4.2. 设置字体
pen.setFont(new Font("微软雅黑", Font.BOLD, fontSize));
// 4.3. 按照一定顺序逐个绘制字符
for (int x = 0; x < codeStr.length(); x++){
// 每一个字体颜色都随机
pen.setColor(randomColor());
pen.drawString(codeStr.charAt(x)+"", 5+x*fontSize, (fontSize+height)/2);
}
// 4.4. 绘制噪音线 用于识别困难度
for (int i = 0; i < 3; i++){
pen.setColor(randomColor());
// 设置线条的粗细 数字越大越粗
pen.setStroke(new BasicStroke(1));
pen.drawLine(random.nextInt(width/2), random.nextInt(height),
random.nextInt(width), random.nextInt(height));
}
// 5. 另存为图片发送
ServletOutputStream out = response.getOutputStream();
ImageIO.write(bufImg, "png", out);
// 刷新缓存
out.flush();
out.close();
}
}
验证用户输入的验证码的 valiCodeServlet
程序:
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
import java.util.Locale;
// valiCodeServlet 用于验证验证码
@WebServlet(name = "valiCodeServlet", value = "/valiCodeServlet")
public class valiCodeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 得到用户输入的验证码数据 转换为字符串 小写
String inCode = request.getParameter("inCode").toString().toLowerCase();
// 1.1。读取存进 session 的验证码
String valiCode = request.getSession().getAttribute("valiCode").toString().toLowerCase();
// 2. 验证是否正确
if (inCode.equals(valiCode)){
// 跳转到 index.jsp
response.sendRedirect("index.jsp");
}else {
request.getSession().setAttribute("error", "验证码错误!请重新输入。");
// 返回上一页
String url = request.getHeader("Referer");
response.sendRedirect(url);
}
}
}
部署到 Tomcat 后启动,在浏览器访问其路径即可。
其中,生成随机验证码的部分可以使用 jQuery 代替,用 canvas 标签代替 img 标签。jsp
页面:
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<title>Java-随机字符串验证码V2</title>
<style type="text/css">
.code_b {
color: #3498db;
font-size: 12px;
text-decoration: none;
cursor: pointer;
}
#cvs {
cursor: pointer;
}
</style>
<script type="text/javascript" src="JS/randomV2.js" charset="UTF-8"></script>
<script type="text/javascript">
var valicode2;
function changeCode() {
var cvs = document.getElementById("cvs");
valicode2 = drawCode(cvs);
}
// 验证用户输入
function valiCode() {
var inputCode = document.getElementById("inCode").value;
// 可能都是空的 null
if (inputCode.toLowerCase() === valicode2.toLowerCase()) {
return true;
} else {
document.getElementById("err").innerHTML = "验证码错误!请重新输入。";
// 更换验证码
changeCode();
// 为了不刷新页面
return false;
}
}
// 初始化就加载验证码
window.onload = changeCode;
</script>
</head>
<body>
<form action="index.jsp" method="post">
<label>验证码:</label>
<label for="inCode"></label><input type="text" id="inCode" name="inCode"/>
<canvas id="cvs" onclick="changeCode()"></canvas>
<a class="code_b" onclick="changeCode()">看不清?换一张</a>
<br/>
<input type="submit" value="登录" onclick="return valiCode()"/>
</form>
<!-- 错误信息提示 -->
<div style="color: #e74c3c;" id="err"></div>
</body>
</html>
生成随机验证码的 randomV2
JavaScript 程序:
// 这个是使用 JS 实现随机字符串验证码
// 定义全局变量
var width = 80;
const height = 24;
const fontSize = height - 6;
const strTxt = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
// 自定义生成随机整数
function randomInt(max) {
return Math.floor(Math.random() * 100000 % max);
}
// 生成随机长度的字符串验证码
function randomCode(len) {
// 最少生成4个
if (len < 4) {
len = 4;
}
let code = "";
for (let i = 0; i < len; i++) {
code += strTxt.charAt(randomInt(strTxt.length));
}
return code;
}
// 生成随机颜色
function randomColor() {
const red = randomInt(256);
const green = randomInt(256);
const blue = randomInt(256);
return "rgb(" + red + "," + green + "," + blue + ")";
}
// 生成随机图片
function drawCode(canvas) {
// 得到随机字符串
const resCode = randomCode(4);
console.log(resCode.toString());
width = 5 + fontSize * resCode.length;
// 判断浏览器是否满足 canvas可用:Internet Explorer 8 以及更早的版本不支持 <canvas> 元素
if (canvas != null) {
if (canvas.getContext && canvas.getContext("2d")) {
// 设置显示区域大小
canvas.style.width = width;
// 设置画笔的高度宽度
canvas.setAttribute("width", width);
canvas.setAttribute("height", height);
// 得到画笔
const pen = canvas.getContext("2d");
// 绘制背景
pen.fillStyle = "rgb(255,255,255)";
pen.fillRect(0, 0, width, height);
// 设置绘制字符串的垂直对齐方式 :top middle bottom
pen.textBaseline = "top";
// 绘制内容
for (let i = 0; i < resCode.length; i++) {
pen.fillStyle = randomColor();
// px 要加大
pen.font = "bold" + (fontSize + 100 +randomInt(5)) + "px Arial";
// 参数:要绘制的字符、字符的横坐标、字符的纵坐标
pen.fillText(resCode.charAt(i), 5 + fontSize * i, height / 2 + randomInt(5));
}
// 绘制噪音线
for (let j = 0; j < 3; j++) {
// 起点
pen.moveTo(randomInt(width) / 2, randomInt(height));
// 终点
pen.lineTo(randomInt(width), randomInt(height));
// 随机颜色
pen.strokeStyle = randomColor();
// 线条粗细
pen.lineWidth = 2;
pen.stroke();
}
return resCode;
}else {
console.log("Canvas.getContext方法不可用!!!");
}
}else {
console.log("canvas对象为【null】!!!");
}
}
算术验证码
算术验证码与随机字符串相比,区别在于:需要用户对显示的表达式进行计算。主要将“生成随机字符”替换为“随机生成两个整数和运算符”。jsp
页面:
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<title>Java-算术验证码V2</title>
<style type="text/css">
.code_b {
color: #3498db;
font-size: 12px;
text-decoration: none;
cursor: pointer;
}
#cvs {
cursor: pointer;
}
</style>
<%-- 更改引用的 js文件 --%>
<script type="text/javascript" src="JS/arithmeticV2.js" charset="UTF-8"></script>
<script>
var valicode;
function changeCode(){
var cvs = document.getElementById("cvs");
valicode = drawCode(cvs);
}
// 验证用户输入
function valiCode(){
var inputCode = document.getElementById("inCode").value;
// JS返回的结果是一个 int ,所以转型
if (inputCode.toString() === valicode.toString()){
return true;
}else {
document.getElementById("err").innerHTML = "验证码错误!请重新输入。";
// 更换验证码
changeCode();
// 为了不刷新页面
return false;
}
}
// 初始化就加载验证码
window.onload = changeCode;
</script>
</head>
<body>
<form action="index.jsp" method="post">
<label>验证码:</label>
<input type="text" id="inCode" name="inCode" />
<canvas id="cvs" onclick="changeCode()"></canvas>
<a class="code_b" onclick="changeCode()">看不清?换一张</a>
<br />
<input type="submit" value="登录" onclick="return valiCode()" />
</form>
<!-- 错误信息提示 -->
<div style="color: #e74c3c;" id="err"></div>
</body>
</html>
对应的 JavaScript 程序:
// 这个是使用 JQuery实现算术验证码
// 定义全局变量
var width = 80;
var height = 24;
var fontSize = height - 6;
var Str = "+-×÷";
// 自定义生成随机整数:最大值不超过 max
function randomInt(max) {
return Math.floor(Math.random() * 100000 % max);
}
// 生成随机长度的字符串验证码
function randomCode() {
var one = randomInt(100);
var two = randomInt(100);
var operator = Str.charAt(randomInt(Str.length));
return "" + one + operator + two + "=";
}
// 生成随机颜色
function randomColor() {
var red = randomInt(256);
var green = randomInt(256);
var blue = randomInt(256);
return "rgb(" + red + "," + green + "," + blue + ")";
}
// 生成随机图片
function drawCode(canvas) {
var resCode = randomCode();
width = 5 + fontSize * resCode.length;
// 判断浏览器是否满足 canvas可用
if (canvas != null) {
if (canvas.getContext && canvas.getContext("2d")) {
// 设置显示区域大小
canvas.style.width = width;
// 设置画笔的高度宽度
canvas.setAttribute("width", width);
canvas.setAttribute("height", height);
// 得到画笔
var pen = canvas.getContext("2d");
// 绘制背景
pen.fillStyle = "rgb(255,255,255)";
pen.fillRect(0, 0, width, height);
// 设置水平线的位置 :middle bottom
pen.textBaseline = "top";
// 绘制内容
for (var i = 0; i < resCode.length; i++) {
pen.fillStyle = randomColor();
pen.font = "BOLD" + (fontSize + 500 + randomInt(3)) + "px 微软雅黑";
pen.fillText(resCode.charAt(i), 5 + fontSize * i, height / 2 + randomInt(5));
}
// 绘制噪音线
for (var j = 0; j < 3; j++) {
// 起点
pen.moveTo(randomInt(width) / 2, randomInt(height));
// 终点
pen.lineTo(randomInt(width), randomInt(height));
// 随机颜色
pen.strokeStyle = randomColor();
// 线条粗细
pen.lineWidth = 2;
pen.stroke();
}
// 去掉等于号
resCode = resCode.substr(0, resCode.length - 1);
// 调用 eval()函数,它可计算某个字符串,并执行其中的的 JavaScript 代码。
return eval(resCode);
}
}
}
在哔哩哔哩弹幕网,直播间领取领瓜子宝箱时需要算术验证。
图片滑块验证码
以哔哩哔哩弹幕网、京东为例,在注册用户时都使用了图片滑块验证码:
用 CSS 与 JavaScript 实现一个图片滑块验证码:
破解参考:CSDN
文字验证码
以哔哩哔哩弹幕网为例,文字验证码需要你按照一定的顺序或逻辑点击。
手机短信验证码
最常用的就是这个了,个人项目中如果有需要的话一般去购买 API。
验证码框架
自己实现的话,需要考虑很多,面面俱到;而使用框架的话,根据帮助文档进行配置即可,不需要考虑实现细节。
在 Servlet 中使用
- 下载驱动 jar 包,放到项目中的 lib 目录下;
- 在 web.xml 文件中配置验证码 Servlet(设置字体、颜色);
- 编写验证类;
- 前台页面调用并验证。
谷歌的 kaptcha
打开下载链接,下载后解压把得到的 kaptcha-2.3.2.jar
引入 Module 或 Libraries 中。
区别:Modules 下的 Dependencies 引入的依赖 jar 包,仅供当前 Module 模块使用;而 Libraries 下引入的是供整个Project 项目来使用的。
web.xml 中配置如下:
<!-- 使用 kaptcha 框架的 servlet -->
<servlet>
<servlet-name>myKaServlet</servlet-name>
<!-- 这个是 kaptcha 的 servlet,不是自己写的 -->
<servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>
<!-- 自定义属性值 -->
<init-param>
<!-- 去除边框 -->
<param-name>kaptcha.border</param-name>
<param-value>no</param-value>
</init-param>
<init-param>
<!-- 字体颜色 -->
<param-name>kaptcha.textproducer.font.color</param-name>
<!-- 对应的RGB值,没有空格 -->
<param-value>16,157,88</param-value>
</init-param>
<init-param>
<!-- 图片宽度 -->
<param-name>kaptcha.image.width</param-name>
<param-value>250</param-value>
</init-param>
<init-param>
<!-- 图片高度 -->
<param-name>kaptcha.image.height</param-name>
<param-value>50</param-value>
</init-param>
<init-param>
<!-- 使用的字符集 -->
<param-name>kaptcha.textproducer.char.string</param-name>
<param-value>Aa1bZc2dYeXfW3VgUhTiSj4kRlQmP0O9NnMoL5pKqJrIsHt8Gu6vFwExDyzC7B</param-value>
</init-param>
<init-param>
<!-- 验证码的长度:默认为 5 -->
<param-name>kaptcha.textproducer.char.length</param-name>
<param-value>6</param-value>
</init-param>
<init-param>
<!-- 验证码的间隔:像素 -->
<param-name>kaptcha.textproducer.char.space</param-name>
<param-value>3</param-value>
</init-param>
<init-param>
<!-- 字体大小 -->
<param-name>kaptcha.textproducer.font.size</param-name>
<param-value>40</param-value>
</init-param>
<init-param>
<!-- 图片显示样式 -->
<param-name>kaptcha.obscurificator.impl</param-name>
<param-value>com.google.code.kaptcha.impl.ShadowGimpy</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>myKaServlet</servlet-name>
<!-- 访问图片的路径 -->
<url-pattern>/showKaCode</url-pattern>
</servlet-mapping>
<!-- 这个才是自己写的处理验证码的servlet,继承 HttpServlet -->
<servlet>
<servlet-name>KaServlet</servlet-name>
<servlet-class>com.yyt.VerificationCode.KaServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>KaServlet</servlet-name>
<url-pattern>/KaServlet</url-pattern>
</servlet-mapping>
验证类 KaServlet
程序:
import com.google.code.kaptcha.Constants;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
/**
* 验证 kaptcha框架的验证码
* @author yyt
*/
@WebServlet(name = "KaServlet", value = "/KaServlet")
public class KaServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1.得到图片验证码:KaptchaServlet会把验证码设置到session中,得到之后,转小写
String valiCode = (String) request.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY);
// java.lang.NullPointerException:因为web.xml文件配置出错了
System.out.println(valiCode);
// 转小写
String temp = valiCode.toLowerCase();
// 2.得到用户输入的验证码:也需要转小写
String inputCode = request.getParameter("inCode").toLowerCase();
// 3.判断是否相等
if (inputCode.equals(temp)){
// 跳转
response.sendRedirect("index.jsp");
}else {
request.getSession().setAttribute("error", "验证码错误!请重新输入。");
// 返回上一页
String url = request.getHeader("Referer");
response.sendRedirect(url);
}
}
}
前台页面 KaCode.jsp
:
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<title>谷歌kaptcha框架-验证码</title>
<style type="text/css">
.code_a {
color: #3498db;
font-size: 12px;
text-decoration: none;
cursor: pointer;
}
#imgCode {
cursor: pointer;
}
</style>
<script type="text/javascript">
function changeCode(){
var imgCode = document.getElementById("imgCode");
// 为了达到刷新的效果,需要每一次访问路径不同
imgCode.src = "showKaCode?" + Math.random();
}
</script>
</head>
<body>
<form action="KaServlet" method="post">
<label>验证码:</label>
<input type="text" id="inCode" name="inCode" />
<%-- 图片路径为 web.xml 中 mapping 的 url-pattern 定义的访问路径 --%>
<img src="showKaCode" align="center" id="imgCode" onclick="changeCode()" />
<a class="code_a" onclick="changeCode()">看不清?换一张</a>
<br />
<input type="submit" value="登录" />
</form>
<!-- 错误信息提示 -->
<div style="color: #e74c3c;">${error}</div>
</body>
</html>
开源的 EasyCaptcha
效果更加精美,验证码更加多样。
官方文档:EasyCaptcha
在 Spring Boot 中使用
一般通过 maven 方式引入依赖,然后在 Controller 中设置验证码的参数配置。
其他
除了这些验证码,还有一些验证码比较有意思,可以在这个网站看到。
- 在验证码图片中,字母有多种颜色,例如红、蓝、粉,只需要输入粉色的字母作为验证码;
- 在验证码图片的上方,有一行小符号,例如实心圆、三角形,只需要输入实心圆下面的字母作为验证码;
- 验证码图片的背景图是一行比较模糊的字母,真正的验证码比较清晰,而且要求输入的验证码之间有空格。
讨厌点
有时候,一张验证码可以治疗好你的低血压。
例如,如果混用了不同的字体,那么数字 1、小写字母 l、大写字母 I 这三者;数字 0 与小写字母 o;数字 9、数字 6、小写字母 g ... 这几种真的很难分清楚。
参考
CSDN - kaptcha验证码使用