news 2026/5/26 16:10:13

你写的“校验”真的是安全校验吗?还是只是在安慰自己“应该没事吧”?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
你写的“校验”真的是安全校验吗?还是只是在安慰自己“应该没事吧”?

👋你好,欢迎来到我的博客!我是【菜鸟不学编程】
我是一个正在奋斗中的职场码农,步入职场多年,正在从“小码农”慢慢成长为有深度、有思考的技术人。在这条不断进阶的路上,我决定记录下自己的学习与成长过程,也希望通过博客结识更多志同道合的朋友。

🛠️ 主要方向包括 Java 基础、Spring 全家桶、数据库优化、项目实战等,也会分享一些踩坑经历与面试复盘,希望能为还在迷茫中的你提供一些参考。
💡 我相信:写作是一种思考的过程,分享是一种进步的方式。

如果你和我一样热爱技术、热爱成长,欢迎关注我,一起交流进步!

全文目录:

    • I. 输入验证:防注入的第一道门(别用黑名单,别赌运气)
      • 1)做输入验证时,我更相信“白名单 + 类型化”
      • 2)SQL 注入:别拼接字符串,直接参数化
    • II. 加密最佳实践:PBKDF2(存密码)+ AES-GCM(加密数据)
      • 1)PBKDF2:把“猜密码”变得更贵一点(越贵越好,但别贵到把自己打死)
      • 2)AES-GCM:加密 + 完整性校验一起做(别再 CBC+自制 MAC 乱拼了)
    • III. 证书管理:KeyStore vs TrustStore(别再把它俩当成一个东西)
      • 示例:加载 KeyStore,初始化 SSLContext(服务端/客户端都能用思路)
    • IV. OWASP Top 10:Java 里怎么对着打(别背名单,背“对策模式”)
      • 1)A03 Injection(注入)
      • 2)A02 Cryptographic Failures(加密失败)
      • 3)A01 Broken Access Control(访问控制失效)
    • V. 静态分析:SonarQube + FindBugs(SpotBugs)(让“低级错误”死在 CI 里)
      • 1)SonarQube:安全规则 + 污点分析(taint analysis)
      • 2)FindBugs 已成历史,建议用 SpotBugs
    • VI. 项目:安全 API 的实现与审计(把“安全”做成可检查的工程件)
      • 目标功能
      • 1)接口层:输入校验 + 统一错误响应(别给攻击者“提示礼包”)
      • 2)认证与会话:别把 token 当“身份证复印件”到处乱扔
      • 3)敏感数据:AES-GCM + AAD 绑定“上下文”
      • 4)审计:把安全事件当“业务数据”认真存
    • 一份我自己会用的“安全编码小抄”(写代码时贴在脑门上)
    • 📝 写在最后

I. 输入验证:防注入的第一道门(别用黑名单,别赌运气)

**注入(Injection)**依然是 OWASP Top 10 的核心风险之一,关键问题几乎都指向同一句话:

不可信输入被拼进了命令/查询/表达式里执行。

1)做输入验证时,我更相信“白名单 + 类型化”

  • 白名单:只允许合法集合(正则/枚举/范围)
  • 类型化:能用int/UUID/enum就别全用String
  • 结构化校验:Bean Validation(JSR 380)比你手写 if/else 更稳(也更容易审计)

示例:Spring Boot + Bean Validation(接口层把脏数据挡在门外)

importjakarta.validation.constraints.*;publicrecordCreateUserReq(@NotBlank@Size(max=32)@Pattern(regexp="^[a-zA-Z0-9_]+$")Stringusername,@Email@NotBlank@Size(max=254)Stringemail,@NotBlank@Size(min=8,max=72)Stringpassword){}

小吐槽:别把“校验失败”当成用户体验问题就放水。
安全世界里,放水通常不是温柔,是放虎归山。🙂

2)SQL 注入:别拼接字符串,直接参数化

OWASP 的 Java 安全速查表强调用PreparedStatement / 参数化查询来防注入。

Stringsql="SELECT id, email FROM users WHERE username = ?";try(PreparedStatementps=conn.prepareStatement(sql)){ps.setString(1,username);try(ResultSetrs=ps.executeQuery()){...}}

II. 加密最佳实践:PBKDF2(存密码)+ AES-GCM(加密数据)

这里先把最容易混的两件事掰开:

  • 密码存储:不要“加密后存”,要用专用的密码哈希/KDF(PBKDF2 / bcrypt / scrypt / Argon2)。OWASP 明确:密码不该用可逆加密存储。([cheatsheetseries.owasp.org][3])
  • 业务数据加密(比如身份证号、token、密钥包):用AEAD(认证加密)模式,AES-GCM 是常用首选之一。OWASP 的 Cryptographic Storage Cheat Sheet 推荐优先使用 GCM/CCM 这类认证模式,并强调避免 ECB。

1)PBKDF2:把“猜密码”变得更贵一点(越贵越好,但别贵到把自己打死)

OWASP Password Storage Cheat Sheet 给出了 PBKDF2 的使用建议(带盐、足够迭代、输出长度等,并建议基于硬件做“可承受的成本”校准)。

Java 例子:PBKDF2WithHmacSHA256 + 随机盐 + 常量时间比较

importjavax.crypto.SecretKeyFactory;importjavax.crypto.spec.PBEKeySpec;importjava.security.SecureRandom;importjava.util.Base64;importjava.util.Arrays;publicfinalclassPasswordHasher{privatestaticfinalSecureRandomRNG=newSecureRandom();// 迭代次数别拍脑袋:按你线上机器校准到“可接受登录延迟”(比如 80~200ms)更靠谱。:contentReference[oaicite:5]{index=5}privatestaticfinalintITERATIONS=210_000;privatestaticfinalintKEY_LEN_BITS=256;// 32 bytespublicstaticStringhash(char[]password)throwsException{byte[]salt=newbyte[16];RNG.nextBytes(salt);byte[]dk=pbkdf2(password,salt,ITERATIONS,KEY_LEN_BITS);// 存储格式:algo$iter$salt$hash(便于将来升级参数)return"PBKDF2HmacSHA256$"+ITERATIONS+"$"+b64(salt)+"$"+b64(dk);}publicstaticbooleanverify(char[]password,Stringstored)throwsException{String[]parts=stored.split("\\$");if(parts.length!=4)returnfalse;intit=Integer.parseInt(parts[1]);byte[]salt=b64d(parts[2]);byte[]expected=b64d(parts[3]);byte[]dk=pbkdf2(password,salt,it,expected.length*8);returnconstantTimeEquals(expected,dk);}privatestaticbyte[]pbkdf2(char[]password,byte[]salt,intiters,intkeyLenBits)throwsException{PBEKeySpecspec=newPBEKeySpec(password,salt,iters,keyLenBits);// PBEKeySpec 用 char[] 是为了可擦除,避免 String 常驻内存。:contentReference[oaicite:6]{index=6}SecretKeyFactoryskf=SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");try{returnskf.generateSecret(spec).getEncoded();}finally{spec.clearPassword();}}privatestaticbooleanconstantTimeEquals(byte[]a,byte[]b){if(a.length!=b.length)returnfalse;intr=0;for(inti=0;i<a.length;i++)r|=(a[i]^b[i]);returnr==0;}privatestaticStringb64(byte[]x){returnBase64.getEncoder().encodeToString(x);}privatestaticbyte[]b64d(Strings){returnBase64.getDecoder().decode(s);}}

2)AES-GCM:加密 + 完整性校验一起做(别再 CBC+自制 MAC 乱拼了)

OWASP 建议优先用 GCM/CCM 这种认证加密模式。
Java 里 GCM 的参数由GCMParameterSpec描述:包含IV认证标签长度。([Oracle 文档][6])

Java 例子:AES/GCM/NoPadding(IV 每次随机、密文带上 IV)

importjavax.crypto.Cipher;importjavax.crypto.KeyGenerator;importjavax.crypto.SecretKey;importjavax.crypto.spec.GCMParameterSpec;importjavax.crypto.spec.SecretKeySpec;importjava.security.SecureRandom;importjava.util.Base64;publicfinalclassAesGcm{privatestaticfinalSecureRandomRNG=newSecureRandom();privatestaticfinalintIV_LEN=12;// 96-bit IV(常见实践)privatestaticfinalintTAG_LEN=128;// 128-bit tag :contentReference[oaicite:9]{index=9}publicstaticStringencrypt(byte[]key,byte[]plaintext,byte[]aad)throwsException{byte[]iv=newbyte[IV_LEN];RNG.nextBytes(iv);Cipherc=Cipher.getInstance("AES/GCM/NoPadding");SecretKeySpeck=newSecretKeySpec(key,"AES");c.init(Cipher.ENCRYPT_MODE,k,newGCMParameterSpec(TAG_LEN,iv));if(aad!=null)c.updateAAD(aad);byte[]ct=c.doFinal(plaintext);// 输出:base64(iv).base64(ciphertext)returnb64(iv)+"."+b64(ct);}publicstaticbyte[]decrypt(byte[]key,Stringtoken,byte[]aad)throwsException{String[]parts=token.split("\\.");if(parts.length!=2)thrownewIllegalArgumentException("Bad token");byte[]iv=b64d(parts[0]);byte[]ct=b64d(parts[1]);Cipherc=Cipher.getInstance("AES/GCM/NoPadding");SecretKeySpeck=newSecretKeySpec(key,"AES");c.init(Cipher.DECRYPT_MODE,k,newGCMParameterSpec(TAG_LEN,iv));if(aad!=null)c.updateAAD(aad);returnc.doFinal(ct);// tag 校验失败会抛异常(这就是“认证”的意义)}privatestaticStringb64(byte[]x){returnBase64.getEncoder().encodeToString(x);}privatestaticbyte[]b64d(Strings){returnBase64.getDecoder().decode(s);}// 生成随机 AES key(示例用;生产建议走 KMS/KeyStore 管控)publicstaticbyte[]newKey256()throwsException{KeyGeneratorkg=KeyGenerator.getInstance("AES");kg.init(256);SecretKeyk=kg.generateKey();returnk.getEncoded();}}

你会发现:AES-GCM 的“正确姿势”反而很朴素:每次随机 IV + 别复用 IV + 别自己拼认证
复杂的是“密钥怎么管”,这就进入 KeyStore/TrustStore 了。

III. 证书管理:KeyStore vs TrustStore(别再把它俩当成一个东西)

一句话版(很够用):

  • KeyStore:放“我自己的身份材料”(私钥+证书链等)
  • TrustStore:放“我信任谁”(对端证书/CA)

这个区分在 TLS/双向 TLS 场景里非常关键。

示例:加载 KeyStore,初始化 SSLContext(服务端/客户端都能用思路)

importjavax.net.ssl.*;importjava.io.FileInputStream;importjava.security.KeyStore;publicfinalclassTlsContextFactory{publicstaticSSLContextbuild(StringkeyStorePath,char[]ksPass,StringtrustStorePath,char[]tsPass)throwsException{// KeyStore:私钥与证书链KeyStoreks=KeyStore.getInstance("PKCS12");try(FileInputStreamin=newFileInputStream(keyStorePath)){ks.load(in,ksPass);}KeyManagerFactorykmf=KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());kmf.init(ks,ksPass);// TrustStore:信任的 CA/证书KeyStorets=KeyStore.getInstance("PKCS12");try(FileInputStreamin=newFileInputStream(trustStorePath)){ts.load(in,tsPass);}TrustManagerFactorytmf=TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());tmf.init(ts);SSLContextctx=SSLContext.getInstance("TLS");ctx.init(kmf.getKeyManagers(),tmf.getTrustManagers(),null);returnctx;}}

真正容易翻车的不是 API,而是流程:证书过期、链不全、环境差异、私钥权限、口令泄露……
所以“可审计”和“自动化检查”比“会写代码”更重要(下面项目里会落到审计)。

IV. OWASP Top 10:Java 里怎么对着打(别背名单,背“对策模式”)

OWASP Top 10:2021 是面向开发者的安全风险共识文档。
我更喜欢把它拆成“在 Java 里要形成习惯的防线”:

1)A03 Injection(注入)

  • 参数化查询 / ORM 参数绑定(别拼接)
  • 命令执行用 API,不拼 shell 字符串(同理)
  • 输入验证(白名单)+ 输出转义(按上下文)

2)A02 Cryptographic Failures(加密失败)

  • 密码存储用 PBKDF2/bcrypt/Argon2,不用可逆加密
  • 数据加密用 AEAD(AES-GCM/CCM),别用 ECB
  • 密钥要管:KMS/KeyStore/轮换/最小权限(工程问题,不只是代码问题)

3)A01 Broken Access Control(访问控制失效)

访问控制是 Top 10 的头号风险之一。
Java 里最常见“自杀式失误”是:

  • 只在前端/网关做鉴权,后端方法没二次校验
  • 资源级权限缺失(能登录就能看别人的数据)
  • 缺少默认拒绝(default deny)

说真的,很多系统不是被黑客“攻破”的,是被我们自己“放行”的。🙂

V. 静态分析:SonarQube + FindBugs(SpotBugs)(让“低级错误”死在 CI 里)

1)SonarQube:安全规则 + 污点分析(taint analysis)

SonarQube 的安全引擎支持对注入类问题的污点分析,并允许你配置 sources/sanitizers/sinks 来适配自研框架。

落地建议(不花哨但有效):

  • PR 必须过 Quality Gate(安全热点/漏洞等级)
  • 对“注入/弱加密/TLS 配置”类规则设为阻断
  • 把“例外”写成显式审计记录(谁批准、为什么、何时复审)

2)FindBugs 已成历史,建议用 SpotBugs

SpotBugs 是 FindBugs 的继任者/分支项目(社区维护延续)。

我喜欢把 SpotBugs 当“代码里的地雷探测器”:它不保证你一定安全,但能帮你少踩一堆明显的坑。

VI. 项目:安全 API 的实现与审计(把“安全”做成可检查的工程件)

下面我给一个“可审计”的安全 API 轮廓(偏 Spring 思路,但你换框架也能照搬):

目标功能

  1. POST /auth/register:注册(PBKDF2 存储密码)
  2. POST /auth/login:登录(常量时间比对)
  3. POST /secrets:存储敏感数据(AES-GCM 加密,AAD 绑定用户)
  4. GET /audit/events:审计查询(只读、分页、最小暴露)

1)接口层:输入校验 + 统一错误响应(别给攻击者“提示礼包”)

  • 校验失败:返回通用错误(别把内部规则、SQL、堆栈吐出去)
  • 日志:记录 requestId、用户、IP、失败原因类别(不要记录明文密码/密钥/完整 token)

2)认证与会话:别把 token 当“身份证复印件”到处乱扔

  • token 放 Header,不放 URL
  • token 存储只保留哈希/指纹(可选),便于吊销与审计
  • 关键操作要求二次验证(按风险级别)

3)敏感数据:AES-GCM + AAD 绑定“上下文”

把 AAD 设成userId + resourceId + version之类,让密文“离开上下文就解不开/解出来也校验不过”,这能降低很多“搬运密文”类问题(思路见上面的updateAAD)。

4)审计:把安全事件当“业务数据”认真存

我建议至少记录这些事件:

  • 登录成功/失败(含失败类型:密码错误/账号不存在/风控拦截)
  • 权限拒绝(访问控制触发)
  • 密钥/证书加载失败、TLS 握手异常(系统级安全事件)
  • 管理员操作(改权限/改配置/导出数据)

审计日志的气质应该是:能复盘、能追责、能告警,而不是“出事后才想起来没开”。🙂

一份我自己会用的“安全编码小抄”(写代码时贴在脑门上)

  • 所有外部输入:先验证再使用(白名单优先)
  • 所有查询/命令:参数化,不拼接
  • 密码:PBKDF2/bcrypt/Argon2,永不加密存储
  • 加密数据:AES-GCM/CCM,拒绝 ECB
  • 证书:KeyStore/TrustStore 分清楚,别把私钥当配置文件乱传
  • 工程化:SonarQube + SpotBugs 上 CI,默认阻断高危

📝 写在最后

如果你觉得这篇文章对你有帮助,或者有任何想法、建议,欢迎在评论区留言交流!你的每一个点赞 👍、收藏 ⭐、关注 ❤️,都是我持续更新的最大动力!

我是一个在代码世界里不断摸索的小码农,愿我们都能在成长的路上越走越远,越学越强!

感谢你的阅读,我们下篇文章再见~👋

✍️ 作者:某个被流“治愈”过的 Java 老兵
📅 日期:2025-08-25
🧵 本文原创,转载请注明出处。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/23 1:52:27

从零开始学Agent:写给AI小白的保姆级入门指南

Bug如山勤为径&#xff0c;代码似海苦作舟。友友们好&#xff0c;这里是苦瓜大王。Agent这个词早就火得一塌糊涂&#xff0c;但很多人看完一堆文章还是懵的&#xff1a; 它到底是个啥&#xff1f;跟我有什么关系&#xff1f;我能用它做什么&#xff1f; 别急&#xff0c;这篇文…

作者头像 李华
网站建设 2026/5/27 14:32:26

基于FLUX.1-dev的像素艺术生成器实战:支持内存流导出的Streamlit应用

基于FLUX.1-dev的像素艺术生成器实战&#xff1a;支持内存流导出的Streamlit应用 1. 项目概述 像素幻梦 (Pixel Dream Workshop) 是一款基于FLUX.1-dev扩散模型的像素艺术生成工具。与传统AI绘图工具不同&#xff0c;它专为像素艺术创作设计&#xff0c;采用16-bit复古游戏风…

作者头像 李华
网站建设 2026/5/23 1:52:26

Qwen3-0.6B-FP8应用场景:Qwen3-0.6B-FP8在在线教育答题辅导应用

Qwen3-0.6B-FP8在在线教育答题辅导应用 1. 引言&#xff1a;当轻量级AI遇到教育难题 想象一下这个场景&#xff1a;深夜十一点&#xff0c;一个初中生正对着数学作业本上的几何证明题发愁。他尝试了几种思路&#xff0c;但都卡在了某个步骤。父母已经休息&#xff0c;老师也无…

作者头像 李华
网站建设 2026/5/23 1:52:25

OpenClaw配置加密方案:保护Phi-3-mini-128k-instruct的API密钥安全

OpenClaw配置加密方案&#xff1a;保护Phi-3-mini-128k-instruct的API密钥安全 1. 为什么需要加密配置&#xff1f; 去年夏天&#xff0c;我在调试一个自动化文档处理流程时&#xff0c;不小心把包含API密钥的配置文件上传到了GitHub。虽然及时发现并删除了仓库&#xff0c;但…

作者头像 李华