尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

API接口安全加固:JWT认证从零到生产

API接口安全加固:JWT认证从零到生产 我做的JWT认证从0到生产3个月踩坑实录前不久接了一个Web应用安全加固项目客户是一家做B2B SaaS的中型公司他们的接口完全裸奔——没有认证机制任何人都能直接调用后台API。说实话看到他们生产环境的接口文档时我愣了一下所有接口都不需要任何鉴权连管理后台的删除接口都暴露在公网。客户那边催得紧要求在3个月内完成认证体系重构同时不能影响现有业务。项目规模不算大核心业务接口大概80多个用户量在5万左右但安全合规要求很高毕竟涉及企业客户的数据。算法选型和密钥管理刚开始做的时候我第一反应是直接用HS256对称算法代码简单实现快试了一圈发现Spring Security集成JWT确实几行配置就能跑起来。当时我觉得这样就行结果安全审计的时候被怼了——HS256的密钥必须存储在服务器端一旦密钥泄露攻击者就能伪造任意用户的Token。坑死了这个锅我背。后来我选了RS256非对称算法私钥用云厂商的KMS托管公钥公开给客户端验证。这个决策做的时候纠结了一阵子因为非对称算法计算量更大但考虑到生产环境的安全要求这个代价值得付。有意思的是RFC 9206在2025年8月生效后明确规定了JWS紧凑序列化作为默认格式我们项目刚好赶上这个节点直接把标准对齐了。java// 密钥配置类私钥从KMS获取Configurationpublic class JwtConfig {Value(${jwt.private-key-id})private String privateKeyId;Value(${jwt.public-key-id})private String publicKeyId;Beanpublic KeyPair keyPair() {// 从AWS KMS获取密钥对GetPublicKeyRequest pubRequest GetPublicKeyRequest.builder().keyPairId(publicKeyId).build();GetPrivateKeyRequest privRequest GetPrivateKeyRequest.builder().keyPairId(privateKeyId).build();// 生成KeyPairreturn KeyPair.from(pubResponse.getPublicKey(),privResponse.getPrivateKey());}}Token策略这边我做了拆分Access Token有效期设了20分钟Refresh Token设了14天存在HttpOnly Secure SameSite Cookie里。这样设计的原因是即使Access Token被劫持攻击者的有效窗口也很短而Refresh Token因为存在Cookie里且设置了安全属性被盗用的风险低很多。防重放和权限控制说实话JWT最大的坑不是认证本身而是Token被劫持后的防重放。我一开始没想清楚这个问题后来查资料发现必须引入jti声明加Redis缓存的方案。java// Token生成时添加jtipublic String generateToken(User user) {String jti UUID.randomUUID().toString();return Jwts.builder().setSubject(String.valueOf(user.getId())).claim(username, user.getUsername()).claim(roles, user.getRoles()).setIssuedAt(new Date()).setExpiration(Date.from(Instant.now().plus(Duration.ofMinutes(20)))).setId(jti) // 关键唯一ID.signWith(getPrivateKey(), SignatureAlgorithm.RS256).compact();}// Redis缓存jti防止重放redisTemplate.opsForValue().setIfAbsent(jti: jti, 1, Duration.ofMinutes(20));权限控制这块我踩了一个坑。一开始我把用户角色直接存在JWT的claims里后来发现一个问题用户角色变更了但旧Token里的角色还是旧的要等20分钟过期才能生效。这个延迟在企业场景里是不可接受的。最终方案是Claims转换器把角色信息存在Redis里JWT只存用户ID验证时从Redis动态获取角色。这样角色变更可以实时生效而且即使Token被伪造没有Redis里的角色数据也拿不到权限。java// Claims转换器从Redis动态获取权限Componentpublic class JwtAuthenticationConverterimplements Converter {Overridepublic AbstractAuthenticationToken convert(Jwt jwt) {String userId jwt.getSubject();// 从Redis获取最新角色Set roles redisTemplate.opsForSet().members(user:roles: userId);// 转换为Spring Security权限对象List authorities roles.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());return new JwtAuthenticationToken(jwt, authorities);}}整个项目做下来3个月时间从0到生产环境上线中间踩了不少坑。最深刻的教训是安全这东西不能偷懒一开始图省事用的方案后期改起来代价巨大。现在这套方案已经在生产环境跑了两个月没出过安全问题算是比较稳了。本文基于实际项目经验整理欢迎在评论区交流技术问题。
返回列表