验证码问题根源在于绘制顺序与抗锯齿冲突、Session键不匹配及大小写/空格处理缺失;需先绘干扰线、统一key存取、输入trim转大写,并启用headless模式与逻辑字体。

验证码图片生成时干扰线不显示或杂乱无章
根本原因是 Graphics2D 绘制顺序和抗锯齿设置冲突,尤其在开启 setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) 后,细线容易被模糊甚至消失。
- 干扰线必须在文字绘制之前画,否则会被文字遮盖;但也不能太早(比如在背景填充前),否则可能被背景色覆盖
- 用
g2d.setColor(new Color(180, 180, 180))而非纯灰(Color.GRAY),避免在浅色背景上不可见 - 线宽固定设为
1.0f,别用g2d.setStroke(new BasicStroke(0.5f))—— 小于 1 的线宽在部分 JDK 版本(如 OpenJDK 17)下直接不渲染 - 干扰线数量控制在 3–6 条,起点终点都用
random.nextInt()在图像边界内生成,别写死0或width,否则容易出界或重叠
Session 中存的验证码值取出来总是 null 或类型错
不是 Session 没存进去,而是取的时候没对上 key,或者存的是字符串、取成了对象——典型类型不匹配 + 作用域错位问题。
- 存的时候用
session.setAttribute("verifyCode", codeString),取的时候必须用完全相同的字符串 key:(String) session.getAttribute("verifyCode") - 千万别在过滤器或拦截器里提前调用
request.getSession(false),它可能返回 null;校验时统一用request.getSession()(带创建)再判空 - 用户提交表单后,立刻调用
session.removeAttribute("verifyCode"),防止重复使用;但要在比对成功之后删,别在校验逻辑前就删 - 注意 JSP/Thymeleaf 模板里用
${sessionScope.verifyCode}是只读的,不能用于校验逻辑,那只是给前端看的
验证码校验失败但看不出哪里错了
最常见的是大小写和空格:前端输入框默认有首尾空格,后端没 trim;或者前端把字母 O 和数字 0 渲染得一模一样,用户输错你还不知道。
- 生成验证码时,明确排除易混淆字符:从字符集
"ABCDEFGHJKLMNPQRSTUVWXYZ23456789"里选,去掉0O1I - 校验前必须对用户输入做
input.trim().toUpperCase(),服务端不信任任何前端处理 - 对比用
equalsIgnoreCase(),别用==;Session 取出来是 null 时直接返回失败,别让它进 equals 空指针 - 加一行日志:
log.debug("Session code: {}, Input: {}", sessionCode, userInput),部署时打开 DEBUG 级别,比抓包快得多
本地跑得通,上线后验证码图片空白或 500
本质是服务器缺少图形渲染环境:Linux 服务器没装字体或没启用 headless 模式,Graphics2D 创建失败。
立即学习“Java免费学习笔记(深入)”;
- 启动 JVM 时加参数:
-Djava.awt.headless=true,否则BufferedImage构造可能抛HeadlessException - 检查服务器是否安装了基础字体,CentOS 执行
yum install fontconfig dejavu-sans-fonts,Ubuntu 用apt install fonts-dejavu - 别用系统默认字体名如
"Arial",改用"SansSerif"或"Dialog"这类逻辑字体名,它们在所有平台都有 fallback - 生成图片前加兜底判断:
if (graphics == null) throw new IllegalStateException("Graphics2D init failed");,避免静默返回空白图
真正麻烦的不是画线或存 Session,是字体、headless、大小写归一化这三处细节一漏,问题就藏得特别深——尤其是跨环境时,连日志都不报错,只给你一张透明图。










