Java安全编码实践总结
字数 991 2025-08-15 21:31:19
Java安全编码实践指南
1. SQL注入防范
1.1 预编译技术
适用场景:大多数对数据库进行操作的场景
实现方式:
-
JDBC:
String sql = "SELECT * FROM users WHERE username = ?"; PreparedStatement stmt = connection.prepareStatement(sql); stmt.setString(1, username); -
Hibernate:
Query query = session.createQuery("FROM User WHERE username = :username"); query.setParameter("username", username); -
MyBatis:
<select id="getUser" resultType="User"> SELECT * FROM users WHERE username = #{username} </select>
1.2 无法使用预编译的场景
场景:涉及表名、字段名的动态查询(如ORDER BY、LIMIT、GROUP BY等)
解决方案:白名单验证
// 定义允许的排序字段白名单
private static final Set<String> ALLOWED_SORT_FIELDS = new HashSet<>(Arrays.asList("id", "name", "create_time"));
public List<User> getUsers(String sortField) {
if (!ALLOWED_SORT_FIELDS.contains(sortField)) {
throw new IllegalArgumentException("Invalid sort field");
}
String sql = "SELECT * FROM users ORDER BY " + sortField;
// 执行查询...
}
2. NoSQL注入防范
MongoDB注入防范
错误写法(拼接用户查询条件):
// 错误示例
BasicDBObject query = new BasicDBObject("$where", "this.username == '" + username + "'");
正确写法(参数绑定):
// 正确示例
BasicDBObject query = new BasicDBObject("username", username);
3. XSS防范
3.1 白名单校验
适用场景:纯数字、纯文本等简单输入
// 只允许字母数字
if (!username.matches("^[a-zA-Z0-9]+$")) {
throw new IllegalArgumentException("Invalid username");
}
3.2 ESAPI处理
适用场景:常规输入输出,如用户评论
import org.owasp.esapi.ESAPI;
// 过滤XSS
String safeInput = ESAPI.encoder().encodeForHTML(userInput);
4. XXE注入防范
4.1 禁用外部实体
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
xmlReader.setFeature("http://xml.org/sax/features/external-general-entities", false);
xmlReader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
xmlReader.parse(new InputSource(new StringReader(body)));
4.2 禁用DOCTYPE声明(更严格)
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
5. 文件上传漏洞防范
5.1 安全上传实践
// 限制文件类型
private static final Set<String> ALLOWED_TYPES = Set.of("image/jpeg", "image/png");
// 生成随机文件名
String fileName = UUID.randomUUID().toString() + "." + FilenameUtils.getExtension(originalFilename);
// 限制文件大小
@Bean
public MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
factory.setMaxFileSize("10MB");
factory.setMaxRequestSize("10MB");
return factory.createMultipartConfig();
}
6. 文件包含防范
6.1 安全实践
// 通过ID映射真实路径
public String getFile(String fileId) {
if (SecurityUtil.checkId(fileId) == null) {
return "资源文件不存在!";
}
return get_file(SecurityUtil.find_path(fileId));
}
7. CSRF防范
7.1 Spring Security配置
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
8. 越权防范
8.1 垂直越权
// 检查用户权限
public void sensitiveOperation(User user) {
if (!user.getRoles().contains("ADMIN")) {
throw new AccessDeniedException("权限不足");
}
// 执行操作...
}
8.2 水平越权
// 检查数据所有权
public User getUserDetails(Long userId, Long currentUserId) {
User user = userRepository.findById(userId);
if (!user.getId().equals(currentUserId)) {
throw new AccessDeniedException("无权访问此数据");
}
return user;
}
9. URL重定向与SSRF防范
9.1 URL重定向安全
// 白名单验证
private static final Set<String> ALLOWED_DOMAINS = Set.of("example.com", "trusted.org");
public String redirect(String url) {
try {
URI uri = new URI(url);
if (!ALLOWED_DOMAINS.contains(uri.getHost())) {
return "warning"; // 给用户风险提示
}
return "redirect:" + url;
} catch (URISyntaxException e) {
return "error";
}
}
9.2 SSRF防范
// 限制协议和域名
private static final Set<String> ALLOWED_PROTOCOLS = Set.of("http", "https");
private static final Set<String> ALLOWED_HOSTS = Set.of("api.example.com");
public void makeRequest(String url) {
try {
URI uri = new URI(url);
if (!ALLOWED_PROTOCOLS.contains(uri.getScheme()) ||
!ALLOWED_HOSTS.contains(uri.getHost())) {
throw new IllegalArgumentException("非法请求");
}
// 执行请求...
} catch (URISyntaxException e) {
throw new IllegalArgumentException("非法URL");
}
}
10. 拒绝服务防范
10.1 正则表达式DoS防范
// 设置超时
Pattern pattern = Pattern.compile("(a+)+$");
Matcher matcher = pattern.matcher(input);
long startTime = System.currentTimeMillis();
while (matcher.find()) {
if (System.currentTimeMillis() - startTime > 2000) {
throw new RuntimeException("匹配超时");
}
// 处理匹配结果...
}
11. 加密安全
11.1 安全加密模式
错误写法(使用ECB模式):
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
正确写法(使用CBC模式):
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
12. 随机数安全
12.1 安全随机数
错误写法(使用伪随机):
Random random = new Random(seed); // 不安全
正确写法(使用SecureRandom):
SecureRandom secureRandom = new SecureRandom();
byte[] bytes = new byte[16];
secureRandom.nextBytes(bytes);
13. 条件竞争防范
13.1 线程同步
private final Object lock = new Object();
public void checkIn(User user) {
synchronized (lock) {
if (user.hasCheckedInToday()) {
throw new IllegalStateException("今日已签到");
}
user.addPoints(1);
user.setLastCheckIn(LocalDate.now());
}
}
14. 日志伪造防范
14.1 安全日志记录
public void logUserAction(String username) {
// 过滤换行和特殊字符
Pattern p = Pattern.compile("\\s*|\t|\r|\n");
Matcher m = p.matcher(username);
String safeUsername = m.replaceAll("");
logger.info("User action by: " + safeUsername);
}
15. 动态代码执行安全
15.1 命令执行安全
错误写法:
Runtime.getRuntime().exec(userInput);
正确写法:
// 1. 输入净化
String sanitized = userInput.replaceAll("[^a-zA-Z0-9]", "");
// 2. 命令映射
if (!"allowedCommand".equals(sanitized)) {
throw new IllegalArgumentException("非法命令");
}
// 3. 使用系统API替代
String userName = System.getProperty("user.name");
16. 反序列化安全
16.1 安全反序列化
错误写法:
ObjectInputStream in = new ObjectInputStream(inputStream);
Object obj = in.readObject();
正确写法(使用SerialKiller):
try {
ObjectInputStream in = new SerialKiller(inputStream, "serialkiller.conf");
Object obj = in.readObject();
} catch (InvalidClassException e) {
logger.warn("尝试反序列化非法类");
}
17. 表达式注入防范
17.1 SpEL表达式安全
错误写法:
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
parser.parseExpression("Name = " + userInput).getValue(context, object);
正确写法:
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
parser.parseExpression("Name = " + userInput).getValue(context, object);
18. Spring Boot安全配置
18.1 关键安全配置
# 禁用shutdown端点
endpoints.shutdown.enabled=false
# 禁用开发工具
spring.devtools.restart.enabled=false
# 禁用管理功能
spring.application.admin.enabled=false
# 启用HTML转义
spring.mvc.contentnegotiation.favor-path-extension=false
# Actuator安全配置
management.security.enabled=true
management.endpoints.web.exposure.include=health,info
management.endpoints.jmx.exposure.exclude=*
总结
Java安全编码需要从多个层面进行防护:
- 输入验证:始终验证用户输入,使用白名单优于黑名单
- 安全配置:框架和服务器应进行安全配置
- 最小权限:遵循最小权限原则
- 安全API:优先使用安全API而非危险API
- 防御性编程:考虑边界条件和异常情况
- 持续更新:保持依赖库和框架的最新版本
通过实施这些安全编码实践,可以显著降低Java应用程序的安全风险。