⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 https://mp.weixin.qq.com/s?__biz=MzAxNTM4NzAyNg==&mid=2247512481&idx=1&sn=809ed5087fb9e0e638864fec3fc973b9&poc_token=HBBBxmqjU3dcjnaBQcc6QIGCvC7v7PoNQS67nb7p 「程序员小富」欢迎转载,保留摘要,谢谢!


大家好,我是芋艿~~~

做微服务开发,很多人都有个疑问:既然网关 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")
public class 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. 安全防线:如果订单拥有者和当前用户对不上,直接拒绝访问
throw new 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 双向 mTLS:Istio等服务网格下,微服务之间的所有网络通信默认加密,且由Envoy代理进行身份互信验证,从基础设施层彻底解决内网防伪问题。

说在最后

其实说白了,微服务的安全边界划分只有一句话:

网关防外,微服务防内。

微服务做二次鉴权和防伪校验确实麻烦,但要想系统在线上跑得稳当,这一步还真省不掉。好在这些代码也就写一次,封装进公共拦截器或者Starter之后,业务开发直接从上下文里拿用户 ID,平时写代码基本不受影响。

《十万个why》系列持续更新~

看完等于学会,点个赞吧!!!

文章目录
  1. 1. 网关和微服务到底怎么分工?
  2. 2. 内网就绝对安全?
    1. 2.1. 内网横向移动
    2. 2.2. 服务端请求伪造攻击 SSRF
    3. 2.3. 内鬼与运维误操作
  3. 3. 业务授权网关干不了
    1. 3.1. 产生严重的业务耦合
    2. 3.2. 网关性能下降
  4. 4. 业内普遍的做法是什么?
    1. 4.1. 网关层粗粒度认证与信息传递
    2. 4.2. 微服务层做细粒度授权与业务校验
  5. 5. 内部服务调用和防伪怎么做?
    1. 5.1. Feign 拦截器透传用户上下文
    2. 5.2. 内部信任与防伪校验
  6. 6. 说在最后