在微服务架构的业务开发中,我们常常会遇到这样的场景:某些接口仅供内部服务间调用,不允许直接暴露给外网。这可能是出于安全、数据隔离或业务逻辑的考虑。面对这个需求,有哪些可行的实现方案?我们又该如何选择并落地呢?今天就来系统地梳理一下。
1、三种可行的解决方案
目前主流的思路大致有三种:通过微服务进行物理隔离、利用网关配合Redis实现白名单、以及在网关和业务侧结合AOP进行智能判断。
1.1 方案一:内外网接口微服务隔离
顾名思义,此方案将需要对外暴露的接口和仅对内暴露的接口分别部署到两个独立的微服务中。对外服务开放所有接口,而对内服务则部署在内部网络,禁止外部直接访问。
实现方式:需要额外编写一个聚合内部接口的微服务,该服务通过内网调用各个业务模块获取数据或执行业务,再对外部网关提供统一的内部接口入口。
优缺点分析:
- 优点:物理隔离,安全性高,权限边界清晰。
- 缺点:显著增加了系统复杂性(多维护一个服务)、调用链变长导致耗时增加、后期维护成本较高。
1.2 方案二:网关 + Redis 实现白名单机制
这个方案的核心思想是在网关上做统一拦截。将所有允许外网访问的接口URL维护在Redis的一个白名单集合中。外部请求到达网关时,网关判断请求的接口是否在白名单内,是则放行,否则拒绝。
优缺点分析:
- 优点:对业务代码零侵入,只需维护好白名单列表。
- 缺点:
- 维护成本高:白名单需要持续维护,在很多公司流程中,开发人员修改Redis配置可能需要提工单,不够敏捷。
- 性能损耗:每个外部请求都需要执行一次Redis查询和匹配逻辑。考虑到绝大多数正常请求都应被放行,这种“防御性”检查在多数时候成了不必要的开销,性价比较低。
1.3 方案三:网关 + AOP(推荐)
相比于方案二对“接口”进行判断,方案三巧妙地转为对“请求来源”进行判断,并将判断逻辑下沉到各个业务侧,从而避免网关成为性能瓶颈。
其原理基于一个关键事实:所有从外部互联网进入的请求都必然经过统一网关(如Spring Cloud Gateway, Nginx等),而内部服务之间的调用(例如通过K8s Service)则无需经过此外部网关。
我们可以利用这个特点:
- 网关侧:对所有经过外部网关的请求,在Header中添加一个特定标识字段(例如:
from=public)。
- 业务侧:接口被调用时,通过AOP切面判断请求Header中是否包含该标识。如果有,则说明请求来自外网;如果没有,则说明是内部服务调用。对于标记为“仅内网访问”的接口,如果发现请求携带外网标识,则直接拒绝。
优缺点分析:
- 优点:
- 高性能:将判断压力从网关分散到各个业务实例,消除了系统性瓶颈。
- 高可维护性:开发者可以在业务开发时,通过注解直接、清晰地定义接口的访问权限,代码可读性极强。
- 低侵入:通过自定义注解实现,对业务代码的侵入性降到最低。
- 缺点:需要在业务侧引入一套AOP拦截机制,有一定框架耦合度,但通过良好封装可以接受。

2、方案三(网关+AOP)代码实战
下面我们针对推荐的方案三,进行具体的代码实现演示。
2.1 网关侧:添加请求来源标识
我们需要在网关的全局过滤器中,为所有请求添加一个Header。
@Component
public class AuthFilter implements GlobalFilter, Ordered {
@Override
public Mono < Void > filter ( ServerWebExchange exchange, GatewayFilterChain chain ) {
return chain.filter(
exchange.mutate().request(
exchange.getRequest().mutate().header("id", "").header("from", "public").build())
.build()
);
}
@Override
public int getOrder () {
return 0;
}
}
2.2 业务侧:定义注解与AOP切面
首先,定义一个用于标记“仅内网访问”的注解。
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OnlyIntranetAccess {
}
然后,实现处理该注解的AOP切面。
@Aspect
@Component
@Slf4j
public class OnlyIntranetAccessAspect {
@Pointcut ( "@within(org.openmmlab.platform.common.annotation.OnlyIntranetAccess)" )
public void onlyIntranetAccessOnClass () {}
@Pointcut ( "@annotation(org.openmmlab.platform.common.annotation.OnlyIntranetAccess)" )
public void onlyIntranetAccessOnMethed () {
}
@Before ( value = "onlyIntranetAccessOnMethed() || onlyIntranetAccessOnClass()" )
public void before () {
HttpServletRequest hsr = (( ServletRequestAttributes ) RequestContextHolder.getRequestAttributes()) .getRequest ();
String from = hsr.getHeader ( "from" );
if ( !StringUtils.isEmpty( from ) && "public".equals ( from )) {
log.error ( "This api is only allowed invoked by intranet source" );
throw new MMException ( ReturnEnum.C_NETWORK_INTERNET_ACCESS_NOT_ALLOWED_ERROR);
}
}
}
2.3 应用注解
最后,在需要限制访问权限的Controller方法上,使用 @OnlyIntranetAccess 注解即可。
@GetMapping ( "/role/add" )
@OnlyIntranetAccess
public String onlyIntranetAccess() {
return "该接口只允许内部服务调用";
}
总结
对比三种方案,“网关添加标识 + 业务侧AOP判断”的方案在性能、可维护性和开发效率上取得了较好的平衡。它遵循了“谁开发,谁负责”的原则,将权限定义权交还给业务开发者,并通过技术手段将外部流量特征传递到业务层进行精准识别,是一种优雅且实用的解决方案。
当然,具体方案选择还需结合项目的实际架构、团队技术栈和安全要求来定。希望本文的梳理和实战演示,能为你在云栈社区处理类似问题时提供清晰的思路和可行的代码参考。