本文目录导读:

搭建图形验证码有很多方式,主要取决于你的技术栈(后端语言、框架)以及是否需要集成到现有业务中。
目前主流的图形验证码方案可以分为三大类:
- 自研方案:自己生成图片或SVG。
- 集成开源库:使用成熟的开源验证码工具包(如
captcha、kaptcha、django-simple-captcha)。 - 第三方服务:极验、腾讯防水墙、Cloudflare Turnstile等(这些更多是滑块、点选或行为验证,安全性更高,但非纯图形验证码)。
下面以最常用的 后端生成图片 + 前端展示 方案为例,从原理到具体实现进行说明。
核心原理(无论用什么语言)
- 后端生成:在服务器生成一张包含随机字符或图形的图片,同时将正确答案(通常是文本)存入Session或缓存(Redis)。
- 存入Session:
session["captcha_code"] = "A1B2" - 存入Redis:
SET 用户唯一标识 "A1B2" EX 300(5分钟过期)
- 存入Session:
- 前端展示:后端通过HTTP响应返回图片的二进制流(
image/png),前端直接使用<img src="/captcha">展示。 - 用户输入:用户在输入框中填写看到的字符。
- 后端校验:用户提交表单时,将输入的字符与 session中/redis 中存储的答案进行比对(通常忽略大小写),比对后立即删除该session/缓存,防止重复使用。
具体实现示例(主流语言)
Python(Flask + Pillow 方案)
这是非常经典且上手快的方式,需要安装 Pillow 库用于图像处理。
from flask import Flask, session, make_response, request
from PIL import Image, ImageDraw, ImageFont
import random
import io
app = Flask(__name__)
app.secret_key = 'your-secret-key-here' # 必须设置,用于签名session
def generate_captcha():
# 1. 生成随机字符 (4-6位)
chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789' # 去掉易混淆的 0, O, 1, I
code = ''.join(random.choices(chars, k=4))
# 2. 创建图片 (宽度120, 高度40)
img = Image.new('RGB', (120, 40), color=(255, 255, 255)) # 白色背景
draw = ImageDraw.Draw(img)
# 3. 添加干扰线 (3条)
for i in range(3):
start = (random.randint(0, 120), random.randint(0, 40))
end = (random.randint(0, 120), random.randint(0, 40))
draw.line([start, end], fill=(random.randint(0, 200), random.randint(0, 200), random.randint(0, 200)), width=2)
# 4. 绘制字符 (注意字体路径,Windows用arial.ttf,Linux需下载)
font = ImageFont.truetype("arial.ttf", size=30)
for i, char in enumerate(code):
x = 10 + i * 25 + random.randint(-3, 3) # 位置随机偏移
y = random.randint(2, 10)
draw.text((x, y), char, fill=(random.randint(0, 100), random.randint(0, 100), random.randint(0, 100)), font=font)
# 5. 添加噪点
for i in range(50):
draw.point((random.randint(0, 120), random.randint(0, 40)), fill=(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)))
# 6. 保存到内存字节流
buf = io.BytesIO()
img.save(buf, format='PNG')
img_data = buf.getvalue()
return code, img_data
@app.route('/captcha')
def get_captcha():
code, img_data = generate_captcha()
session['captcha_code'] = code # 存入服务器session
response = make_response(img_data)
response.headers['Content-Type'] = 'image/png'
return response
@app.route('/login', methods=['POST'])
def login():
user_input = request.form.get('captcha_input', '').upper()
real_code = session.pop('captcha_code', None) # 取出后立刻删除
if not real_code:
return '验证码已过期', 400
if user_input != real_code:
return '验证码错误', 400
return '验证成功!'
Java(Spring Boot + EasyCaptcha)
比自研更推荐使用 EasyCaptcha 库,效率高,开箱即用,在 pom.xml 中添加依赖:
<dependency>
<groupId>com.github.whvcse</groupId>
<artifactId>easy-captcha</artifactId>
<version>1.6.2</version>
</dependency>
import com.wf.captcha.SpecCaptcha;
import com.wf.captcha.base.Captcha;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class CaptchaController {
@GetMapping("/captcha")
public void captcha(HttpServletResponse response, HttpSession session) throws Exception {
// 1. 设置响应头
response.setContentType("image/png");
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
// 2. 生成验证码 (宽度160, 高度50, 字符数4)
SpecCaptcha captcha = new SpecCaptcha(160, 50, 4);
captcha.setCharType(Captcha.TYPE_ONLY_NUMBER); // 可选:纯数字、纯字母、数字字母混合
// 3. 存储验证码到Session
session.setAttribute("captcha_code", captcha.text().toLowerCase()); // 注意大小写
// 4. 输出图片流
captcha.out(response.getOutputStream());
}
}
前端展示与交互(通用HTML/JS)
不论是哪种后端方案,前端代码基本一致:
<form id="loginForm" action="/login" method="post">
<div>
<label>用户名:<input type="text" name="username"></label>
</div>
<div>
<label>验证码:<input type="text" name="captcha_input" placeholder="请输入上图字符" required></label>
<!-- 验证码图片,点击可刷新 -->
<img id="captcha_img" src="/captcha" alt="验证码" onclick="this.src='/captcha?'+Math.random()" style="cursor:pointer;">
<span onclick="document.getElementById('captcha_img').src='/captcha?'+Math.random()" style="cursor:pointer; color:blue;">换一张</span>
</div>
<button type="submit">提交</button>
</form>
重要安全加固建议(生产环境必做)
- 复杂化处理:
- 扭曲变形:对字符进行旋转、波浪形扭曲(Pillow有
transform方法可做)。 - 干扰背景:使用随机颜色、渐变、网格线而非纯色。
- 字体随机:随机从多个字体文件中选取。
- 扭曲变形:对字符进行旋转、波浪形扭曲(Pillow有
- 防自动化识别:
- 复杂度:字符粘连在一起(切割难度大增)、加入噪点块。
- 速度限制:对同一个IP的请求
/captcha做限速(例如1秒内最多5次)。 - 一次性使用:验证码校验成功后,必须立即从Session/Redis中删除,绝对不允许复用。
- 防暴力破解:
- 时效性:验证码有效期通常设为2-5分钟。
- 尝试次数:用户登录失败3次后,前端必须手动刷新验证码(并重置服务端状态)。
- 会话绑定:将验证码与Session ID或用户IP绑定,防止A用户的验证码被B用户使用。
- 前端安全:
- 计算
/?t=timestamp加时间戳,或者直接用Math.random()加上去,防止图片缓存导致用户一直看到旧code。
- 计算
进阶替代方案
如果觉得传统图形验证码不安全或用户体验不好,可以考虑:
- 行为验证:极验、腾讯防水墙等,用户只需点击“我不是机器人”或拖动滑块,体验更好,安全性显著提升,集成方式主要是前端引用SDK + 后端调用API校验。
- Google reCAPTCHA v3:无感验证,根据用户行为给一个风险分数(0.0-1.0),完全无需用户输入字符。
- 短信/邮箱验证码:更强的身份验证机制,常用于关键操作(修改密码、大额转账)。
总结建议
- 如果是学习/小型项目:用Pillow或EasyCaptcha自建即可。
- 如果是正式商业项目:不要自己造轮子处理图形验证码(识别率低、安全性难保障),推荐集成第三方安全服务(如极验3.0)的前端行为验证 + 后端一次校验。
- 如果是要求极低但需要图片:使用开源项目调整参数即可。
标签: 搭建工具
版权声明:除非特别标注,否则均为本站原创文章,转载时请以链接形式注明文章出处。