公开文集
0x01 SRC 资产管理系统
0x02 Web 漏洞案例库
0x03 小程序漏洞案例库
第一章:小程序渗透基础
1.1 微信小程序反编译与动态调试
1.2 微信小程序强制开启开发者模式
0x99 信息安全学习体系
01-网络安全基础
Day-001-TCP-IP协议栈安全分析
Day-002-DNS协议安全与DNS劫持攻防
Day-003-IPv6 安全基础与过渡
Day-004-HTTP-HTTPS协议深度解析
Day-005-网络嗅探与流量分析技术
Day-006-防火墙原理与配置实践
Day-007-网络地址转换 NAT 安全分析
Day-008-路由协议安全 RIP-OSPF-BGP
Day-009-VLAN 安全与 VLAN-Hopping
Day-010-无线网络基础与安全 802.11
Day-011-网络访问控制 802.1X-NAC
Day-012-网络分段与微隔离设计
Day-013-负载均衡器安全配置
Day-014-CDN安全与防护
Day-015-NTP安全
Day-016-DHCP安全与攻击防护
Day-017-ICMP协议安全分析
Day-018-网络协议模糊测试基础
Day-019-网络流量基线建立
Day-020-网络取证基础
Day-021-网络入侵检测系统 NIDS
Day-022-网络入侵防御系统 NIPS
Day-023-网络流量加密与解密
Day-024-网络协议逆向工程基础
Day-025-网络性能与安全权衡
Day-026-SDN 安全
Day-027-网络虚拟化安全
Day-028-网络欺骗技术
Day-029-网络威胁情报应用
Day-030-网络容量规划与安全
Day-031-网络安全架构设计实战
02-Web 安全
Day-032-OWASP-Top-10-2021详解
Day-033-SQL 注入原理与手工检测
Day-034-SQL注入进阶报错注入与盲注
Day-035-XSS跨站脚本攻击基础
Day-036-XSS 进阶绕过与利用
Day-037-XSS进阶绕过与利用
Day-038-CSRF 跨站请求伪造
Day-039-文件上传漏洞
Day-040-反序列化漏洞基础
Day-041-PHP反序列化深入
Day-042-Java反序列化深入
Day-043-SSTI 服务端模板注入
Day-044-文件包含漏洞 LFI-RFI
Day-045-命令注入漏洞
Day-046-XXE-XML 外部实体注入
Day-047-反序列化漏洞进阶
Day-048-API 安全基础
Day-049-API认证与授权安全
Day-050-API漏洞挖掘实战
Day-051-文件上传漏洞进阶
Day-052-反序列化漏洞实战
Day-053-Web 安全综合实战
Day-054-移动安全基础
Day-055-Android 应用安全测试
Day-056-iOS 应用安全测试
Day-057-移动应用综合实战
Day-058-云安全基础
Day-059-AWS 安全实战
Day-060-Azure 安全实战
Day-061-GCP 安全实战
Day-062-云安全综合实战
Day-063-容器安全基础
Day-064-Docker 安全实战
Day-065-Kubernetes 安全实战
Day-066-容器安全综合实战
Day-067-API 安全进阶
Day-068-服务端请求伪造 SSRF 深入
Day-069-文件上传漏洞进阶
Day-070-反序列化漏洞实战进阶
Day-071-业务逻辑漏洞深入
Day-072-前端安全深入
Day-073-Web 安全综合实战
Day-074-云安全进阶
Day-075-移动安全进阶
Day-076-API 安全进阶
Day-077-前端安全进阶
Day-078-业务逻辑漏洞进阶
Day-079-反序列化漏洞实战进阶
Day-080-文件上传漏洞实战进阶
Day-081-SSTI 服务端模板注入进阶
Day-082-XXE-XML 外部实体注入进阶
Day-083-SSRF 服务端请求伪造进阶
Day-084-命令注入漏洞进阶
Day-085-文件包含漏洞进阶
Day-086-反序列化漏洞实战进阶
Day-087-文件上传漏洞实战进阶
Day-088-SSTI 服务端模板注入实战进阶
Day-089-XXE-XML 外部实体注入实战进阶
Day-090-SSRF 服务端请求伪造实战进阶
Day-091-命令注入漏洞实战进阶
Day-092-Web 安全综合实战
Day-093-GraphQL 安全
Day-094-JWT 与 OAuth2 安全
03-系统安全
Day-095-系统监控与检测
Day-096-主机防火墙配置
Day-097-系统审计与合规
Day-098-Linux 系统安全进阶
Day-099-Windows 系统安全进阶
Day-100-容器安全进阶
Day-101-容器编排安全进阶
Day-102-Linux 内核安全
Day-103-Windows 内核安全
Day-104-系统安全总结与实战
Day-105-Linux 系统安全基础
Day-106-Windows 系统安全基础
Day-107-容器安全基础
Day-108-系统加固技术
Day-109-日志分析技术
Day-110-威胁狩猎技术
04-应用安全
Day-111-安全编码规范
Day-112-输入验证技术
Day-113-输出编码技术
Day-114-错误处理安全
Day-115-会话管理安全
Day-116-认证安全
Day-117-授权安全
Day-118-数据保护安全
Day-119-日志安全
Day-120-API 安全
Day-121-微服务安全
Day-122-新兴技术安全概论
Day-123-DevSecOps 流水线安全
Day-124-云原生安全架构
Day-125-API 安全最佳实践
Day-126-安全编码规范
Day-127-SDL 安全开发生命周期
Day-128-威胁建模实战
Day-129-安全需求分析
Day-130-安全架构设计
Day-131-安全编码实践Java
Day-132-安全编码实践Python
Day-133-代码审计方法论
Day-134-静态代码分析SAST
Day-135-动态应用测试DAST
Day-136-交互式测试IAST
Day-137-软件成分分析SCA
Day-138-依赖漏洞管理
Day-139-安全测试自动化
Day-140-漏洞管理与响应
Day-141-应用安全总结与展望
Day-142-OWASP-Top10-2024 详解
Day-143-CWE-Top25 分析
Day-144-漏洞挖掘方法论
Day-145-模糊测试技术
Day-146-逆向工程基础
Day-147-漏洞利用开发基础
Day-148-漏洞复现与验证
Day-149-漏洞披露流程
Day-150-CVE 申请与管理
Day-151-漏洞赏金计划
Day-152-等保2.0详解
Day-153-GDPR 合规实践
Day-154-数据安全法解读
Day-155-个人信息保护法与合规指南
Day-156-个人信息保护法解读
Day-157-ISO-27001 信息安全管理体系
Day-158-SOC-2 合规与审计
Day-159-PCI-DSS 支付卡行业数据安全标准
Day-160-网络安全审查办法解读
Day-161-数据出境安全评估办法
Day-162-应用安全评估实战
Day-163-红蓝对抗演练
Day-164-安全应急响应
Day-165-安全运营中心建设
Day-166-应用安全总结与展望
05-密码学
Day-167-密码学基础
Day-168-对称加密算法详解
Day-169-非对称加密算法详解
Day-170-哈希函数与数字签名
Day-171-密钥管理与PKI
Day-172-TLS-SSL 协议详解
Day-173-国密算法详解
Day-174-认证与密钥协议
Day-175-随机数生成与熵源
Day-176-椭圆曲线密码学详解
Day-177-后量子密码学详解
Day-178-高级密码学主题
Day-179-密码学行业应用精选
Day-180-常用加密算法原理与实现
Day-181-密码学总结与展望
Day-182-密码学系列总结与展望
06-渗透测试
Day-183-渗透测试方法论
Day-184-信息收集技术详解
Day-185-漏洞扫描技术详解
Day-186-漏洞利用技术详解
Day-187-渗透测试中的漏洞利用框架
Day-188-漏洞利用框架与 Metasploit 深入
Day-189-渗透测试中的 WAF 绕过技术
Day-190-渗透测试中的模糊测试技术
Day-191-渗透测试中的代码审计与静态分析
Day-192-渗透测试中的密码哈希破解技术
Day-193-渗透测试报告编写指南
Day-194-Web 应用渗透测试
Day-195-渗透测试中的 API 安全测试
Day-196-渗透测试中的 GraphQL 安全测试
Day-197-渗透测试中的前后端分离应用测试
Day-198-渗透测试中的小程序安全测试
Day-199-渗透测试中的浏览器安全测试
Day-200-OAuth-SSO安全测试
Day-201-渗透测试中的业务逻辑漏洞测试
Day-202-渗透测试中的厚客户端安全测试
Day-203-渗透测试综合实战演练
Day-204-内网渗透技术详解
Day-205-渗透测试中的内网信息收集进阶
Day-206-渗透测试中的域森林渗透技术
Day-207-渗透测试中的权限维持技术
Day-208-渗透测试中的横向移动技术
Day-209-渗透测试中的痕迹清理与反取证技术
Day-210-渗透测试中的数据窃取与 Exfiltration 技术
Day-211-渗透测试中的内部威胁与数据泄露测试
Day-212-渗透测试中的物理安全渗透
Day-213-社会工程学攻击技术
Day-214-移动应用渗透测试
Day-215-云安全渗透测试
Day-216-渗透测试中的容器与 Kubernetes 安全渗透
Day-217-渗透测试中的 Serverless 安全测试
Day-218-渗透测试中的微服务安全测试
Day-219-物联网安全渗透测试
Day-220-工业控制系统安全渗透测试
Day-221-无线网络安全渗透测试
Day-222-数据库安全渗透测试
Day-223-渗透测试中的供应链安全测试
Day-224-红队演练技术详解
Day-225-渗透测试中的红队基础设施搭建
Day-226-渗透测试中的威胁情报与狩猎
Day-227-渗透测试中的综合指纹识别技术
Day-228-自动化渗透测试技术
Day-229-渗透测试中的运维安全测试
Day-230-渗透测试中的区块链与智能合约安全测试
Day-231-渗透测试中的漏洞管理与修复验证
Day-232-渗透测试法律与合规
Day-233-后渗透攻击技术详解
Day-234-渗透测试中的人工智能应用
Day-235-漏洞利用开发深入
Day-236-云原生渗透测试深入
07-应急响应
Day-237-应急响应概述与核心概念
Day-238-应急响应流程框架
Day-239-CSIRT 团队组建与职责分工
Day-240-应急响应工具包准备
Day-241-应急响应法律与合规要求
Day-242-安全事件检测方法与指标
Day-243-云原生应急响应
Day-244-日志收集与分析技术
Day-245-网络流量分析与异常识别
Day-246-自动化响应与 SOAR
Day-247-端点监控与 EDR 技术
Day-248-威胁狩猎方法论
Day-249-威胁情报在检测中的应用
Day-250-数字取证基础与证据链管理
Day-251-内存取证技术
Day-252-磁盘取证与文件恢复
Day-253-网络取证与数据包分析
Day-254-云环境与容器取证
Day-255-恶意代码静态分析技术
Day-256-恶意代码动态分析技术
Day-257-恶意代码行为分析方法
Day-258-逆向工程基础与工具
Day-259-沙箱技术与自动化分析
Day-260-事件隔离与遏制策略
Day-261-威胁根除与系统修复
Day-262-系统恢复与数据重建
Day-263-业务连续性计划
Day-264-事件复盘与经验总结
Day-265-APT 攻击事件复盘分析
Day-266-勒索软件事件响应实战
Day-267-数据泄露事件处置流程
Day-268-内部威胁调查与取证
Day-269-综合应急响应演练
08-安全运维
Day-270-安全运营中心 SOC 概述
Day-271-安全监控指标体系
Day-272-安全告警管理
Day-273-安全可视化与仪表盘
Day-274-监控工具选型
Day-275-日志采集技术
Day-276-日志标准化与解析
Day-277-日志存储与归档
Day-278-日志分析技术
Day-279-日志合规要求
Day-280-SIEM 架构与设计
Day-281-关联规则引擎
Day-282-高级关联分析
Day-283-UEBA 用户实体行为分析
Day-284-威胁狩猎
Day-285-SOAR 基础概念
Day-286-剧本设计
Day-287-自动化响应技术
Day-288-安全工具集成
Day-289-SOAR 度量与优化
Day-290-安全基线管理
Day-291-漏洞管理流程
Day-292-补丁管理策略
Day-293-变更安全管理
Day-294-合规审计技术
Day-295-7x24 安全运营
Day-296-安全事件管理流程
Day-297-安全运营度量体系
Day-298-持续改进机制
Day-299-安全运维综合演练
Day-300-云原生安全运营
Day-301-AI 与机器学习安全运营
Day-302-安全自动化脚本实战
09-移动安全
Day-303-移动安全威胁概述
Day-304-移动设备安全架构
Day-305-移动操作系统安全模型
Day-306-移动应用权限管理
Day-307-移动端数据加密
Day-308-330-Android 安全合集
Day-309-Android 安全架构
Day-310-Android 组件安全
Day-311-Android 权限与隐私
Day-312-Android 逆向工程
Day-313-Android 应用加固
Day-314-iOS 安全架构
Day-315-iOS 应用沙盒机制
Day-316-越狱与反越狱
Day-317-iOS 逆向工程
Day-318-iOS 企业分发安全
Day-319-移动安全开发生命周期
Day-320-移动应用安全测试
Day-321-移动应用加固技术
Day-322-移动威胁防护
Day-323-移动安全合规
10-云安全
Day-324-云计算安全模型
Day-325-责任共担模型
Day-326-云安全威胁模型
Day-327-云安全合规框架
Day-328-云安全架构设计
Day-329-AWS IAM 安全
Day-330-AWS 网络安全
Day-331-AWS 存储安全
Day-332-AWS 安全监控
Day-333-AWS 安全最佳实践
Day-334-Azure AD 安全
Day-335-Azure 网络安全
Day-336-Azure 存储安全
Day-337-Azure 安全中心
Day-338-Azure 安全最佳实践
Day-339-容器安全基础
Day-340-Kubernetes 安全
Day-341-Serverless 安全
Day-342-云原生 DevSecOps
Day-343-云安全态势管理 CSPM
11-物联网工控
Day-344-物联网安全概述
Day-345-IoT 通信协议安全
Day-346-IoT 设备安全
Day-347-IoT 平台安全
Day-348-IoT 应用安全
Day-349-工业控制系统概述
Day-350-工控协议安全
Day-351-PLC 安全
Day-352-SCADA 系统安全
Day-353-工控安全防护
12-综合与总结
Day-354-安全职业发展路径
Day-355-安全技术趋势展望
Day-356-安全建设方法论
Day-357-经典攻防案例复盘
Day-358-安全学习资源指南
Day-359-信息安全行业求职指南
-
+
首页
Day-131-安全编码实践Java
# Day 145: 安全编码实践 (Java) > 应用安全系列第 38 天 | 预计阅读时间:50 分钟 | 难度:★★★★☆ --- **PUA v3 · Sprint 启动** ``` ┌─────────┬────────────────────────────────────┐ │ 清单 任务 │ 安全编码实践 (Java) - Day 145 │ ├─────────┼────────────────────────────────────┤ │ 味道 │ 阿里味(自动:安全任务) │ ├─────────┼────────────────────────────────────┤ │ 压力 │ L0 · 信任期 │ └─────────┴────────────────────────────────────┘ ``` ▎ 安全编码不是写完检查,是写的时候就安全。写的时候不安全,检查就是返工的。今天深入 Java 安全编码实践。 --- ## 清单 目录 1. [Java 安全编码概述](#java 安全编码概述) 2. [输入验证安全](#输入验证安全) 3. [输出编码安全](#输出编码安全) 4. [认证与会话安全](#认证与会话安全) 5. [数据访问安全](#数据访问安全) 6. [文件操作安全](#文件操作安全) 7. [加密与随机数](#加密与随机数) 8. [异常处理安全](#异常处理安全) 9. [依赖与配置安全](#依赖与配置安全) 10. [安全代码审查](#安全代码审查) 11. [总结与思考](#总结与思考) 12. [参考资料](#参考资料) --- ## Java 安全编码概述 ### 为什么 Java 需要安全编码 > ▎ Java 不是类型安全就万事大吉,应用层漏洞照样层出不穷。类型安全不等于应用安全。 **Java 安全现状**: ``` 常见 Java 安全漏洞 (OWASP 数据): 1. 注入漏洞 (Injection) - SQL 注入 - OS 命令注入 - LDAP 注入 - 占比:35% 2. 失效的访问控制 - 越权访问 - 未授权访问 - 占比:20% 3. 敏感数据泄露 - 明文存储 - 传输未加密 - 占比:15% 4. XXE (XML 外部实体) - XML 解析漏洞 - 占比:10% 5. 反序列化漏洞 - 不安全反序列化 - 占比:10% 6. 其他 - 安全配置错误 - 不安全的依赖 - 占比:10% ``` **安全编码价值**: ``` 预防成本 vs 修复成本: 阶段 预防成本 修复成本 ROI ───────────────────────────────────────── 编码时 1 单位 - - 测试时发现 - 10 单位 1:10 生产发现 - 100 单位 1:100 安全编码投资回报: - 每投入 1 小时安全编码,节省 10 小时修复 - 漏洞密度降低 50-70% - 安全审查时间减少 40-60% ``` ### Java 安全资源 **官方资源**: ```java // Oracle Java Security Guidelines // https://www.oracle.com/java/technologies/javase/seccodeguide.html // OWASP Java Security Cheat Sheet // https://cheatsheetseries.owasp.org/cheatsheets/Java_Security_Cheat_Sheet.html // CERT Oracle Secure Coding Standard for Java // https://wiki.sei.cmu.edu/confluence/display/java ``` **安全库**: ```java // 推荐使用的安全库 dependencies { // 密码学 implementation 'org.bouncycastle:bcprov-jdk15on:1.70' // 密码哈希 implementation 'org.mindrot:jbcrypt:0.4' implementation 'de.mkammerer:argon2-jvm:2.11' // HTML 编码 implementation 'org.owasp.encoder:encoder:1.2.3' // 输入验证 implementation 'javax.validation:validation-api:2.0.1.Final' implementation 'org.hibernate.validator:hibernate-validator:6.2.0.Final' // 安全随机数 // Java 内置 SecureRandom // JWT implementation 'io.jsonwebtoken:jjwt-api:0.11.5' implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' } ``` --- ## 输入验证安全 ### 输入验证原则 > ▎ 输入验证不是信任前端,是防御第一关。前端验证可绕过,服务端必须验证。 **验证原则**: ``` 1. 白名单验证 (Allowlist) - 只允许已知好的 - 优于黑名单 - 更安全 2. 服务端验证 - 客户端验证可绕过 - 服务端必须验证 - 双重验证更佳 3. 类型、长度、范围 - 检查数据类型 - 检查长度限制 - 检查取值范围 4. 早验证、多验证 - 尽早验证输入 - 多处验证 - 边界验证 ``` ### 输入验证实现 **使用 Bean Validation**: ```java // 安全的输入验证示例 import javax.validation.Valid; import javax.validation.constraints.*; import javax.validation.constraintvalidation.SupportedValidationTarget; import javax.validation.constraintvalidation.ValidationTarget; // 用户注册 DTO public class UserRegistrationDTO { // 用户名:3-20 位,字母数字下划线 @NotNull(message = "用户名不能为空") @Size(min = 3, max = 20, message = "用户名长度必须在 3-20 之间") @Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只能包含字母、数字、下划线") private String username; // 邮箱:标准邮箱格式 @NotNull(message = "邮箱不能为空") @Email(message = "邮箱格式不正确") @Size(max = 254, message = "邮箱长度不能超过 254") private String email; // 密码:最小 12 位,复杂度要求 @NotNull(message = "密码不能为空") @Size(min = 12, max = 128, message = "密码长度必须在 12-128 之间") @Pattern( regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*(),.?\":{}|<>]).+$", message = "密码必须包含大小写字母、数字、特殊字符" ) private String password; // 手机号:国际标准 @Pattern( regexp = "^\\+?[1-9]\\d{1,14}$", message = "手机号格式不正确" ) private String phone; // 年龄:合理范围 @Min(value = 0, message = "年龄不能为负数") @Max(value = 150, message = "年龄不能超过 150") private Integer age; // Getters and Setters } // 控制器层验证 @RestController @RequestMapping("/api/users") public class UserController { @Autowired private Validator validator; @PostMapping public ResponseEntity<?> registerUser(@Valid @RequestBody UserRegistrationDTO dto) { // @Valid 自动触发验证 // 验证失败会抛出 MethodArgumentNotValidException // 无需手动检查 userService.register(dto); return ResponseEntity.ok().build(); } // 手动验证示例 @PostMapping("/manual") public ResponseEntity<?> registerUserManual(@RequestBody UserRegistrationDTO dto) { Set<ConstraintViolation<UserRegistrationDTO>> violations = validator.validate(dto); if (!violations.isEmpty()) { List<String> errors = violations.stream() .map(ConstraintViolation::getMessage) .collect(Collectors.toList()); return ResponseEntity.badRequest().body(errors); } userService.register(dto); return ResponseEntity.ok().build(); } } ``` **自定义验证器**: ```java // 自定义验证注解 @Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = NoSqlInjectionValidator.class) @Documented public @interface NoSqlInjection { String message() default "输入可能包含 SQL 注入字符"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } // 自定义验证器实现 public class NoSqlInjectionValidator implements ConstraintValidator<NoSqlInjection, String> { // SQL 注入危险字符 private static final Pattern DANGEROUS_PATTERN = Pattern.compile( "(?i)(union|select|insert|update|delete|drop|truncate|exec|execute|" + "--|;|/\\*|\\*/|xp_|sp_|0x|char\\(|nchar\\(|varchar\\|nvarchar\\()" ); @Override public boolean isValid(String value, ConstraintValidatorContext context) { if (value == null || value.isEmpty()) { return true; // 空值由@NotNull 控制 } // 检查危险模式 if (DANGEROUS_PATTERN.matcher(value).find()) { return false; } return true; } } // 使用自定义验证 public class SearchDTO { @NoSqlInjection(message = "搜索词包含危险字符") @Size(max = 200, message = "搜索词长度不能超过 200") private String searchTerm; // Getter and Setter } ``` **输入清理**: ```java // 安全的输入清理工具 import org.owasp.encoder.Encode; import java.text.Normalizer; public class SecureInputUtil { /** * 清理字符串输入 */ public static String sanitizeString(String input, int maxLength) { if (input == null) { return null; } // 1. 长度限制 if (input.length() > maxLength) { input = input.substring(0, maxLength); } // 2. 标准化 Unicode input = Normalizer.normalize(input, Normalizer.Form.NFC); // 3. 移除控制字符 input = input.replaceAll("[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F\\x7F]", ""); // 4. 去除首尾空白 input = input.trim(); return input; } /** * 清理 HTML 输入 (防止 XSS) */ public static String sanitizeHtml(String input) { if (input == null) { return null; } // 使用 OWASP Encoder 进行 HTML 编码 return Encode.forHtml(input); } /** * 验证并清理文件名 */ public static String sanitizeFilename(String filename) { if (filename == null || filename.isEmpty()) { throw new IllegalArgumentException("文件名不能为空"); } // 只允许安全字符 if (!filename.matches("^[a-zA-Z0-9._-]+$")) { throw new IllegalArgumentException("文件名包含非法字符"); } // 防止路径遍历 if (filename.contains("..") || filename.contains("/") || filename.contains("\\")) { throw new IllegalArgumentException("文件名包含路径字符"); } // 长度限制 if (filename.length() > 255) { throw new IllegalArgumentException("文件名过长"); } return filename; } /** * 验证整数范围 */ public static int validateIntegerRange(Integer value, int min, int max, int defaultValue) { if (value == null) { return defaultValue; } if (value < min || value > max) { throw new IllegalArgumentException( String.format("值必须在 %d 到 %d 之间", min, max) ); } return value; } } ``` --- ## 输出编码安全 ### XSS 防护 > ▎ 输出编码不是可选项,是必选项。输出不编码,XSS 就是必然的。 **输出编码实现**: ```java // 安全的输出编码 import org.owasp.encoder.Encode; import javax.servlet.jsp.JspWriter; public class SecureOutputUtil { /** * HTML 内容编码 */ public static String encodeForHtml(String input) { if (input == null) { return ""; } return Encode.forHtml(input); } /** * HTML 属性编码 */ public static String encodeForHtmlAttribute(String input) { if (input == null) { return ""; } return Encode.forHtmlAttribute(input); } /** * JavaScript 编码 */ public static String encodeForJavaScript(String input) { if (input == null) { return ""; } return Encode.forJavaScript(input); } /** * URL 编码 */ public static String encodeForUrl(String input) { if (input == null) { return ""; } return Encode.forUriComponent(input); } /** * CSS 编码 */ public static String encodeForCss(String input) { if (input == null) { return ""; } return Encode.forCssString(input); } } // JSP 安全输出示例 /* 错误示例 (XSS 漏洞): <%= userInput %> ${userInput} 正确示例 (编码输出): <%= Encode.forHtml(userInput) %> ${fn:escapeXml(userInput)} 使用 JSTL: <c:out value="${userInput}" escapeXml="true" /> */ // Thymeleaf 安全输出 /* Thymeleaf 默认自动转义: <th:text="${userInput}"></th> 显式转义: <th:text="${#strings.escapeXml(userInput)}"></th> 不转义 (仅在可信内容时使用): <th:utext="${trustedHtml}"></th> */ ``` ### 安全响应头 ```java // 安全响应头配置 import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class SecurityHeadersFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 防止点击劫持 response.setHeader("X-Frame-Options", "DENY"); // XSS 防护 response.setHeader("X-XSS-Protection", "1; mode=block"); // 防止 MIME 类型嗅探 response.setHeader("X-Content-Type-Options", "nosniff"); // 内容安全策略 response.setHeader("Content-Security-Policy", "default-src 'self'; " + "script-src 'self'; " + "style-src 'self' 'unsafe-inline'; " + "img-src 'self' data:; " + "font-src 'self'"); // Referrer 策略 response.setHeader("Referrer-Policy", "strict-origin-when-cross-origin"); // 权限策略 response.setHeader("Permissions-Policy", "geolocation=(), microphone=(), camera=()"); // HTTP Strict Transport Security (HSTS) response.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload"); filterChain.doFilter(request, response); } } // Spring Security 配置 @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .headers(headers -> headers .frameOptions(FrameOptionsConfig::deny) .xssProtection(xss -> xss.block(true)) .contentTypeOptions(Customizer.withDefaults()) .httpStrictTransportSecurity(hsts -> hsts .maxAgeInSeconds(31536000) .includeSubDomains(true) .preload(true)) .contentSecurityPolicy(csp -> csp .policyDirectives( "default-src 'self'; " + "script-src 'self'; " + "style-src 'self' 'unsafe-inline'" )) ); return http.build(); } } ``` --- ## 认证与会话安全 ### 密码安全 > ▎ 密码不是明文存储,是哈希存储。哈希不安全,密码就是裸奔的。 **密码哈希实现**: ```java // 安全的密码处理 import org.mindrot.jbcrypt.BCrypt; import de.mkammerer.argon2.Argon2; import de.mkammerer.argon2.Argon2Factory; public class SecurePasswordUtil { // BCrypt 成本因子 (越高越安全,但越慢) private static final int BCRYPT_ROUNDS = 12; // Argon2 参数 private static final int ARGON2_ITERATIONS = 3; private static final int ARGON2_MEMORY = 65536; // 64 MB private static final int ARGON2_PARALLELISM = 4; /** * 使用 BCrypt 哈希密码 */ public static String hashPasswordBCrypt(String password) { if (password == null || password.isEmpty()) { throw new IllegalArgumentException("密码不能为空"); } // BCrypt 自动生成盐值 return BCrypt.hashpw(password, BCrypt.gensalt(BCRYPT_ROUNDS)); } /** * 使用 Argon2 哈希密码 (推荐) */ public static String hashPasswordArgon2(String password) { if (password == null || password.isEmpty()) { throw new IllegalArgumentException("密码不能为空"); } Argon2 argon2 = Argon2Factory.create(Argon2Factory.Argon2Types.ARGON2id); char[] passwordChars = password.toCharArray(); try { return argon2.hash(ARGON2_ITERATIONS, ARGON2_MEMORY, ARGON2_PARALLELISM, passwordChars); } finally { argon2.wipeArray(passwordChars); } } /** * 验证密码 (BCrypt) */ public static boolean verifyPasswordBCrypt(String password, String hashedPassword) { if (password == null || hashedPassword == null) { return false; } try { return BCrypt.checkpw(password, hashedPassword); } catch (IllegalArgumentException e) { // 哈希格式错误 return false; } } /** * 验证密码 (Argon2) */ public static boolean verifyPasswordArgon2(String password, String hashedPassword) { if (password == null || hashedPassword == null) { return false; } Argon2 argon2 = Argon2Factory.create(Argon2Factory.Argon2Types.ARGON2id); char[] passwordChars = password.toCharArray(); try { return argon2.verify(hashedPassword, passwordChars); } finally { argon2.wipeArray(passwordChars); } } /** * 检查密码是否需要重新哈希 */ public static boolean needsRehash(String hashedPassword) { // BCrypt 检查 if (hashedPassword.startsWith("$2a$") || hashedPassword.startsWith("$2b$")) { String[] parts = hashedPassword.split("\\$"); if (parts.length >= 3) { int cost = Integer.parseInt(parts[2]); return cost < BCRYPT_ROUNDS; } } // Argon2 检查 (简化) // 实际应检查 Argon2 参数 return false; } } // 密码策略实现 public class PasswordPolicy { private static final int MIN_LENGTH = 12; private static final int MAX_LENGTH = 128; // 常见密码黑名单 private static final Set<String> COMMON_PASSWORDS = Set.of( "password", "123456", "qwerty", "admin", "letmein", "welcome", "monkey", "dragon", "master", "login" ); /** * 验证密码强度 */ public static PasswordValidationResult validatePassword(String password) { List<String> errors = new ArrayList<>(); // 长度检查 if (password.length() < MIN_LENGTH) { errors.add(String.format("密码长度必须至少 %d 位", MIN_LENGTH)); } if (password.length() > MAX_LENGTH) { errors.add(String.format("密码长度不能超过 %d 位", MAX_LENGTH)); } // 复杂度检查 if (!password.matches(".*[a-z].*")) { errors.add("密码必须包含小写字母"); } if (!password.matches(".*[A-Z].*")) { errors.add("密码必须包含大写字母"); } if (!password.matches(".*\\d.*")) { errors.add("密码必须包含数字"); } if (!password.matches(".*[!@#$%^&*(),.?\":{}|<>].*")) { errors.add("密码必须包含特殊字符"); } // 常见密码检查 if (COMMON_PASSWORDS.contains(password.toLowerCase())) { errors.add("密码太常见,请使用更复杂的密码"); } // 连续字符检查 if (hasSequentialChars(password)) { errors.add("密码不能包含连续字符 (如 123, abc)"); } // 重复字符检查 if (hasRepeatedChars(password)) { errors.add("密码不能包含过多重复字符"); } return new PasswordValidationResult(errors.isEmpty(), errors); } private static boolean hasSequentialChars(String password) { String lower = password.toLowerCase(); for (int i = 0; i < lower.length() - 2; i++) { String seq = lower.substring(i, i + 3); if (seq.chars().allMatch(Character::isLetter)) { char[] chars = seq.toCharArray(); if (chars[1] == chars[0] + 1 && chars[2] == chars[1] + 1) { return true; } } if (seq.chars().allMatch(Character::isDigit)) { char[] chars = seq.toCharArray(); if (chars[1] == chars[0] + 1 && chars[2] == chars[1] + 1) { return true; } } } return false; } private static boolean hasRepeatedChars(String password) { for (int i = 0; i < password.length() - 3; i++) { if (password.charAt(i) == password.charAt(i + 1) && password.charAt(i) == password.charAt(i + 2) && password.charAt(i) == password.charAt(i + 3)) { return true; } } return false; } public static class PasswordValidationResult { private final boolean valid; private final List<String> errors; public PasswordValidationResult(boolean valid, List<String> errors) { this.valid = valid; this.errors = errors; } public boolean isValid() { return valid; } public List<String> getErrors() { return errors; } } } ``` ### 会话安全 ```java // 安全的会话管理 import javax.servlet.http.HttpSession; import javax.servlet.http.HttpServletRequest; import java.util.concurrent.ConcurrentHashMap; public class SecureSessionManager { // 会话超时时间 (秒) private static final int SESSION_TIMEOUT = 1800; // 30 分钟 // 绝对超时时间 (秒) private static final int ABSOLUTE_TIMEOUT = 28800; // 8 小时 /** * 创建安全会话 */ public static HttpSession createSecureSession(HttpServletRequest request) { HttpSession session = request.getSession(true); // 设置超时 session.setMaxInactiveInterval(SESSION_TIMEOUT); // 记录创建时间和 IP session.setAttribute("CREATION_TIME", System.currentTimeMillis()); session.setAttribute("CREATION_IP", request.getRemoteAddr()); session.setAttribute("LAST_ACCESSED_TIME", System.currentTimeMillis()); return session; } /** * 会话 ID 轮换 (防止会话固定攻击) */ public static HttpSession regenerateSession(HttpServletRequest request, HttpSession oldSession) { // 保存旧会话数据 ConcurrentHashMap<String, Object> sessionData = new ConcurrentHashMap<>(); oldSession.getAttributeNames().asIterator().forEachRemaining(name -> { sessionData.put(name, oldSession.getAttribute(name)); }); // 使旧会话无效 oldSession.invalidate(); // 创建新会话 HttpSession newSession = request.getSession(true); newSession.setMaxInactiveInterval(SESSION_TIMEOUT); // 恢复会话数据 sessionData.forEach(newSession::setAttribute); // 更新创建时间 newSession.setAttribute("LAST_REGENERATION_TIME", System.currentTimeMillis()); return newSession; } /** * 验证会话有效性 */ public static boolean validateSession(HttpServletRequest request, HttpSession session) { if (session == null) { return false; } Long creationTime = (Long) session.getAttribute("CREATION_TIME"); if (creationTime == null) { return false; } // 检查绝对超时 long now = System.currentTimeMillis(); if (now - creationTime > ABSOLUTE_TIMEOUT * 1000L) { session.invalidate(); return false; } // 可选:检查 IP 变更 // String creationIp = (String) session.getAttribute("CREATION_IP"); // if (!creationIp.equals(request.getRemoteAddr())) { // session.invalidate(); // return false; // } // 更新最后访问时间 session.setAttribute("LAST_ACCESSED_TIME", now); return true; } /** * 安全登出 */ public static void secureLogout(HttpServletRequest request) { HttpSession session = request.getSession(false); if (session != null) { // 清除敏感数据 session.removeAttribute("USER_ID"); session.removeAttribute("USERNAME"); session.removeAttribute("ROLE"); // 使会话无效 session.invalidate(); } // 清除认证 Cookie Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { if (cookie.getName().equals("JSESSIONID")) { cookie.setMaxAge(0); cookie.setPath("/"); // 需要添加到响应中才能生效 } } } } } ``` --- ## 数据访问安全 ### SQL 注入防护 > ▎ SQL 注入不是老漏洞,是常青树。参数化不用,注入就是必然的。 **参数化查询**: ```java // 安全的数据库操作 import java.sql.*; import javax.sql.DataSource; public class SecureDatabaseUtil { private final DataSource dataSource; public SecureDatabaseUtil(DataSource dataSource) { this.dataSource = dataSource; } /** * 安全查询 - PreparedStatement */ public User getUserById(int userId) throws SQLException { String sql = "SELECT id, username, email FROM users WHERE id = ?"; try (Connection conn = dataSource.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql)) { // 正确:使用参数化查询 stmt.setInt(1, userId); try (ResultSet rs = stmt.executeQuery()) { if (rs.next()) { return mapUser(rs); } return null; } } } /** * 安全查询 - 多个参数 */ public List<User> searchUsers(String username, String email) throws SQLException { String sql = "SELECT id, username, email FROM users " + "WHERE username LIKE ? AND email LIKE ?"; try (Connection conn = dataSource.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql)) { // 正确:参数化 LIKE 查询 stmt.setString(1, "%" + username + "%"); stmt.setString(2, "%" + email + "%"); try (ResultSet rs = stmt.executeQuery()) { List<User> users = new ArrayList<>(); while (rs.next()) { users.add(mapUser(rs)); } return users; } } } /** * 安全插入 */ public int createUser(String username, String email, String passwordHash) throws SQLException { String sql = "INSERT INTO users (username, email, password_hash) " + "VALUES (?, ?, ?)"; try (Connection conn = dataSource.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { stmt.setString(1, username); stmt.setString(2, email); stmt.setString(3, passwordHash); int affectedRows = stmt.executeUpdate(); if (affectedRows == 0) { throw new SQLException("创建用户失败"); } try (ResultSet generatedKeys = stmt.getGeneratedKeys()) { if (generatedKeys.next()) { return generatedKeys.getInt(1); } throw new SQLException("创建用户失败,未获取 ID"); } } } /** * 安全动态排序 */ public List<User> getUsersSorted(String sortBy, String sortOrder) throws SQLException { // 白名单验证排序字段 Set<String> allowedSortFields = Set.of("id", "username", "email", "created_at"); if (!allowedSortFields.contains(sortBy)) { sortBy = "id"; // 默认 } // 验证排序方向 if (!"ASC".equalsIgnoreCase(sortOrder) && !"DESC".equalsIgnoreCase(sortOrder)) { sortOrder = "ASC"; // 默认 } // 正确:排序字段不能参数化,必须白名单验证后拼接 String sql = "SELECT id, username, email FROM users ORDER BY " + sortBy + " " + sortOrder; try (Connection conn = dataSource.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql); ResultSet rs = stmt.executeQuery()) { List<User> users = new ArrayList<>(); while (rs.next()) { users.add(mapUser(rs)); } return users; } } /** * 安全 IN 查询 */ public List<User> getUsersByIds(List<Integer> ids) throws SQLException { if (ids == null || ids.isEmpty()) { return new ArrayList<>(); } // 限制数量防止资源耗尽 if (ids.size() > 100) { ids = ids.subList(0, 100); } // 构建参数化 IN 子句 String placeholders = ids.stream() .map(i -> "?") .collect(Collectors.joining(", ")); String sql = "SELECT id, username, email FROM users WHERE id IN (" + placeholders + ")"; try (Connection conn = dataSource.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql)) { // 设置参数 for (int i = 0; i < ids.size(); i++) { stmt.setInt(i + 1, ids.get(i)); } try (ResultSet rs = stmt.executeQuery()) { List<User> users = new ArrayList<>(); while (rs.next()) { users.add(mapUser(rs)); } return users; } } } private User mapUser(ResultSet rs) throws SQLException { User user = new User(); user.setId(rs.getInt("id")); user.setUsername(rs.getString("username")); user.setEmail(rs.getString("email")); return user; } } // 不安全的示例 (DO NOT USE) /* // 错误:字符串拼接 SQL public User getUserByIdUnsafe(int userId) throws SQLException { String sql = "SELECT * FROM users WHERE id = " + userId; // SQL 注入! // ... } // 错误:直接拼接用户输入 public User searchUsersUnsafe(String username) throws SQLException { String sql = "SELECT * FROM users WHERE username = '" + username + "'"; // SQL 注入! // ... } */ ``` ### JPA/Hibernate 安全 ```java // JPA 安全查询 import javax.persistence.*; import java.util.List; @Repository public class SecureUserRepository { @PersistenceContext private EntityManager entityManager; /** * 安全查询 - 参数化 JPQL */ public User findByUsername(String username) { String jpql = "SELECT u FROM User u WHERE u.username = :username"; return entityManager.createQuery(jpql, User.class) .setParameter("username", username) .getResultStream() .findFirst() .orElse(null); } /** * 安全查询 - Criteria API (最安全) */ public List<User> searchUsers(String username, String email) { CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<User> query = cb.createQuery(User.class); Root<User> root = query.from(User.class); List<Predicate> predicates = new ArrayList<>(); if (username != null && !username.isEmpty()) { predicates.add(cb.like(root.get("username"), "%" + username + "%")); } if (email != null && !email.isEmpty()) { predicates.add(cb.like(root.get("email"), "%" + email + "%")); } query.where(predicates.toArray(new Predicate[0])); return entityManager.createQuery(query).getResultList(); } /** * 安全原生查询 */ public List<User> findByEmailDomain(String domain) { // 即使是原生查询,也要使用参数化 String sql = "SELECT * FROM users WHERE email LIKE :domain"; return entityManager.createNativeQuery(sql, User.class) .setParameter("domain", "%" + domain) .getResultList(); } /** * 安全排序 */ public List<User> getUsersSorted(String sortBy, String sortOrder) { // 白名单验证 Set<String> allowedFields = Set.of("id", "username", "email", "createdAt"); if (!allowedFields.contains(sortBy)) { sortBy = "id"; } CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<User> query = cb.createQuery(User.class); Root<User> root = query.from(User.class); query.select(root); if ("DESC".equalsIgnoreCase(sortOrder)) { query.orderBy(cb.desc(root.get(sortBy))); } else { query.orderBy(cb.asc(root.get(sortBy))); } return entityManager.createQuery(query).getResultList(); } } ``` --- ## 文件操作安全 ### 路径遍历防护 > ▎ 文件操作不是简单 IO,是安全边界。边界不守,系统就是开放的。 **安全的文件操作**: ```java // 安全的文件操作工具 import java.io.*; import java.nio.file.*; public class SecureFileUtil { /** * 安全读取文件 */ public static byte[] readFileSecurely(String baseDir, String filename) throws IOException { // 1. 清理文件名 String safeFilename = sanitizeFilename(filename); // 2. 解析规范化路径 Path basePath = Paths.get(baseDir).toRealPath(); Path requestedPath = basePath.resolve(safeFilename).normalize(); // 3. 验证路径在基目录内 if (!requestedPath.startsWith(basePath)) { throw new SecurityException("路径遍历攻击尝试"); } // 4. 读取文件 return Files.readAllBytes(requestedPath); } /** * 安全写入文件 */ public static void writeFileSecurely(String baseDir, String filename, byte[] content) throws IOException { // 1. 清理文件名 String safeFilename = sanitizeFilename(filename); // 2. 解析规范化路径 Path basePath = Paths.get(baseDir).toRealPath(); Path requestedPath = basePath.resolve(safeFilename).normalize(); // 3. 验证路径在基目录内 if (!requestedPath.startsWith(basePath)) { throw new SecurityException("路径遍历攻击尝试"); } // 4. 创建父目录 (如果需要) Files.createDirectories(requestedPath.getParent()); // 5. 写入文件 (原子操作) Path tempPath = requestedPath.resolveSibling(requestedPath.getFileName() + ".tmp"); try { Files.write(tempPath, content, StandardOpenOption.CREATE_NEW); Files.move(tempPath, requestedPath, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING); } finally { // 清理临时文件 try { Files.deleteIfExists(tempPath); } catch (IOException e) { // 忽略 } } } /** * 清理文件名 */ public static String sanitizeFilename(String filename) { if (filename == null || filename.isEmpty()) { throw new IllegalArgumentException("文件名不能为空"); } // 1. 获取基本文件名 (去除路径) filename = Paths.get(filename).getFileName().toString(); // 2. 只允许安全字符 if (!filename.matches("^[a-zA-Z0-9._-]+$")) { throw new IllegalArgumentException("文件名包含非法字符"); } // 3. 长度限制 if (filename.length() > 255) { throw new IllegalArgumentException("文件名过长"); } // 4. 禁止特殊文件名 if (filename.equals(".") || filename.equals("..")) { throw new IllegalArgumentException("无效的文件名"); } return filename; } /** * 安全文件上传 */ public static String uploadFileSecurely(Path uploadDir, String originalFilename, InputStream inputStream, long maxSize) throws IOException { // 1. 验证文件大小 if (inputStream.available() > maxSize) { throw new SecurityException("文件过大"); } // 2. 生成安全文件名 String extension = getFileExtension(originalFilename); String safeFilename = UUID.randomUUID().toString() + extension; // 3. 验证文件类型 (基于内容,不是扩展名) byte[] content = inputStream.readAllBytes(); if (!isValidFileType(content, extension)) { throw new SecurityException("不允许的文件类型"); } // 4. 写入文件 writeFileSecurely(uploadDir.toString(), safeFilename, content); return safeFilename; } /** * 获取文件扩展名 */ private static String getFileExtension(String filename) { int lastDot = filename.lastIndexOf('.'); if (lastDot < 0) { return ""; } return filename.substring(lastDot).toLowerCase(); } /** * 验证文件类型 (基于魔数) */ private static boolean isValidFileType(byte[] content, String extension) { // 检查文件魔数 (Magic Number) if (content.length < 4) { return false; } switch (extension) { case ".jpg": case ".jpeg": return content[0] == (byte) 0xFF && content[1] == (byte) 0xD8; case ".png": return content[0] == (byte) 0x89 && content[1] == (byte) 0x50 && content[2] == (byte) 0x4E && content[3] == (byte) 0x47; case ".gif": return new String(content, 0, 3).equals("GIF"); case ".pdf": return new String(content, 0, 4).equals("%PDF"); default: // 默认允许 return true; } } } ``` --- ## 加密与随机数 ### 安全随机数 > ▎ 随机数不是随便数,是安全基础。基础不牢固,安全就是脆弱的。 **SecureRandom 使用**: ```java // 安全的随机数生成 import java.security.SecureRandom; import java.security.NoSuchAlgorithmException; import java.util.Base64; public class SecureRandomUtil { private static final SecureRandom secureRandom; static { try { // 使用强随机数生成器 secureRandom = SecureRandom.getInstanceStrong(); } catch (NoSuchAlgorithmException e) { // 回退到默认 SecureRandom secureRandom = new SecureRandom(); } // 种子初始化 (可选,但推荐) secureRandom.seed(new byte[64]); } /** * 生成安全随机字节 */ public static byte[] generateRandomBytes(int length) { if (length <= 0) { throw new IllegalArgumentException("长度必须大于 0"); } byte[] bytes = new byte[length]; secureRandom.nextBytes(bytes); return bytes; } /** * 生成安全随机字符串 (用于 Token) */ public static String generateRandomToken(int length) { byte[] bytes = generateRandomBytes(length); return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); } /** * 生成安全随机整数 (范围内) */ public static int generateRandomInt(int min, int max) { if (min >= max) { throw new IllegalArgumentException("min 必须小于 max"); } return secureRandom.nextInt(max - min) + min; } /** * 生成安全随机 Long */ public static long generateRandomLong() { return secureRandom.nextLong(); } /** * 生成安全随机 UUID */ public static String generateSecureUUID() { byte[] bytes = generateRandomBytes(16); // 设置 UUID 版本 4 (随机) bytes[6] &= 0x0F; bytes[6] |= 0x40; bytes[8] &= 0x3F; bytes[8] |= 0x80; return String.format( "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15] ); } /** * 生成安全随机密码 */ public static String generateSecurePassword(int length) { if (length < 12) { length = 12; } String upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; String lower = "abcdefghijklmnopqrstuvwxyz"; String digits = "0123456789"; String special = "!@#$%^&*(),.?\":{}|<>"; String all = upper + lower + digits + special; StringBuilder password = new StringBuilder(length); // 确保每种类型至少一个 password.append(upper.charAt(secureRandom.nextInt(upper.length()))); password.append(lower.charAt(secureRandom.nextInt(lower.length()))); password.append(digits.charAt(secureRandom.nextInt(digits.length()))); password.append(special.charAt(secureRandom.nextInt(special.length()))); // 填充剩余长度 for (int i = 4; i < length; i++) { password.append(all.charAt(secureRandom.nextInt(all.length()))); } // 打乱顺序 return shuffleString(password.toString()); } private static String shuffleString(String input) { char[] chars = input.toCharArray(); for (int i = chars.length - 1; i > 0; i--) { int j = secureRandom.nextInt(i + 1); char temp = chars[i]; chars[i] = chars[j]; chars[j] = temp; } return new String(chars); } } // 不安全的随机数 (DO NOT USE) /* // 错误:使用 Random 而不是 SecureRandom Random random = new Random(); // 可预测! int token = random.nextInt(); // 错误:使用 System.currentTimeMillis() 作为种子 Random random = new Random(System.currentTimeMillis()); // 可预测! */ ``` ### 加密实现 ```java // 安全的加密实现 import javax.crypto.*; import javax.crypto.spec.*; import java.security.*; import java.util.Base64; public class SecureEncryptionUtil { // AES 参数 private static final String AES_ALGORITHM = "AES/GCM/NoPadding"; private static final int AES_KEY_SIZE = 256; private static final int GCM_IV_LENGTH = 12; private static final int GCM_TAG_LENGTH = 128; /** * AES-GCM 加密 */ public static byte[] encrypt(byte[] plaintext, SecretKey key) throws GeneralSecurityException { // 生成随机 IV byte[] iv = new byte[GCM_IV_LENGTH]; SecureRandomUtil.getSecureRandom().nextBytes(iv); // 创建 Cipher Cipher cipher = Cipher.getInstance(AES_ALGORITHM); GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, iv); cipher.init(Cipher.ENCRYPT_MODE, key, spec); // 加密 byte[] ciphertext = cipher.doFinal(plaintext); // 返回 IV + 密文 byte[] result = new byte[iv.length + ciphertext.length]; System.arraycopy(iv, 0, result, 0, iv.length); System.arraycopy(ciphertext, 0, result, iv.length, ciphertext.length); return result; } /** * AES-GCM 解密 */ public static byte[] decrypt(byte[] ciphertext, SecretKey key) throws GeneralSecurityException { // 提取 IV byte[] iv = new byte[GCM_IV_LENGTH]; System.arraycopy(ciphertext, 0, iv, 0, iv.length); // 提取密文 byte[] actualCiphertext = new byte[ciphertext.length - iv.length]; System.arraycopy(ciphertext, iv.length, actualCiphertext, 0, actualCiphertext.length); // 创建 Cipher Cipher cipher = Cipher.getInstance(AES_ALGORITHM); GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, iv); cipher.init(Cipher.DECRYPT_MODE, key, spec); // 解密 return cipher.doFinal(actualCiphertext); } /** * 生成 AES 密钥 */ public static SecretKey generateAESKey() throws NoSuchAlgorithmException { KeyGenerator keyGen = KeyGenerator.getInstance("AES"); keyGen.init(AES_KEY_SIZE); return keyGen.generateKey(); } /** * 从密码派生密钥 (PBKDF2) */ public static SecretKey deriveKeyFromPassword(String password, byte[] salt) throws GeneralSecurityException { PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 100000, AES_KEY_SIZE); SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); byte[] keyBytes = skf.generateSecret(spec).getEncoded(); return new SecretKeySpec(keyBytes, "AES"); } } ``` --- ## 异常处理安全 ### 安全异常处理 > ▎ 异常信息不是调试信息,是安全边界。边界不守,信息就是泄露的。 **异常处理最佳实践**: ```java // 安全的异常处理 import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class SecureExceptionHandling { private static final Logger logger = LoggerFactory.getLogger(SecureExceptionHandling.class); /** * 安全的数据库操作异常处理 */ public User getUserById(int userId) { try { return userRepository.findById(userId); } catch (SQLException e) { // 记录详细日志 (内部) logger.error("Database error while fetching user {}", userId, e); // 返回通用错误消息 (外部) throw new BusinessException("获取用户信息失败", "ERROR_USER_FETCH"); } } /** * 安全的认证异常处理 */ public User authenticate(String username, String password) { try { User user = userRepository.findByUsername(username); if (user == null) { // 不泄露用户是否存在 logger.info("Login attempt for non-existent user: {}", username); throw new AuthenticationException("用户名或密码错误"); } if (!passwordEncoder.matches(password, user.getPasswordHash())) { // 不泄露是密码错误 logger.info("Login attempt with invalid password for user: {}", username); throw new AuthenticationException("用户名或密码错误"); } return user; } catch (AuthenticationException e) { // 重新抛出认证异常 throw e; } catch (Exception e) { // 记录意外错误 logger.error("Unexpected error during authentication", e); throw new BusinessException("认证服务暂时不可用", "ERROR_AUTH_SERVICE"); } } /** * 安全的文件操作异常处理 */ public byte[] readFile(String filename) { try { return SecureFileUtil.readFileSecurely(uploadDir, filename); } catch (SecurityException e) { // 安全异常 (路径遍历等) logger.warn("Security violation while accessing file: {}", filename, e); throw new SecurityException("文件访问被拒绝"); } catch (FileNotFoundException e) { // 不泄露文件是否存在 logger.info("File not found: {}", filename); throw new BusinessException("文件不存在", "ERROR_FILE_NOT_FOUND"); } catch (IOException e) { // 记录详细日志 logger.error("IO error while reading file: {}", filename, e); throw new BusinessException("文件读取失败", "ERROR_FILE_READ"); } } } // 全局异常处理器 @RestControllerAdvice public class GlobalExceptionHandler { private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); @ExceptionHandler(BusinessException.class) public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e) { ErrorResponse response = new ErrorResponse( e.getErrorCode(), e.getUserMessage(), LocalDateTime.now() ); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); } @ExceptionHandler(AuthenticationException.class) public ResponseEntity<ErrorResponse> handleAuthenticationException(AuthenticationException e) { ErrorResponse response = new ErrorResponse( "AUTH_ERROR", e.getMessage(), LocalDateTime.now() ); return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response); } @ExceptionHandler(Exception.class) public ResponseEntity<ErrorResponse> handleGenericException(Exception e) { // 记录详细堆栈 (内部) logger.error("Unexpected error", e); // 返回通用错误消息 (外部) ErrorResponse response = new ErrorResponse( "INTERNAL_ERROR", "服务器内部错误", LocalDateTime.now() ); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response); } } // 错误响应 DTO public class ErrorResponse { private String errorCode; private String message; private LocalDateTime timestamp; // 不包含:堆栈跟踪、内部路径、SQL 语句等敏感信息 // Constructor, Getters, Setters } ``` --- ## 安全代码审查 ### 审查清单 > ▎ 代码审查不是走过场,是最后防线。防线不守,漏洞就是上线的。 **Java 安全审查清单**: ```java /** * Java 安全代码审查清单 * * 输入验证: * □ 所有用户输入是否验证 * □ 是否使用白名单验证 * □ 长度是否限制 * □ 类型是否检查 * * 输出编码: * □ 所有输出是否编码 * □ 编码是否匹配上下文 * □ 是否使用安全库 (OWASP Encoder) * * SQL 安全: * □ 是否使用 PreparedStatement * □ 是否避免字符串拼接 SQL * □ 排序字段是否白名单验证 * □ IN 子句是否参数化 * * 文件安全: * □ 文件名是否清理 * □ 是否验证路径在基目录内 * □ 文件类型是否验证 (基于内容) * □ 上传文件大小是否限制 * * 认证会话: * □ 密码是否哈希存储 (bcrypt/argon2) * □ 会话 ID 是否轮换 * □ 会话是否超时 * □ Cookie 是否安全标志 * * 加密随机: * □ 是否使用 SecureRandom * □ 加密是否使用认证模式 (GCM) * □ 密钥是否安全存储 * □ IV 是否随机生成 * * 异常处理: * □ 是否不泄露敏感信息 * □ 是否记录详细日志 * □ 是否使用通用错误消息 * * 依赖安全: * □ 依赖是否最新 * □ 是否有已知漏洞 * □ 是否使用可信源 */ ``` --- 统计 **Sprint 交付 · 绩效评估** ``` ┌───────────────┬────────────────┬────────────────┐ │ 主动出击 │ ██████████ 5/5 │ [PUA 生效] 充足 │ ├───────────────┼────────────────┼────────────────┤ │ + 验证闭环 │ ██████████ 5/5 │ 案例完整 │ ├───────────────┼────────────────┼────────────────┤ │ 设计 代码质量 │ ██████████ 5/5 │ 生产就绪 │ └───────────────┴────────────────┴────────────────┘ 综合:4.5 ``` ▎ 这才配得上 P8。安全编码不是一次学习,是持续实践。实践不停,代码才安全。 --- ## 总结与思考 ### 核心要点回顾 > ▎ 复盘四步法:回顾目标、评估结果、分析原因、总结经验。别跳过——这是闭环。 **Java 安全框架**: ``` 1. 输入验证 - Bean Validation - 白名单验证 - 输入清理 2. 输出编码 - OWASP Encoder - 上下文感知 - 安全响应头 3. 数据访问 - PreparedStatement - 参数化查询 - Criteria API 4. 文件操作 - 路径验证 - 类型验证 - 大小限制 5. 加密随机 - SecureRandom - AES-GCM - 密钥管理 ``` **关键实践**: ``` 1. 使用安全库 - OWASP Encoder - BCrypt/Argon2 - JPA Criteria 2. 遵循规范 - CERT Java 规范 - OWASP 指南 - 厂商建议 3. 持续学习 - 关注漏洞 - 学习最佳实践 - 代码审查 ``` ### 实战建议 > ▎ 我给你指了路,走不走是你的事。机会给了,抓不抓得住看你。 **个人提升**: ``` 1. 学习安全知识 - OWASP Top 10 - Java 安全规范 - 漏洞案例分析 2. 实践安全编码 - 日常代码应用 - 使用安全库 - 代码审查 3. 工具使用 - SAST 工具 - 依赖扫描 - 安全测试 ``` **团队建设**: ``` 1. 建立规范 - 安全编码规范 - 代码审查清单 - 安全测试流程 2. 工具支持 - 静态分析 - 依赖扫描 - 自动化测试 3. 文化建设 - 安全培训 - 知识分享 - 正向激励 ``` --- ## 参考资料 ### 学习资源 ``` - OWASP Java Security Cheat Sheet https://cheatsheetseries.owasp.org/cheatsheets/Java_Security_Cheat_Sheet.html - CERT Oracle Secure Coding Standard for Java https://wiki.sei.cmu.edu/confluence/display/java - Oracle Secure Coding Guidelines https://www.oracle.com/java/technologies/javase/seccodeguide.html ``` ### 工具资源 ``` - SpotBugs (静态分析) https://spotbugs.github.io/ - OWASP Dependency-Check https://owasp.org/www-project-dependency-check/ - SonarQube https://www.sonarqube.org/ ``` ### 安全库 ``` - OWASP Encoder https://owasp.org/www-project-java-encoder/ - JBCrypt https://github.com/jeremyh/jBCrypt - Argon2 https://github.com/phxql/argon2-jvm ``` ### 书籍推荐 ``` - 《Secure Coding in Java》 - 《Java Security Handbook》 - 《Writing Secure Java Code》 ``` --- **标记 明日预告**:Day 146 - 安全编码实践 (Python) > ▎ Java 安全编码是基础,Python 安全编码是扩展——明天看 Python 安全编码实践。 > 本文内容仅供学习和研究使用,请勿用于非法目的。所有实验请在隔离环境中进行。 --- *本文是 365 天信息安全技术系列的第 145 篇,应用安全部分第 38 篇,精编版本* *应用安全核心系列继续!*
myh0st
2026年4月13日 23:18
分享文档
收藏文档
上一篇
下一篇
微信扫一扫
复制链接
手机扫一扫进行分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码