Spring框架默认提供的安全机制并不足以抵御现代Web应用的常见攻击向量。很多开发者误以为启用了Spring Security、配置了HTTPS、加了一层过滤器就万事大吉,但实际生产环境中,大量漏洞恰恰出现在配置遗漏、默认行为未覆盖、以及自定义过滤器链顺序错误这些细节上。针对Spring应用的攻击面主要集中在会话管理、CSRF、CORS、请求参数绑定、错误信息泄露和依赖版本这几个方向,下面直接拆解具体配置和防护方法。
会话固定攻击与Cookie安全属性Spring Security在用户登录成功后默认不会更换会话ID,这为会话固定攻击留下了空间。攻击者可以先访问应用拿到一个合法的JSESSIONID,再诱导受害者用这个会话ID完成登录,之后攻击者就能复用该会话。修复方式是在登录成功处理逻辑中强制迁移会话,并同步更新认证信息。
http.sessionManagement()
.sessionFixation().migrateSession()
.sessionAuthenticationStrategy(changeSessionIdAuthenticationStrategy());
同时必须设置Cookie的HttpOnly和Secure属性。HttpOnly防止JavaScript读取会话Cookie,降低XSS窃取会话的风险;Secure确保Cookie只在HTTPS连接中传输。Spring Boot中通过配置文件即可完成:
server.servlet.session.cookie.http-only=true server.servlet.session.cookie.secure=true server.servlet.session.cookie.same-site=lax
SameSite属性同样关键,设置为Lax或Strict可以显著降低CSRF攻击的可行性。但要注意,如果你的应用依赖跨站POST请求(例如第三方支付回调直接POST到你的接口),Strict会导致Cookie不被发送,此时需要结合CSRF Token方案做取舍。
CSRF防护的配置盲区Spring Security从4.x版本开始默认开启CSRF防护,但很多团队在前后端分离架构下直接关闭了它,理由是“前后端分离不需要CSRF”。这个判断只对了一半。如果你的后端接口依赖Cookie或Session进行身份认证,那么即使前端是独立的单页应用,CSRF风险依然存在。只有当你完全使用Authorization头携带JWT或API Key、且不依赖浏览器自动发送的凭证时,关闭CSRF才是相对安全的。
对于需要保留Cookie认证的场景,正确做法是让前端在每次状态变更请求中携带CSRF Token。Spring Security提供了CookieCsrfTokenRepository,可以将Token写入Cookie供前端读取,但读取后需要前端手动放入请求头X-XSRF-TOKEN中。配置如下:
http.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.ignoringAntMatchers("/api/webhook/");
注意withHttpOnlyFalse()是必要的,否则前端JavaScript无法读取Cookie中的Token。忽略路径要严格控制,只放行那些确实由第三方服务器直接回调、无法携带自定义请求头的接口,并且这些接口最好额外增加签名校验。
CORS配置不能图省事跨域资源共享配置中最常见的错误是使用allowedOrigins("*")同时允许携带凭证。这种配置在Spring中会直接抛出异常,因为规范禁止通配符来源与凭证同时使用。但更隐蔽的问题是,很多开发者改用allowedOriginPatterns("*")后,误以为安全了,实际上这等于允许任意来源携带Cookie发起跨域请求。
正确的做法是明确列出允许的前端域名,并且根据接口的敏感程度区分是否允许携带凭证。对于公开的只读接口,可以允许较宽松的来源;对于涉及用户数据或状态变更的接口,必须限制来源且要求凭证。
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(Arrays.asList("https://app.example.com", "https://admin.example.com"));
config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
config.setAllowedHeaders(Arrays.asList("Content-Type", "X-Requested-With", "X-XSRF-TOKEN"));
config.setAllowCredentials(true);
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/", config);
return source;
}
另外,CORS配置要放在Spring Security过滤器链之前生效,否则预检请求OPTIONS可能被安全过滤器拦截,导致跨域请求失败。可以通过http.cors()启用Spring Security对CORS配置的集成,而不是仅仅在MVC层配置。
请求参数绑定与Mass Assignment防护Spring MVC的数据绑定机制非常方便,但也带来了Mass Assignment风险。当Controller直接使用实体类接收请求参数时,攻击者可以通过猜测字段名来修改本不应由客户端控制的属性。例如User实体中包含role或enabled字段,如果登录接口直接绑定User对象,攻击者提交role=admin就可能提权。
解决这个问题的最佳实践是引入DTO层,只暴露允许客户端提交的字段。如果项目规模较大,可以使用MapStruct或ModelMapper自动完成实体与DTO之间的转换。对于必须直接绑定实体的场景,使用@InitBinder配置允许的字段白名单:
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.setAllowedFields("username", "password", "email");
}
同时,对敏感字段在实体类上使用@JsonProperty(access = Access.READ_ONLY)或@JsonIgnore注解,防止反序列化时被赋值。但要注意,注解方案只对Jackson反序列化生效,对于表单提交或查询参数绑定仍需依赖DTO或字段白名单。
错误信息泄露与异常处理Spring Boot默认的/error端点会返回详细的错误信息,包括异常类名、堆栈片段和请求路径。在生产环境中,这些信息会帮助攻击者识别框架版本、内部包结构甚至SQL语句片段。必须关闭详细错误信息:
server.error.include-message=never server.error.include-stacktrace=never server.error.include-exception=false
同时自定义全局异常处理器,统一返回格式化的错误响应,避免框架默认行为。对于API接口,返回JSON格式的错误码和通用提示信息;对于页面请求,返回友好的错误页面。不要在响应中暴露任何内部异常类名或堆栈信息。
安全响应头配置Spring Security提供了默认的安全响应头,但部分头需要根据业务场景调整。X-Frame-Options默认是DENY,如果你的应用需要被特定域名的iframe嵌入(例如后台管理嵌入在运营平台中),需要改为SAMEORIGIN或通过Content-Security-Policy的frame-ancestors指令精确控制。
http.headers()
.frameOptions().sameOrigin()
.contentSecurityPolicy("default-src 'self'; frame-ancestors 'self' https://ops.example.com");
Content-Security-Policy是最有效的XSS缓解措施之一,但配置过于严格会导致前端资源加载失败。建议从较宽松的策略开始,逐步收紧。至少应包含default-src 'self',并明确script-src和style-src的来源,避免使用unsafe-inline和unsafe-eval。
HSTS头用于强制浏览器使用HTTPS访问,Spring Security中通过http.headers().httpStrictTransportSecurity()配置。注意在启用HSTS之前,确保全站HTTPS已经稳定运行,否则用户可能无法访问HTTP回退页面。includeSubDomains参数要谨慎使用,如果子域名尚未全部支持HTTPS,会导致子域名访问失败。
依赖版本与已知漏洞管理Spring框架及其生态组件频繁发布安全更新,很多生产事故源于使用了存在已知漏洞的旧版本。Spring Security 5.6之前的版本存在CVE-2023-20860等安全漏洞,Spring Framework 5.3.x系列也有多个高危CVE。仅靠人工关注安全公告不够,必须在构建流程中引入依赖漏洞扫描。
对于Maven项目,可以集成OWASP Dependency-Check插件,在每次构建时检查依赖库的已知漏洞。对于Gradle项目,使用dependency-check-gradle插件。更进一步的方案是在CI流水线中加入安全门禁,发现高危漏洞时阻断发布。
同时要关注Spring Boot的版本升级策略。Spring Boot的每个版本都有对应的支持周期,超过支持期的版本不再获得安全补丁。生产环境应保持在受支持的版本范围内,并制定定期升级计划。升级时重点关注Spring Security、Spring Web、Spring Data等核心模块的变更说明,因为安全相关的默认行为可能在新版本中发生变化。
自定义过滤器的顺序陷阱很多团队在Spring Security过滤器链中插入自定义过滤器来实现特殊逻辑,例如从自定义请求头提取用户身份、记录访问日志、或者对特定路径做额外校验。如果过滤器顺序配置不当,可能导致安全校验被绕过。
自定义过滤器应该添加在SecurityContextPersistenceFilter之后、UsernamePasswordAuthenticationFilter之前,确保安全上下文已经初始化但认证尚未执行。如果自定义过滤器负责从请求头解析Token并设置认证信息,必须放在认证过滤器之前,否则认证过滤器会先执行并判定请求未认证。
http.addFilterBefore(customTokenFilter, UsernamePasswordAuthenticationFilter.class);
如果自定义过滤器用于审计日志,应该放在安全上下文建立之后,以便记录用户身份信息。如果放在最前面,此时安全上下文中还没有用户信息,日志中无法关联到具体用户。过滤器的顺序直接影响安全逻辑的执行时机,任何调整都需要经过完整的渗透测试验证。
Spring框架的安全配置是一个系统工程,单一配置项的正确并不能保证整体安全。每个配置项之间相互关联,例如CSRF防护依赖Cookie的SameSite属性,CORS配置影响凭证的跨域发送,错误处理影响信息泄露面。安全配置需要结合业务场景持续调整,并在每次框架升级后重新审视默认行为的变化。最有效的验证方式是定期执行自动化安全扫描和人工渗透测试,将发现的问题反馈到配置基线中,形成闭环。
