网关已经统一鉴权,为什么微服务内部还要二次鉴权?
2026/7/17 8:32:00 网站建设 项目流程

做微服务开发,很多人都有个疑问:既然网关 Gateway 已经统一拦截了请求,校验了 Token 签名,并把用户信息塞进 Header 传给下游服务了。

为什么微服务内部的业务代码,比如订单服务、会员服务里,每个接口依然还要做一次鉴权和数据所有权校验?

网关把完关了,微服务内部再鉴权,这不是脱裤子放屁——多此一举吗?这么想是因为多人觉得内网是安全的,网关做好防护后内部就可以直接信任通信。

但从实际安全和业务架构设计的角度来看,网关的统一拦截与微服务内部的二次校验,在职责分工上完全不同。

网关和微服务到底怎么分工?

微服务架构的安全体系认证(Authentication)授权(Authorization)是有着明确的分工。

网关负责的是统一认证,解决的是你是谁的问题。它是系统的流量入口,主要负责全局的身份验证,比如校验Token是否合法、是否过期。一旦通过网关就会解析出用户身份信息,透传给下游。

微服务内部负责的是业务授权,解决的是你能干什么的问题。在身份确认后,业务服务需要根据当前用户的身份,校验他是否有权限操作特定的资源。比如校验当前用户是否有权修改这笔订单。这种涉及数据所有权、状态和规则的校验,必须由具体的业务服务来做。

其实这种职责划分,也是微服务架构单一职责原则在安全领域的体现。

内网就绝对安全?

有些传统公司内部,默认外网是危险的,内网是安全的。但在微服务架构中,这种假设一旦成立,就优点危险了。

因为真实的内网里,能出幺蛾子的地方其实非常多,随手举三个高频的坑:

内网横向移动

黑客通过网关某一个对外暴露、且存在严重漏洞的微服务拿到内网的一台服务器权限。如果内网微服务之间互相信任、接口裸奔,黑客可以在这台机器上直接调用其他微服务的敏感接口,比如http://order-service/delete?id=xxx,直接绕过网关。

服务端请求伪造攻击 SSRF

比如某个公开的微服务存在 SSRF 漏洞,黑客诱导该服务向内网其他服务发送伪造请求。由于请求从内网发出,你不做二次鉴权,被请求的服务就会无条件执行。

内鬼与运维误操作

开发或者运维在内网调试,可能直接跳过网关,向微服务直接发起接口调用,或者内部其他微服务写错了调用地址,误删数据。

所以现在微服务架构安全模型无一例外都是零信任模型。就是默认内网不安全,每一次请求、每一个节点,必须进行身份和权限的二次验证。

业务授权网关干不了

大部分也会想过:“既然如此,那我让网关把授权也干了不行吗?网关拦截请求,顺便去数据库查一下这个用户能不能操作这条数据。”

千万别这么干,这纯纯是自嗨式架构!

网关是整个业务集群的流量入口,它的首要职责是快速转发限流降级,如果网关需要做细粒度的业务授权,会产生很多问题。

产生严重的业务耦合

比如校验用户 A 能不能修改订单 B,网关需要知道订单服务的业务逻辑,必须去查询订单数据库,判断订单 B 的 owner 是不是 A。 还要判断订单当前的状态,如果是已发货,可能就不能修改。

这样一来,所有微服务的业务逻辑和数据库查询都会堆积在网关层。网关从一个轻量级的流量转发器,退化成一个臃肿的大单体服务,微服务拆分的那就没意义了。

网关性能下降

网关通常基于Netty这类异步非阻塞框架,像Spring Cloud Gateway就是,如果为了鉴权在网关层发起大量的业务数据库查询,会极大地占用网关的线程池和响应时间,导致整体集群的入口并发量呈指数级下降。

业内普遍的做法是什么?

明确了分工,那在具体的业务中实践,主流的架构一般采用以下方案

网关层粗粒度认证与信息传递

网关负责校验 Token 的合法性,从 Token 中解析出最核心的用户上下文信息,如userId,tenantId,roles,然后通过 HTTP Header,比如加密的 JSON,或者明文的头信息透传给下游。

X-User-Context: {"userId": "10001", "userName": "小富", "roles": ["ADMIN"]}
微服务层做细粒度授权与业务校验

微服务通常在入口处利用拦截器Interceptor或者Filter,拦截请求头X-User-Context,解析出用户身份并将其通过MDC绑定到线程上下文中,方便后续业务方法随拿随用。

在具体的业务方法或Controller上,我们就可以根据上下文进行二次校验。看一段标准的 Java 实现

@RestController @RequestMapping("/orders") publicclass OrderController { @Autowired private OrderService orderService; // 1. 角色权限校验:通过 Spring Security 校验当前用户是否拥有相应权限 @PutMapping("/{orderId}") @PreAuthorize("hasAuthority('ORDER_WRITE')") public ResponseEntity<Void> updateOrder(@PathVariable Long orderId, @RequestBody OrderDto orderDto) { // 2. 从当前线程 ThreadLocal 中获取网关传下来的用户 ID Long currentUserId = UserContextHolder.getUserId(); // 3. 数据归属权校验:必须到数据库查一下,这笔订单是否真正属于当前用户 Order order = orderService.getById(orderId); if (order == null) { return ResponseEntity.notFound().build(); } if (!order.getUserId().equals(currentUserId)) { // 4. 安全防线:如果订单拥有者和当前用户对不上,直接拒绝访问 thrownew AccessDeniedException("您无权修改该订单!"); } orderService.update(orderId, orderDto); return ResponseEntity.ok().build(); } }

这段代码分拦截器处理身份解析与上下文绑定,Spring Security 校验了角色权限,业务代码校验了数据归属权,各司其职,网关完全不需要参与这部分的复杂逻辑。

内部服务调用和防伪怎么做?

这里紧接着就会衍生出一个非常现实的问题:如果是微服务内部相互调用,服务 A 用Feign调服务 B,下游服务怎么拿到这个用户上下文?总不能让服务 A 自己编一个吧?通常有两种解决方案

Feign 拦截器透传用户上下文

利用 Spring Cloud 的RequestInterceptor,可以在 Feign 发起内部调用,自动把当前线程ThreadLocal中的X-User-Context请求头补上,实现身份信息的全链路透传。

@Component public class FeignAuthInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { // 从当前线程 ThreadLocal 拿用户上下文 String userContextJson = UserContextHolder.getRawContext(); if (userContextJson != null) { // 自动塞入 Feign 请求头中,往下游传递 template.header("X-User-Context", userContextJson); } } }
内部信任与防伪校验

如果不做防御,恶意人员可以直接手动往微服务的接口塞一个X-User-Context: {"userId": "1"}绕过鉴权,对内网安全防伪有几种做法:

轻量级对称加密签名:网关生成透传 Header,用HMAC-SHA256共享密钥对 UserContext 进行签名并带上时间戳。微服务内部拿到后先用同样的密钥校验签名和是否过期。相比 RSA 非对称加密,对称加密速度快,高并发下的 CPU 损耗几乎可以忽略不计。

私网网段白名单限制:直接在微服务的网络或安全组层面进行限制,只允许网关 IP 段或者K8s内部 Pod 网段发起调用,凡是来自外网 IP 的直接请求一律拒绝。

Service Mesh 双向 mTLSIstio等服务网格下,微服务之间的所有网络通信默认加密,且由Envoy代理进行身份互信验证,从基础设施层彻底解决内网防伪问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询