
在微服务架构下,细粒度权限(如基于角色、权限及属性的访问控制)应由各资源服务(即后端微服务)独立执行;api 网关仅负责统一认证、令牌透传与客户端特异性安全处理(如 csrf、cors),而非替代业务级授权逻辑。
在微服务架构下,细粒度权限(如基于角色、权限及属性的访问控制)应由各资源服务(即后端微服务)独立执行;api 网关仅负责统一认证、令牌透传与客户端特异性安全处理(如 csrf、cors),而非替代业务级授权逻辑。
✅ 权限校验应下沉至资源服务层
细粒度授权(Fine-Grained Authorization)——例如“用户是否具备对某订单执行‘取消’操作的权限”或“能否查看特定部门下带敏感标签的客户数据”——本质上属于业务上下文强相关的逻辑。这类规则随产品迭代频繁变更,且不同微服务的数据模型、领域边界和合规要求各异。若将其集中于 API 网关实现,将导致:
- 网关耦合业务语义,违背“关注点分离”原则;
- 每次权限策略调整需修改并发布网关,影响所有下游服务稳定性;
- 难以进行单元测试、灰度验证与服务自治演进。
因此,最佳实践是:API 网关仅完成身份认证(Authentication)与初步令牌校验(如 JWT 签名、过期时间、scope 匹配),而将权限决策(Authorization)完全交由各资源服务自主执行。
? 如何在资源服务中高效获取授权依据?
既然 Token Relay 仅向微服务透传 accessToken(标准 JWT),那么服务如何获得用户角色、权限等信息?答案是:从 access token 的 claims 中解析结构化授权声明,并按需补充查询。
推荐做法如下:
-
授权服务器(AS)在签发 access token 时嵌入核心授权声明
例如:{ "sub": "user-123", "roles": ["admin", "finance-auditor"], "permissions": ["order:read", "report:export"], "dept_id": "FIN", "sensitive_level": "L2" }这些字段应通过可扩展的、标准化的方式(如
scope映射或自定义 claim)注入,避免硬编码或网关二次加工。 -
资源服务在拦截器或过滤器中解析 token 并构建授权上下文
示例(Spring Boot + Java):@Component public class OAuthAuthorizer implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) { HttpServletRequest request = (HttpServletRequest) req; String accessToken = extractBearerToken(request); Jwt jwt = JwtDecoder.create().decode(accessToken); // 提取 roles 和自定义权限属性 List<String> roles = jwt.getClaimAsStringList("roles"); String deptId = jwt.getClaimAsString("dept_id"); SecurityContextHolder.getContext() .setAuthentication(new OAuth2AuthenticationToken(jwt, roles, deptId)); chain.doFilter(req, res); } } -
业务逻辑层结合领域数据做最终授权判断
@Service public class OrderService { public void cancelOrder(String orderId) { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); String userId = auth.getName(); String deptId = (String) auth.getDetails(); // 或从 authorities 获取扩展属性 // 查询订单所属部门,比对用户 dept_id 是否匹配 Order order = orderRepository.findById(orderId).orElseThrow(); if (!Objects.equals(order.getDeptId(), deptId)) { throw new AccessDeniedException("No permission to cancel order in this department"); } // 执行业务逻辑... } }
⚠️ 注意事项:
- 不要在网关中解析并重写 token(如添加
authorities字段再转发),这会破坏 token 的不可篡改性与审计溯源能力;- 避免在资源服务中重复调用用户中心服务查询权限——应尽量通过 token 内置声明满足 80% 场景,复杂场景再走异步查库或本地缓存;
- 所有权限判断必须发生在服务端,前端隐藏按钮 ≠ 安全控制。
❌ 不应在 API 网关触发 OIDC 授权码流程
问题中提到网关主动执行 authorization code flow 并携带 openid scope,这是一种反模式,主要原因包括:
- 破坏无状态通信契约:API 调用应是纯 HTTP 请求/响应,不应包含重定向(302)等交互式行为。若网关对 Ajax 请求发起跳转,前端将无法处理,导致静默失败;
- 限制客户端类型:移动 App、IoT 设备、Server-to-Server 调用均无法参与浏览器重定向流程;
- 混淆客户端角色:网关不是“用户代理”,它不具备代表终端用户交互的能力,也不该持有用户 session cookie 来驱动登录流程。
✅ 正确做法是:每个前端客户端(Web SPA、Mobile App、Desktop Client)各自作为 OAuth 2.1 / OIDC 客户端,直接与授权服务器交互完成登录,获取 access_token 后,携带该 token 调用 API 网关。
网关只需验证 token 有效性,并透传给后端服务。若 token 无效或过期,直接返回 401 Unauthorized,由客户端自行决定刷新或重新登录。
? API 网关的安全定位:协议适配器 & 客户端安全网关
网关真正的价值在于抽象客户端差异性安全需求,而非替代业务授权。典型职责包括:
| 功能 | 说明 | 示例 |
|---|---|---|
| Token 中继(Token Relay) | 将客户端提供的 access_token 透传至后端服务,不修改内容 |
Spring Cloud Gateway 的 TokenRelayGatewayFilterFactory
|
| Cookie → Token 转换 | 对使用 Secure Cookie 登录的 Web 应用,网关解密 cookie、提取 token 并注入请求头 | 适用于 Backend-for-Frontend(BFF)模式 |
| 跨域与 CSRF 防护 | 针对浏览器客户端启用 CORS 头、校验 SameSite Cookie、验证 X-Requested-With
|
仅作用于 Web 流量,不影响 App 直连 |
| Scope 校验与路由分流 | 根据 token 中的 scope 决定是否允许访问某类 API(粗粒度准入) |
如 api:admin scope 才能访问 /v1/admin/**
|
? 总结一句话:API 网关是“守门人”,不是“裁判员”;它确保来者持有效门票入场,但具体能坐哪个座位、能不能上台发言,得由每个场馆(微服务)自己说了算。

















