Java微服务架构的安全问题本质上不是单一漏洞的修补,而是要把安全能力嵌入每一次服务调用、每一条配置下发和每一份日志记录中。很多团队在微服务化之后,最先暴露的往往不是业务bug,而是内部服务之间“裸奔”的HTTP调用、共享的数据库账号、以及一个网关被打穿后所有下游服务全部沦陷。要搭建真正可用的安全防护体系,需要从边界控制、身份与权限、通信加密、配置与依赖、运行时防护、审计与应急六个层面同时下手,而不是只装一个Spring Security就完事。
先解决边界问题:网关不是反向代理,而是安全闸口
微服务架构里,API网关是所有外部流量的唯一入口,它承担的不只是路由转发。网关层必须做四件事:全量HTTPS终结、统一身份校验、请求限流与黑白名单、请求体大小和内容类型校验。很多攻击在到达业务服务之前就应该被网关拦截,比如超长JSON、畸形Header、SQL注入特征串、以及高频撞库请求。
在Spring Cloud Gateway中,可以配置全局过滤器做请求体校验和特征拦截:
@Component
public class RequestValidationFilter implements GlobalFilter, Ordered {
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
if (request.getHeaders().getContentLength() > 1024 * 1024) {
exchange.getResponse().setStatusCode(HttpStatus.PAYLOAD_TOO_LARGE);
return exchange.getResponse().setComplete();
}
String path = request.getURI().getPath();
if (path.contains("..") || path.contains("%2e%2e")) {
exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -100;
}
}
网关后面不要直接暴露所有服务。内部服务之间默认不信任,只有经过网关认证的请求才带有可信身份信息。网关将用户身份转换为内部JWT或透传Header时,要防止外部伪造Header,必须在网关层剥离所有以X-Internal开头的请求头,再重新注入。
身份与权限:从“能登录”到“能做什么”的细粒度控制
微服务场景下,身份认证和权限校验必须分离。认证放在网关统一处理,权限校验则下沉到每个服务自己完成。不要在所有服务里重复写登录逻辑,也不要只靠网关判断角色,因为服务间调用可能绕过网关。
推荐使用JWT作为身份凭证,但JWT不能存敏感信息,且必须设置短过期时间配合刷新令牌。每个下游服务拿到JWT后,先验签,再解析角色和权限范围。Spring Security Resource Server配置可以非常精简:
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/actuator/health").permitAll()
.requestMatchers("/api/orders/").hasAuthority("ORDER_READ")
.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
return http.build();
}
权限模型建议采用RBAC加资源归属校验。角色判断只能解决“能不能访问这个接口”,资源归属校验才能解决“能不能访问这条数据”。例如订单服务中,用户只能查询自己的订单,即使拥有ORDER_READ权限,也要在SQL或业务层追加user_id条件。很多越权漏洞就是因为只做了角色判断,没有做数据归属判断。
服务间通信:内部网络不是安全边界
微服务拆得越细,服务间调用越频繁,内部流量的安全就越重要。默认情况下,Kubernetes集群内Pod之间可以互相访问,如果某个服务被攻破,攻击者可以直接探测和调用其他服务。因此服务间通信必须启用mTLS,让每个服务都有独立证书,双向验证身份。
Service Mesh方案如Istio可以透明地完成mTLS,但引入成本较高。轻量方案是使用Spring Cloud OpenFeign配合OAuth2 Client Credentials模式,让调用方服务携带自己的客户端凭证去获取访问令牌。被调用方验证令牌的issuer和audience,确保请求来自合法的服务身份,而不是任意Pod。
同时,服务间调用要设置超时、重试上限和熔断。没有超时控制的服务容易被慢请求拖垮,没有重试上限的调用会在下游故障时放大流量,形成雪崩。这些虽然不是传统意义上的“安全漏洞”,但直接影响服务可用性,属于可用性安全范畴。
配置与依赖安全:漏洞往往来自不起眼的默认值
Spring Boot应用最常见的配置安全问题包括:Actuator端点暴露、数据库密码明文、默认密钥、调试模式开启。生产环境必须关闭或限制Actuator端点,至少要将health和info之外的所有端点关闭,或者通过独立端口暴露并加认证。
management:
endpoints:
web:
exposure:
include: health,info
endpoint:
health:
show-details: never
数据库密码、消息队列密码、第三方API密钥等敏感配置不能写在application.yml里提交到代码仓库。使用配置中心加环境变量注入,或者使用Kubernetes Secret配合外部密钥管理系统。对于JWT签名密钥、AES加密密钥这类高敏感值,建议使用专用的密钥管理服务,并定期轮换。
依赖安全是另一个被低估的领域。Java项目依赖树非常庞大,一个间接依赖的漏洞可能直接导致远程代码执行。必须在CI流水线中加入依赖漏洞扫描,例如使用OWASP Dependency-Check或Snyk,在构建阶段阻断已知高危漏洞的引入。同时定期升级Spring Boot和Spring Cloud版本,很多安全修复只在新版本中提供。
运行时防护:把安全控制前移到代码执行之前
即使网关和身份认证做得再好,业务代码本身仍然可能出现漏洞。SQL注入、命令注入、XXE、反序列化漏洞、SSRF等,都需要在运行时进行防护。Java生态中最有效的方案是使用RASP(Runtime Application Self-Protection)技术,在JVM层面拦截危险调用。
对于SQL注入,除了使用PreparedStatement参数化查询,还要在RASP层面检测SQL语句中的异常模式。对于反序列化,要限制可反序列化的类白名单,禁止ObjectInputStream直接反序列化不可信数据。对于SSRF,要在发起外部HTTP请求前校验目标地址,禁止访问内网IP段和云元数据地址。
日志安全同样重要。日志中不能记录密码、令牌、身份证号、手机号等敏感信息。在日志框架层面配置脱敏规则,例如Logback的PatternLayout配合自定义Converter,对匹配正则的字段进行掩码处理。同时要记录安全事件日志,包括登录失败、权限拒绝、异常请求参数、配置变更等,这些日志是事后溯源的基础。
审计与应急:安全体系必须有闭环
安全防护体系不是部署完就结束了,必须有持续的审计和应急响应能力。每个服务都应该输出结构化日志,统一收集到日志平台,并设置安全告警规则。例如同一用户5分钟内登录失败超过10次、某个服务短时间内返回大量403、请求参数中出现明显的攻击Payload,这些都应该触发告警。
微服务架构下,一次请求可能经过多个服务,分布式追踪必须包含安全上下文。在Trace中记录用户ID、请求来源IP、经过的服务节点和耗时,当发生安全事件时,可以快速还原攻击路径。Jaeger或Zipkin等追踪系统需要与日志系统打通,通过TraceID关联所有相关日志。
应急响应方面,要提前准备好服务降级和隔离预案。当某个服务被攻击时,应该能够快速将该服务从流量中摘除,而不是让整个集群受影响。Kubernetes的NetworkPolicy可以限制Pod间通信,在应急时快速隔离被攻破的服务。同时要定期进行红蓝对抗演练,模拟内部服务被攻破后的横向移动场景,检验防护体系的实际效果。
安全体系是分层防御,不是单点堆砌
Java微服务安全防护体系的核心思路是分层防御:网关层做第一道过滤,身份层做统一认证,服务层做细粒度授权,通信层做加密和双向验证,配置层做密钥和依赖管理,运行时做漏洞拦截,日志层做审计和告警。每一层都不能单独解决所有问题,但每一层都能让攻击者的成本大幅上升。真正落地的关键不是引入多少安全组件,而是把安全控制嵌入到开发、部署、运行的每一个环节中,让安全成为默认行为,而不是事后补丁。
