找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2070

积分

0

好友

287

主题
发表于 6 天前 | 查看: 16| 回复: 0

在业务系统开发中,我们时常会面临这样的需求:某些核心或敏感接口仅允许集群内部的服务间调用,而不能暴露给外网直接访问。这可能是出于安全、数据隔离或业务逻辑的考量。那么,有哪些技术方案可以实现这一目标?我们又该如何选择并落地实现呢?本文将梳理几种常见思路,并重点详解一种对业务侵入性较低、且易于维护的实践方案。

几种可行的技术方案

目前,主流的实现方案大致有三种思路,各有其适用场景与优缺点。

方案一:内外网接口微服务隔离

这种方案的核心思想是进行物理隔离。我们将对外部用户暴露的接口与仅供内部服务调用的接口,分别部署到两个独立的微服务中。其中一个服务专门承载所有对外接口,另一个则作为“内部聚合服务”,汇集所有只能内网访问的接口,并由它去调用各个业务模块获取数据。

  • 优点:隔离彻底,权限边界清晰。
  • 缺点:引入了额外的服务,增加了系统的复杂度、网络调用链长度以及后期的运维成本。对于内部接口不多的场景,性价比不高。

方案二:网关配合Redis实现接口白名单

此方案将鉴权逻辑上移到网关层。我们在 Redis 中维护一个“接口白名单”列表。当外部请求抵达网关时,网关会查询 Redis,判断当前请求的接口是否在白名单内。如果在,则放行;如果不在,则直接拒绝。

  • 优点:对后端业务代码完全无侵入,只需维护好白名单配置即可。
  • 缺点:白名单的维护(增删改查)本身是一项持续性工作,在许多公司流程中,开发人员操作 Redis 可能需要提申请流程,效率较低。更重要的是,每次外部请求都需要执行一次Redis查询和匹配逻辑,增加了系统响应耗时。考虑到正常流量中合法请求占绝大多数,仅为拦截少数非法请求而让所有请求都付出这个代价,性能损耗较大。

方案三:网关添加标识 + 业务侧AOP鉴权

这个方案转换了思路,从“判断接口是否在白名单”变为“判断请求是否来自外部”。它巧妙利用了网络路径的特点:所有从公网进入的请求必然经过外部网关(如 Spring Cloud Gateway, Nginx 等),而集群内部服务间的调用则通过内部服务发现(如 Kubernetes Service)直接通信,不经过外部网关。

基于此,我们可以在网关上为所有经过它的请求(即外部请求)统一添加一个特定的Header标识(例如 from=public)。后端的业务接口则通过AOP切面,在方法执行前检查请求中是否携带此标识。如果携带,则说明是外部请求,再根据该接口上标注的“仅限内网”注解来决定是否放行。

网关与AOP协作流程图

  • 优点
    1. 性能更优:将鉴权压力从网关分散到各个业务节点,避免了网关成为性能瓶颈。内部服务间调用的性能完全不受影响。
    2. 开发高效:开发者可以直接在代码中通过注解定义接口的访问权限,意图明确,可读性强。
    3. 侵入性低:通过注解和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 开发的最佳实践,欢迎访问 云栈社区 进行交流探讨。




上一篇:职业开发者如何“控制”AI编程:从研究看真实工程实践
下一篇:Linux内核SMP多核启动、调度与并发机制全面解析
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-10 09:02 , Processed in 0.293933 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表