Spring Boot 的自动配置机制极大地简化了开发,但这也是安全问题的重灾区。很多团队只关注业务代码的安全,却忽略了框架本身默认开启的组件、端点和管理接口,这些地方一旦暴露,攻击者甚至不需要SQL注入就能直接拿下服务器内存数据或执行远程命令。安全自动配置加固,不是去写一堆自定义过滤器,而是从Spring Boot的Auto Configuration机制入手,堵住那些“默认开启但你根本不知道”的缺口。

关闭或限制Actuator端点暴露

Spring Boot Actuator是最大的信息泄露源。默认情况下,/actuator/health和/actuator/info可能被暴露,但如果引入了完整的Actuator Starter,很多敏感端点会被自动注册。加固的第一步是显式配置哪些端点可以对外,哪些只能内部访问。在application.yml中,不要只配置include,要同时配置exclude,并强制要求所有端点默认禁用,按需开启。

management:
  endpoints:
    web:
      exposure:
        include: "health,info"
        exclude: "env,beans,threaddump,heapdump,mappings"
  endpoint:
    health:
      show-details: never
    env:
      enabled: false
    beans:
      enabled: false
    heapdump:
      enabled: false

这里的关键点是heapdump端点。它默认可能被自动配置加载,攻击者通过下载堆转储文件可以分析出内存中的密码、Token、密钥等敏感数据。即使你配置了exclude,也要在endpoint级别显式禁用,因为某些版本中exclude只影响发现页面,端点本身仍可访问。另外,/actuator路径本身应该通过security配置进行访问控制,不要仅依赖Web容器层面的IP白名单,因为代理和CDN可能导致IP伪造绕过。

禁用自动配置中不必要的Web安全特性

Spring Security的自动配置会生成默认的用户名和随机密码打印在日志中,这在生产环境是灾难性的。很多开发者以为改了默认密码就安全了,但实际上控制台日志可能被集中采集到ELK或Splunk,任何有日志查看权限的人都能拿到初始凭证。必须显式禁用Security的自动UserDetailsService配置,并提供自定义的认证机制。

spring:
  security:
    user:
      name: ""
      password: ""

仅仅置空还不够,因为某些条件装配仍然会触发默认的InMemoryUserDetailsManager。更彻底的做法是在配置类中排除UserDetailsServiceAutoConfiguration:

@SpringBootApplication(exclude = {
    org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration.class
})

同时要检查是否自动引入了Spring Security的CSRF保护。对于纯API服务,CSRF是多余的,但Spring Boot自动配置默认开启。如果不显式关闭,所有POST/PUT/DELETE请求都需要携带CSRF Token,这会导致移动端或第三方调用失败,而开发者往往为了快速解决直接禁用整个Security配置,反而造成更大的安全缺口。正确做法是保留Security框架,仅针对特定路径关闭CSRF。

Session创建策略的强制收敛

Spring Boot自动配置的Session策略是IF_REQUIRED,这意味着只要某个请求触发了HttpSession的创建,容器就会生成JSESSIONID。在RESTful API中,这会导致无状态服务意外地变成有状态,还可能引发Session固定攻击。更危险的是,Spring Session自动配置如果检测到Redis等中间件,会自动将Session持久化到共享存储,一旦攻击者通过其他漏洞注入恶意Session属性,整个集群都会受影响。

server:
  servlet:
    session:
      tracking-modes: cookie
      cookie:
        http-only: true
        secure: true
        same-site: strict
spring:
  session:
    store-type: none

对于纯无状态API,直接设置store-type为none,彻底禁用Spring Session的自动配置。如果业务确实需要Session,必须将cookie的SameSite设置为Strict,并开启HttpOnly和Secure标志。同时检查是否有自动配置的SessionRegistry,它会将所有登录用户的Session缓存在内存中,大量并发登录时可能导致内存溢出。

数据源和JPA的自动配置风险

Spring Boot的DataSource自动配置会根据classpath中的驱动自动创建数据源,但连接池的默认配置往往过于宽松。HikariCP虽然性能优秀,但默认的connection-timeout、idle-timeout和max-lifetime可能不适合生产环境,更重要的是,自动配置不会强制开启连接验证,导致从连接池中取出的连接可能已经被数据库端关闭,引发异常后重试机制可能暴露错误信息。

spring:
  datasource:
    hikari:
      connection-test-query: "SELECT 1"
      validation-timeout: 3000
      max-lifetime: 1800000
      leak-detection-threshold: 60000
  jpa:
    show-sql: false
    open-in-view: false
    properties:
      hibernate:
        hbm2ddl:
          auto: validate

open-in-view是Spring Boot JPA自动配置中最常被忽视的安全和性能陷阱。它默认开启,意味着视图渲染完成前数据库连接一直保持,这不仅导致连接池耗尽,还会让事务边界模糊,攻击者可以通过构造慢请求长时间占用数据库连接。show-sql在生产环境必须关闭,否则SQL语句会输出到日志,包含敏感查询条件。hbm2ddl.auto绝对不能设置为update或create-drop,只能使用validate或none,防止自动修改数据库结构。

Jackson和消息转换器的反序列化安全

Spring Boot自动配置了Jackson作为JSON序列化工具,但默认的ObjectMapper配置启用了Default Typing相关的多态类型处理,这是反序列化漏洞的根源。攻击者可以通过构造特殊的JSON payload,利用多态类型解析触发任意类加载和代码执行。必须显式禁用默认类型处理,并限制可反序列化的类白名单。

@Configuration
public class JacksonConfig {
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() {
        return builder -> {
            builder.featuresToDisable(
                com.fasterxml.jackson.databind.SerializationFeature.FAIL_ON_EMPTY_BEANS,
                com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
            );
            builder.featuresToEnable(
                com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_INVALID_SUBTYPE
            );
        };
    }
}

更重要的是,检查项目中是否自动引入了Jackson的JAXB注解支持或Scala模块,这些模块扩展了多态类型处理的范围,增加了攻击面。如果不需要XML格式的请求,应该在WebMvcConfigurer中限制消息转换器只保留JSON,移除XML和protobuf等自动配置的转换器。

Thymeleaf和模板引擎的自动配置加固

Spring Boot对Thymeleaf的自动配置默认开启了模板缓存和SpringEL表达式,但同时也默认允许在模板中执行OGNL或SpEL表达式。如果业务中有用户输入被直接拼接到模板中(即使不是模板文件,而是通过Thymeleaf处理邮件模板等场景),就可能触发表达式注入。必须显式限制模板引擎的表达式执行能力。

spring:
  thymeleaf:
    cache: true
    mode: HTML
    encoding: UTF-8
    servlet:
      content-type: text/html
    enabled: true
    check-template-location: true
    reactive:
      max-chunk-size: 8192

更彻底的做法是使用Thymeleaf的SpringSecurityDialect时,确保不将原始用户输入作为模板变量传递。如果使用了其他模板引擎如Freemarker,同样需要检查其自动配置是否允许执行任意Java方法,Freemarker的ObjectWrapper默认配置可能允许模板中调用Runtime.exec(),必须设置为BeansWrapper的简单模式。

DevTools和热重载的自动配置在生产环境的残留

Spring Boot DevTools的自动配置包含远程调试和远程更新功能,如果生产环境的依赖中意外包含了DevTools(常见于构建工具配置错误),应用启动时会自动开启远程调试端口和HTTP远程重启端点。这相当于给攻击者留了一个后门。必须在生产构建中排除DevTools依赖,并在配置中显式禁用它。

spring:
  devtools:
    restart:
      enabled: false
    livereload:
      enabled: false
    remote:
      secret: ""

检查项目的Maven或Gradle配置,确保DevTools的scope为runtime且optional为true,或者在生产profile中排除该依赖。同时要扫描classpath中是否存在spring-boot-devtools的jar包,因为某些传递依赖可能意外引入。

内嵌Tomcat/Undertow的容器级安全配置

Spring Boot自动配置的内嵌容器默认会暴露Server头信息,显示具体的容器类型和版本号,这为攻击者提供了精准打击的依据。同时,默认的HTTP连接器配置允许TRACE和OPTIONS等HTTP方法,这些方法可能被利用进行跨站追踪攻击或信息探测。

server:
  error:
    include-stacktrace: never
    include-message: never
    include-binding-errors: never
  tomcat:
    accesslog:
      enabled: true
      pattern: "%h %l %u %t \"%r\" %s %b %D"
      max-days: 30
    max-http-form-post-size: 2MB
    connection-timeout: 5000

对于错误处理,Spring Boot的自动配置会返回详细的异常堆栈信息,这在开发环境有用,但生产环境必须全部关闭。include-message设为never后,客户端不会看到任何异常消息,防止信息泄露。同时要限制POST表单的大小,防止拒绝服务攻击。内嵌容器的最大线程数和连接数也需要根据实际负载调整,默认的200线程在攻击场景下可能很快耗尽。

自动配置的CORS策略收紧

Spring Boot如果检测到Spring Web依赖,会自动配置CORS,但默认的CorsFilter是宽松的,允许所有来源、所有方法和所有头部。很多开发者为了前后端分离开发方便,直接使用@CrossOrigin("*")注解,这个配置会被自动扫描并生效。生产环境必须覆盖这个自动配置,明确指定允许的源和方法。

@Configuration
public class CorsConfig {
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/api/")
                    .allowedOrigins("https://your-domain.com")
                    .allowedMethods("GET", "POST", "PUT", "DELETE")
                    .allowedHeaders("Authorization", "Content-Type")
                    .allowCredentials(true)
                    .maxAge(3600);
            }
        };
    }
}

特别注意allowCredentials(true)不能与allowedOrigins("*")同时使用,这是浏览器安全策略明确禁止的,但Spring Boot自动配置不会检查这个冲突,导致配置无效时开发者往往不知情。另外,如果使用了Spring Security,CORS配置必须在Security过滤链之前生效,否则OPTIONS预检请求会被Security拦截返回401。

日志框架的自动配置安全

Spring Boot默认使用Logback,自动配置会加载classpath下的logback-spring.xml或logback.xml。如果配置不当,日志中可能记录请求参数、响应体、认证头等敏感信息。更严重的是,Logback的自动重载配置功能默认开启,攻击者如果能通过其他漏洞修改日志配置文件(如路径穿越),就可以注入恶意的日志Appender,实现远程代码执行。必须关闭自动扫描重载功能。

logging:
  level:
    root: WARN
    org.springframework: WARN
    org.hibernate: ERROR
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
  file:
    name: /var/log/app.log
    max-size: 100MB
    max-history: 7

在logback-spring.xml中,显式设置scan="false",禁止运行时扫描配置文件变化。同时,日志输出格式中不要包含%mdc或%X,因为MDC中可能存储了用户ID、Session ID等敏感上下文信息。如果必须使用MDC进行链路追踪,要在日志输出前对敏感字段做脱敏处理。

自动配置的缓存和调度任务安全

Spring Boot对@EnableCaching和@EnableScheduling的自动配置会创建线程池和缓存管理器。默认的线程池大小是1,但在高并发场景下开发者往往会调大,却没有配置拒绝策略和队列容量,导致任务堆积内存溢出。缓存方面,自动配置的ConcurrentMapCacheManager是无界的,攻击者可以通过构造大量不同的缓存键填满JVM堆内存。

spring:
  task:
    scheduling:
      pool:
        size: 4
      thread-name-prefix: scheduled-
    execution:
      pool:
        core-size: 8
        max-size: 16
        queue-capacity: 100
        keep-alive: 60s
      thread-name-prefix: async-
  cache:
    type: caffeine
    caffeine:
      spec: maximumSize=500,expireAfterWrite=600s

将缓存类型从默认的Simple改为Caffeine,并设置最大容量和过期时间。对于异步任务执行器,必须限制队列容量,防止任务无限排队。同时,调度任务的执行时间如果超过调度间隔,默认的调度器会启动新线程执行,可能导致任务并发执行引发数据竞争,需要配置spring.task.scheduling.pool.size并配合@Async注解使用独立的线程池。

Spring Boot的安全自动配置加固不是一次性工作,而是需要在每次版本升级时重新审视。新版本可能会引入新的自动配置类,或者修改现有配置的默认值。建议在CI/CD流水线中加入Spring Boot配置审计步骤,使用spring-boot-actuator的configprops端点(仅内部开放)定期导出所有自动配置项,与安全基线进行比对。真正的安全不在于写了多少自定义代码,而在于你关闭了多少不需要的默认功能。