适用读者:要在项目里接一个"标准能力"(令牌、敏感词、验证码、字符串工具)的人。
前置:S06-B05(第三方工具库)、B04(JDK 自带工具类)、S07(鉴权基础)。
覆盖旧文:3705(JJWT 入门)、3707(sensitive-word 入门)、3708(tianai-captcha 入门)、3709(Commons Lang3 入门)→ 4 篇 / 约 1.1 万字符。
扩展点:E-S06-015(四库的"该用/不该用"判据与选型替代)、E-S06-016(JJWT 版本迁移与三个安全坑)、E-S06-017(敏感词与验证码的工程约束:性能、绕过、可维护)。
下一篇:S06-J04 · Maven 深水区。
1. 一句话结论
"标准能力"优先用成熟库,但每个库都自带一条不可绕过的约束:JWT 的密钥强度与算法校验、敏感词的词库维护与误伤、验证码的绕过与无障碍、字符串工具的分包体积。先看约束,再接库。
出处:
A-3705(JJWT)、A-3707(敏感词)、A-3708(验证码)、A-3709(Lang3)。
2. 四库总览与选型判据(E-S06-015)
| 库 | 解决 | 该用 | 不该用 | 替代 |
|---|---|---|---|---|
| JJWT | JWT 签发/解析(HS256/RS256) | 无状态会话、服务间令牌 | 需要"可撤销"的会话(JWT 天然不可撤) | 服务端 session/OAuth2 introspection |
| sensitive-word(houbb) | 敏感词过滤(DFA/Trie) | 评论、昵称、UGC 文本 | 需要语义级审核(反讽、变形) | 内容安全 API + 规则引擎 |
| tianai-captcha | 行为/滑块验证码 | 登录、注册、风控前置 | 高频 API 防刷(应用限流) | 限流 + 设备指纹 |
| Commons Lang3 | 字符串/日期/反射/Builder | 补齐 JDK 缺失的工具方法 | 已有 JDK 等价 API(String.isBlank)时 |
JDK 原生(17+ 覆盖多数场景) |
判据:"JDK 已能做的不引库"——Java 17 后 String.isBlank/strip/repeat、List.of、Optional、records 已覆盖大量 Lang3 用途;引库前先查 JDK 是否有等价方法。
3. JJWT:版本迁移与三个安全坑(E-S06-016)
API 分水岭(0.9.x → 0.11+/0.12+):
// 0.9.x(旧,已不推荐)
String jwt = Jwts.builder().setSubject("u1").signWith(SignatureAlgorithm.HS256, secret).compact();
// 0.12.x(当前口径):显式 Key + 显式算法
SecretKey key = Keys.hmacShaKeyFor(secretBytes); // ≥ 32 字节(HS256)
String jwt = Jwts.builder()
.subject("u1")
.issuedAt(new Date())
.expiration(Date.from(Instant.now().plus(15, ChronoUnit.MINUTES)))
.signWith(key, Jwts.SIG.HS256)
.compact();
Claims claims = Jwts.parser().verifyWith(key).build()
.parseSignedClaims(jwt).getPayload();
三个安全坑:
① 算法混淆攻击:解析时不限定算法 → 攻击者用 alg=none 或换算法伪造
→ 解析必须 verifyWith(key) 绑死算法,不读 token 头里的 alg 决定用什么
② 密钥太短/口令当密钥:HS256 要求 ≥256 bit(32 字节)
→ Keys.hmacShaKeyFor 会直接抛 WeakKeyException,别捕获后"凑合"
③ 把敏感信息放进 payload:JWT 只是 base64url 编码,不是加密
→ 只放 sub/exp/scope 等非敏感声明;需要加密用 JWE
有效期与撤销:exp 只能"到期失效",不能"立即吊销"。要吊销 → 维护短名单(Redis 存 jti,TTL=剩余有效期)或改用不透明 token + 服务端校验。
版本口径:JJWT 0.12.x 的 Jwts.SIG.*、verifyWith、parseSignedClaims 与 0.11.x 略有差异,按所用版本核对官方 README(待核具体小版本)。
4. sensitive-word:DFA 与误伤控制(E-S06-017)
SensitiveWordBs bs = SensitiveWordBs.newInstance()
.ignoreCase(true).ignoreWidth(true) // 全角/半角、大小写
.ignoreNumStyle(true) // 1 vs 一
.wordAllow(wordAllowList) // 白名单(避免误伤)
.enableNumCheck(false).enableEmailCheck(false) // 数字/邮箱不作为敏感片段
.init();
boolean hit = bs.contains("文本");
String replaced = bs.replace("文本", '*');
| 工程约束 | 说明 |
|---|---|
| 词库维护 | 词库是资产:版本化、可热更新、有变更审计;不要硬编码在代码里 |
| 误伤 | 中文分词边界会导致"正常词被切出敏感片段"→ 白名单 + 词边界策略 |
| 绕过 | 拼音、拆字、符号插入("敏*感")、形近字 → 需归一化(全角/半角、去符号、繁简)后再匹配 |
| 性能 | DFA 单次匹配近似 O(n);但每次新建实例会重建 Trie(昂贵)→ 全局单例 + 词库变更时重建 |
| 语义 | 规则库无法覆盖反讽/上下文 → 高风险场景叠加人工或第三方内容安全 API |
判据:"过滤"不等于"审核"——过滤解决"明显违规词",审核需要模型与人工;两者组合,别指望词库兜底。
5. tianai-captcha:接入与三个边界
流程:前端请求 → 服务端生成(返回背景图/滑块参数 + 唯一 id)→ 用户交互
→ 前端回传轨迹 → 服务端校验(一次性、带过期)
| 边界 | 说明 |
|---|---|
| 一次性 | 校验通过即失效;重复提交必须拒绝(否则可重放) |
| 过期 | 生成后 1~2 分钟过期;校验失败计数要限次 |
| 无障碍 | 滑块对键盘/读屏用户不友好 → 提供语音/短信备选 |
| 不是防刷主力 | 验证码挡"批量脚本",但挡不住"人工打码平台"→ 必须叠加 IP/设备/行为限流 |
判据:验证码的成本目标是"提高单次攻击成本",不是"零攻击"——把它放在限流之后,而不是替代限流。
6. Commons Lang3:什么时候还需要它
| 场景 | Lang3 | JDK 17 原生 |
|---|---|---|
| 判空 | StringUtils.isBlank |
String.isBlank(但 null 不安全) |
| 字符串拼接 | StringUtils.join |
String.join(不支持 null 元素) |
| 反射工具 | FieldUtils/MethodUtils |
无(需自己写或引库) |
| Builder | EqualsBuilder/HashCodeBuilder |
record + Objects.equals/hash |
| 日期 | DateUtils |
java.time(更应优先用) |
判据:Lang3 的剩余价值集中在"null 安全的字符串工具 + 反射 + Builder";日期一律用 java.time,不要再引 DateUtils。
体积:Lang3 约 600 KB(无传递依赖),Android/Serverless 场景需评估;可用 commons-lang3 的按需子集或自行封装 5 个方法替代。
7. 反例:六种"接了库反而更糟"
| 做法 | 后果 |
|---|---|
| JJWT 解析时不绑算法 | 算法混淆伪造令牌 |
| HS256 用 8 字节口令 | 弱密钥,可离线爆破 |
| 把手机号/身份证放 JWT payload | base64 可解,等于明文 |
| 敏感词每次请求 new 实例 | 每请求重建 Trie,CPU 飙高 |
| 只靠词库过滤做内容审核 | 变形词与语义违规全部漏过 |
| 验证码替代限流 | 打码平台人工绕过,接口被打穿 |
8. 自测
- 四库各解决什么、各自的"不该用"?
- JJWT 0.12 的签发/解析最小代码?三个安全坑?
- JWT 为什么不能"立即吊销",怎么办?
- 敏感词的四条工程约束?为什么不能每次 new 实例?
- 验证码与限流的关系?
答案
- 令牌/敏感词/验证码/字符串工具;需可撤销会话、语义审核、防刷主力、JDK 已有等价 API 时不引。
Keys.hmacShaKeyFor+signWith(key, Jwts.SIG.HS256);parser().verifyWith(key);算法混淆、弱密钥、敏感信息入 payload。- 无状态且自校验;用 jti 黑名单(TTL=剩余有效期)或改不透明 token。
- 词库版本化、误伤白名单、绕过归一化、单例复用;重建 Trie 昂贵。
- 验证码在限流之后,是提高攻击成本而非替代限流。
9. 下一篇
S06-J04 · Maven 深水区 —— Profiles 与环境隔离、插件绑定与生命周期、依赖冲突的三种表现与 dependencyManagement 的正确用法、私服与制品可复现。
本篇属于系列《S06 · Java 工程化与组件》。旧文来源:A-3705、A-3707/3708、A-3709;扩展点 E-S06-015/022/023。库版本口径为 2026-11 时点,具体小版本 API 以官方文档为准(待核)。