news 2026/6/10 16:37:06

从开发视角看安全:我的Spring Boot项目是如何一步步堵上SQL注入、XSS、越权这些坑的

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从开发视角看安全:我的Spring Boot项目是如何一步步堵上SQL注入、XSS、越权这些坑的

从开发视角看安全:我的Spring Boot项目是如何一步步堵上SQL注入、XSS、越权这些坑的

在开发一个用户管理系统的过程中,安全问题往往不是一开始就考虑周全的。作为后端开发者,我们更关注功能的快速实现和性能优化,直到某次安全扫描报告亮起红灯,或是线上出现安全事件,才会真正重视起来。本文将分享我在开发Spring Boot项目时,如何从零开始逐步完善安全防护,解决SQL注入、XSS、越权等常见漏洞的真实经历。

1. SQL注入:从拼接SQL到预编译的转变

最初实现用户查询功能时,为了快速上线,我直接使用了字符串拼接的方式构建SQL语句:

// 危险示例:字符串拼接SQL @GetMapping("/users") public List<User> getUsers(@RequestParam String name) { String sql = "SELECT * FROM users WHERE name = '" + name + "'"; return jdbcTemplate.query(sql, new UserRowMapper()); }

直到安全团队用简单的测试用例name=admin' OR '1'='1就获取了全部用户数据,我才意识到问题的严重性。修复方案很简单但极其有效:

// 安全方案:使用预编译语句 @GetMapping("/users") public List<User> getUsers(@RequestParam String name) { String sql = "SELECT * FROM users WHERE name = ?"; return jdbcTemplate.query(sql, new Object[]{name}, new UserRowMapper()); }

在MyBatis中,同样需要注意${}#{}的区别:

<!-- 危险用法 --> <select id="findByName" parameterType="String" resultType="User"> SELECT * FROM users WHERE name = '${name}' </select> <!-- 安全用法 --> <select id="findByName" parameterType="String" resultType="User"> SELECT * FROM users WHERE name = #{name} </select>

关键防御措施

  • 始终使用预编译语句(PreparedStatement)
  • MyBatis中优先使用#{}语法
  • 对必须使用动态表名/列名的场景,严格白名单校验
  • 最小化数据库账号权限

2. XSS防护:从基础转义到内容安全策略

用户评论功能上线后不久,运营同事报告有用户昵称显示异常。检查发现有人提交了这样的内容:

<script>alert('XSS攻击')</script>

第一层防御是在服务端对输出进行HTML转义:

// Spring Boot默认的Thymeleaf模板已自动转义 // 手动转义示例: import org.springframework.web.util.HtmlUtils; public String safeOutput(String input) { return HtmlUtils.htmlEscape(input); }

但转义并不能解决所有场景。比如用户需要在评论中使用富文本时,我们采用了更精细的防护:

  1. 引入JSoup库进行HTML过滤
// 只允许安全的HTML标签和属性 String safeHtml = Jsoup.clean(unsafeHtml, Whitelist.basicWithImages() .addAttributes("a", "href", "title") .addProtocols("a", "href", "http", "https"));
  1. 设置HTTP安全头增强防护
// 在Spring Security配置中添加 http.headers() .xssProtection() .contentSecurityPolicy("default-src 'self'; script-src 'self' 'unsafe-inline'");

多维度防护方案

  • 响应头设置:X-XSS-Protection, Content-Security-Policy
  • 前端框架如React/Vue的自动转义
  • 富文本场景使用白名单过滤
  • Cookie设置HttpOnly属性

3. 越权漏洞:从简单判断到系统化权限控制

最初的用户信息接口只验证了用户是否登录,没有校验操作的资源是否属于当前用户:

// 危险示例:未校验用户权限 @GetMapping("/users/{userId}") public User getUser(@PathVariable Long userId) { return userRepository.findById(userId).orElse(null); }

这导致了水平越权漏洞——任何登录用户只需修改userId参数就能查看他人信息。修复方案:

// 基础权限校验 @GetMapping("/users/{userId}") public User getUser(@PathVariable Long userId, @AuthenticationPrincipal User currentUser) { if (!currentUser.getId().equals(userId)) { throw new AccessDeniedException("无权访问该用户信息"); } return userRepository.findById(userId).orElse(null); }

随着业务复杂化,我们引入了Spring Security的权限系统:

// 基于注解的细粒度控制 @PreAuthorize("hasRole('ADMIN') or #userId == principal.id") @GetMapping("/users/{userId}") public User getUser(@PathVariable Long userId) { // ... } // 方法级权限控制 @PreAuthorize("@permissionService.canAccessUser(principal, #userId)") @GetMapping("/users/{userId}/details") public UserDetail getUserDetails(@PathVariable Long userId) { // ... }

权限控制最佳实践

  • 遵循最小权限原则
  • 服务端校验永远比前端校验可靠
  • 对敏感操作记录详细日志
  • 定期审计权限配置

4. CSRF防护:从手动Token到框架集成

在发现通过伪造请求可以执行用户非预期的操作后,我们首先手动实现了CSRF Token:

<!-- 前端表单中添加Token --> <form action="/transfer" method="post"> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> <!-- 其他表单字段 --> </form>
// 服务端校验 @PostMapping("/transfer") public void transferMoney(@Valid TransferRequest request, @RequestParam("_csrf") String csrfToken) { // 验证CSRF Token if (!csrfToken.equals(session.getAttribute("CSRF_TOKEN"))) { throw new SecurityException("Invalid CSRF token"); } // 处理业务逻辑 }

后来发现Spring Security已经提供了完善的CSRF防护,只需简单配置:

@Override protected void configure(HttpSecurity http) throws Exception { http .csrf() .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) // 其他配置... }

CSRF防护要点

  • 重要操作使用POST/PUT/DELETE方法
  • 同源策略检查
  • 敏感操作二次认证
  • 避免GET请求修改状态

5. 文件上传安全:从简单后缀检查到全流程防护

用户头像上传功能最初只检查了文件后缀:

// 不安全的检查方式 String ext = FilenameUtils.getExtension(filename); if (!Arrays.asList("jpg", "png").contains(ext)) { throw new IllegalArgumentException("不支持的文件类型"); }

这种防护很容易绕过,我们升级为更全面的检查:

  1. 文件内容类型验证
// 使用Tika检测真实文件类型 InputStream is = file.getInputStream(); ContentType contentType = new Tika().detect(is); if (!"image/jpeg".equals(contentType.toString())) { throw new IllegalArgumentException("非图片文件"); }
  1. 文件存储安全处理
// 生成随机文件名并限制访问权限 String safeFilename = UUID.randomUUID() + ".jpg"; Path dest = Paths.get("/var/upload", safeFilename); Files.copy(file.getInputStream(), dest, StandardCopyOption.REPLACE_EXISTING); // 设置文件权限 Files.setPosixFilePermissions(dest, PosixFilePermissions.fromString("rw-r-----"));
  1. 服务端图片二次处理
// 使用Thumbnailator重写图片文件 Thumbnails.of(inputStream) .size(200, 200) .outputFormat("jpg") .toOutputStream(outputStream);

文件上传安全措施

  • 文件类型白名单校验
  • 文件内容真实检测
  • 随机化存储文件名
  • 限制文件访问权限
  • 图片文件重压缩处理
  • 独立文件服务器部署

6. 日志与监控:安全问题的最后防线

即使做了各种防护,仍然需要完善的日志系统作为最后保障。我们在关键位置添加了安全日志:

@Aspect @Component public class SecurityLogAspect { @AfterReturning( pointcut = "execution(* com.example..*(..)) && @annotation(securityLog)", returning = "result") public void logAfter(JoinPoint joinPoint, SecurityLog securityLog, Object result) { String method = joinPoint.getSignature().toShortString(); String params = Arrays.toString(joinPoint.getArgs()); // 记录到专门的审计日志 auditLogger.info("操作[{}], 参数[{}], 结果[{}]", method, params, result); } @AfterThrowing( pointcut = "execution(* com.example..*(..))", throwing = "ex") public void logException(JoinPoint joinPoint, Throwable ex) { if (ex instanceof AccessDeniedException) { securityAlert.warn("权限拒绝访问: {}", joinPoint.getSignature(), ex.getMessage()); } } }

同时配置了ELK日志系统,设置关键安全事件的告警规则:

# 示例告警规则 alert: name: "多次登录失败" condition: > count by "source_ip" ( status == "FAILURE" and event == "LOGIN_ATTEMPT" ) > 5 action: > notify_security_team(source_ip)

安全是一个持续的过程,需要开发者在每个功能迭代中都保持警惕。从我的经验来看,最有效的安全策略不是复杂的防护体系,而是开发团队对安全问题的持续关注和及时响应。

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

别再乱装IDEA了!Mac/Win双系统保姆级配置指南(附2023.1版避坑清单)

跨平台开发者必备&#xff1a;IntelliJ IDEA 2023终极配置避坑手册 刚接触IntelliJ IDEA的开发者常会遇到这样的困境&#xff1a;明明按照教程一步步操作&#xff0c;却在环境配置环节卡壳数小时。更令人头疼的是&#xff0c;不同操作系统间的配置差异往往被大多数教程忽略&…

作者头像 李华
网站建设 2026/6/10 16:32:54

MC1323x无线MCU深度解析:从引脚功能到射频电路设计的实战指南

1. 项目概述与核心价值在物联网设备开发中&#xff0c;选型一颗合适的无线微控制器&#xff08;MCU&#xff09;往往是项目成败的第一步。面对市面上琳琅满目的芯片&#xff0c;数据手册里密密麻麻的引脚定义和电气参数表格&#xff0c;常常让工程师感到无从下手。今天&#xf…

作者头像 李华
网站建设 2026/6/10 16:29:26

DiDAE框架:深度学习模型反事实生成与虚假相关性消除

1. 项目概述&#xff1a;DiDAE框架的核心价值 在深度学习模型的训练过程中&#xff0c;一个长期存在的挑战是模型容易学习到数据中的虚假相关性&#xff08;Spurious Correlations&#xff09;。这种现象被称为"Clever Hans"策略——就像20世纪初那匹会做算术的马一样…

作者头像 李华