在业务系统开发中,我们时常会面临这样的需求:某些核心或敏感接口仅允许集群内部的服务间调用,而不能暴露给外网直接访问。这可能是出于安全、数据隔离或业务逻辑的考量。那么,有哪些技术方案可以实现这一目标?我们又该如何选择并落地实现呢?本文将梳理几种常见思路,并重点详解一种对业务侵入性较低、且易于维护的实践方案。
几种可行的技术方案
目前,主流的实现方案大致有三种思路,各有其适用场景与优缺点。
方案一:内外网接口微服务隔离
这种方案的核心思想是进行物理隔离。我们将对外部用户暴露的接口与仅供内部服务调用的接口,分别部署到两个独立的微服务中。其中一个服务专门承载所有对外接口,另一个则作为“内部聚合服务”,汇集所有只能内网访问的接口,并由它去调用各个业务模块获取数据。
- 优点:隔离彻底,权限边界清晰。
- 缺点:引入了额外的服务,增加了系统的复杂度、网络调用链长度以及后期的运维成本。对于内部接口不多的场景,性价比不高。
方案二:网关配合Redis实现接口白名单
此方案将鉴权逻辑上移到网关层。我们在 Redis 中维护一个“接口白名单”列表。当外部请求抵达网关时,网关会查询 Redis,判断当前请求的接口是否在白名单内。如果在,则放行;如果不在,则直接拒绝。
- 优点:对后端业务代码完全无侵入,只需维护好白名单配置即可。
- 缺点:白名单的维护(增删改查)本身是一项持续性工作,在许多公司流程中,开发人员操作 Redis 可能需要提申请流程,效率较低。更重要的是,每次外部请求都需要执行一次Redis查询和匹配逻辑,增加了系统响应耗时。考虑到正常流量中合法请求占绝大多数,仅为拦截少数非法请求而让所有请求都付出这个代价,性能损耗较大。
方案三:网关添加标识 + 业务侧AOP鉴权
这个方案转换了思路,从“判断接口是否在白名单”变为“判断请求是否来自外部”。它巧妙利用了网络路径的特点:所有从公网进入的请求必然经过外部网关(如 Spring Cloud Gateway, Nginx 等),而集群内部服务间的调用则通过内部服务发现(如 Kubernetes Service)直接通信,不经过外部网关。
基于此,我们可以在网关上为所有经过它的请求(即外部请求)统一添加一个特定的Header标识(例如 from=public)。后端的业务接口则通过AOP切面,在方法执行前检查请求中是否携带此标识。如果携带,则说明是外部请求,再根据该接口上标注的“仅限内网”注解来决定是否放行。

- 优点:
- 性能更优:将鉴权压力从网关分散到各个业务节点,避免了网关成为性能瓶颈。内部服务间调用的性能完全不受影响。
- 开发高效:开发者可以直接在代码中通过注解定义接口的访问权限,意图明确,可读性强。
- 侵入性低:通过注解和AOP实现,对业务逻辑的入侵极少。
- 缺点:需要在业务侧引入少量的通用组件(注解和AOP切面)。
综合来看,方案三在性能、开发效率和维护成本上取得了较好的平衡,是许多中大型微服务项目的优先选择。接下来,我们将详细演示如何基于 Spring Cloud Gateway 和 Spring AOP 来实现它。
方案三的具体实现
我们将实现分为三个步骤:网关层添加标识、业务层定义注解与AOP切面、在具体接口上应用注解。
第一步:在网关层为外部请求添加标识
在 Spring Cloud Gateway 中,我们可以通过实现一个 GlobalFilter 来为所有经过网关的请求添加Header。
@Component
public class AuthFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 为所有经过此网关的请求添加 from=public 的Header
return chain.filter(
exchange.mutate().request(
exchange.getRequest().mutate().header("from", "public").build())
.build()
);
}
@Override
public int getOrder() {
return 0; // 过滤器执行顺序,可按需调整
}
}
这样,任何从公网访问进来并到达后端服务的请求,其Header中都会包含 from: public。
第二步:定义注解与AOP切面
首先,定义一个用于标记“仅限内网访问”的注解 @OnlyIntranetAccess。
@Target({ElementType.METHOD, ElementType.TYPE}) // 可以注解在方法或类上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OnlyIntranetAccess {
}
接着,实现核心的AOP切面类 OnlyIntranetAccessAspect。它负责拦截被 @OnlyIntranetAccess 注解的方法或类中的所有方法,并执行权限校验。
@Aspect
@Component
@Slf4j
public class OnlyIntranetAccessAspect {
// 切入点:注解在类上
@Pointcut("@within(com.yourcompany.annotation.OnlyIntranetAccess)")
public void onlyIntranetAccessOnClass() {}
// 切入点:注解在方法上
@Pointcut("@annotation(com.yourcompany.annotation.OnlyIntranetAccess)")
public void onlyIntranetAccessOnMethod() {}
// 环绕通知,在方法执行前进行校验
@Before(value = "onlyIntranetAccessOnMethod() || onlyIntranetAccessOnClass()")
public void before() {
// 获取当前HTTP请求
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// 检查请求头中是否有“from”字段,且其值是否为“public”
String from = request.getHeader("from");
if (!StringUtils.isEmpty(from) && "public".equals(from)) {
log.error("This api is only allowed to be invoked by intranet source.");
// 抛出自定义异常,由全局异常处理器转换为对应的HTTP状态码和错误信息
throw new BusinessException("该接口禁止外部访问");
}
}
}
这个切面是方案的核心。当请求到达一个被 @OnlyIntranetAccess 注解标记的接口时,切面会检查请求Header。如果发现 from=public,则判定为非法外部访问,直接抛出异常中断执行;如果没有这个Header或值不对,则说明是内部调用,允许继续执行业务逻辑。这种方式是 Java 生态中实现横切关注点的标准且高效的方式。
第三步:在需要保护的接口上使用注解
最后,在那些需要限制只能内网访问的Controller方法或整个Controller类上,加上我们定义的注解即可。
@RestController
@RequestMapping("/api/internal")
public class InternalApiController {
@GetMapping("/config")
@OnlyIntranetAccess // 此注解加在方法上,仅保护这一个接口
public String getInternalConfig() {
return "这是一个内部配置信息,外部无法访问";
}
@PostMapping("/sync")
public String syncData() {
return "这个接口可以对外暴露";
}
}
@RestController
@RequestMapping("/api/admin")
@OnlyIntranetAccess // 此注解加在类上,保护此Controller下的所有接口
public class AdminController {
@GetMapping("/users")
public List<User> listUsers() {
// 该方法仅允许内网访问
}
@PostMapping("/role")
public void addRole() {
// 该方法仅允许内网访问
}
}
通过以上三步,我们就完整实现了一套细粒度的接口内外网访问控制机制。这种基于 Spring Cloud Gateway 网关标识和业务侧AOP校验的架构,很好地平衡了功能性、性能和代码的整洁性,是构建安全微服务边界的一个有效实践。希望本文的梳理和示例能为你解决类似问题提供清晰的思路。如果你想了解更多关于微服务架构设计或 Java 开发的最佳实践,欢迎访问 云栈社区 进行交流探讨。