摘要
此前发布的《微服务五层防御架构:SSO、OAuth2、OIDC 原理与 Spring Authorization Server 落地》一文,从物理分层视角搭建了微服务安全的骨架,深度拆解了统一认证层的协议原理与授权服务器代码实现,属于单点深耕。但五层防御只是横向的架构骨架,真正的安全能力需要纵向贯穿每一层 ------身份认证解决「你是谁」,授权解决「你能做什么」,访问控制解决「放行还是拦截」 ,三者形成完整闭环,才是微服务安全的核心。
本文跳出单一协议与框架的局限,站在全链路体系化视角,系统梳理用户与服务两类主体的身份认证方案、两级授权架构、四层访问控制防线,构建一套可直接复用的微服务安全知识体系。
一、总纲:微服务安全的一横一纵体系
1.1 架构全景:横向五层骨架 + 纵向三大能力

- 横向:五层防御架构(物理骨架) 从外到内分为网关接入层、统一认证层、业务应用层、服务通信层、数据访问层,每一层承担不同的物理边界防护职责,形成多层拦截的纵深屏障。攻击流量需要逐层突破,单层防护失效时,其余层级仍可起到拦截作用。
- 纵向:认证 / 授权 / 访问控制(核心能力) 这是贯穿所有层级的安全内核,对应到五层架构中分别落地为:流量访问控制、用户身份认证与客户端授权、接口访问控制与业务细粒度授权、服务身份认证与服务权限校验、数据访问控制。三者职责边界清晰:
- 身份认证 :解决「你是谁」,确认访问主体的合法身份,是安全的第一道门;
- 授权 :解决「你能做什么」,定义主体的权限边界,是规则的定义与分配环节;
- 访问控制 :解决「这次请求放不放行」,在各层执行权限规则,是最终的执行环节。
1.2 本文与前篇的定位关系
本文与前篇《微服务五层防御架构:SSO、OAuth2、OIDC 原理与 Spring Authorization Server 落地》属于同一系列,为横向架构视角与纵向能力视角的互补关系 ,无内容重复,组合形成完整微服务安全知识体系。
前篇:横向分层架构篇
以前端请求物理流转路径为主线,建立「网关‑统一认证‑业务‑服务通信‑数据」五层防御整体架构。
- 重心:深度讲解 SSO/OAuth2/OIDC 协议、时序与 Spring Authorization Server 落地;其余四层仅做架构定义,点到即止。
- 解决:微服务安全的分层框架、边界划分,以及核心认证组件的工程实现。
本篇:纵向能力体系篇
以「身份认证‑授权‑访问控制」安全能力为主线,纵向穿透五层架构,补齐前篇未深入的细节。
- 重心:聚焦用户 / 服务身份体系、权限模型、多层访问控制执行、数据层防护、越权漏洞与设计避坑,不再重复协议基础代码。
- 解决:认证 / 授权 / 访问控制的职责边界、业务权限体系设计、全链路访问控制落地方法论。
简言之:前篇搭建五层安全骨架,本篇填充骨架内部的安全能力与落地逻辑 。
二、身份认证体系:两类主体的全场景认证方案
身份认证是安全体系的第一道门,核心目标是确认访问主体的合法身份 ,为后续授权与访问控制提供可信的身份依据。
微服务场景下,访问主体分为两大类:自然人用户 和机器服务 。前者面向前端访问,后者面向内网服务调用、异步消息等后台场景。
2.1 用户侧身份认证:面向人的访问入口
基于已落地的 OIDC+SSO 统一认证架构,用户侧身份体系的核心不再是能力搭建,而是分级认证管控、令牌全生命周期治理、高安全场景加固 ,构建适配不同业务风险等级的身份准入标准。
2.1.1 多维度分级认证因子选型
摒弃单一密码认证模式,按业务风险等级匹配差异化认证因子,实现 "低风险低门槛、高风险强校验",所有认证能力统一收敛至认证中心,业务服务无需重复开发:
- 基础认证(低风险) :账号密码、短信验证码,适用于日常业务查询、普通用户登录,兼顾安全与体验。
- 强认证(中风险) :TOTP 动态口令、扫码登录,脱离短信通道依赖,可离线校验,防范短信劫持与钓鱼,适用于员工后台登录、个人信息修改等敏感操作。
- 高阶强认证(高风险) :MFA 多因素认证、硬件密钥,采用 "知识因子 + 物理因子" 双重校验,适用于管理员登录、权限变更、批量数据导出、资金操作等高危场景。
2.1.2 生产级令牌与会话治理规范
聚焦生产落地的治理规则,解决令牌滥用、会话失控、信息泄露问题:
- 令牌职责严格拆分 :ID-Token 仅承载标准化用户身份、支撑 SSO 单点登录,禁止用于业务接口鉴权;Access-Token 作为业务访问凭证,承载粗粒度 Scope 权限,严格控制有效期。
- 杜绝敏感信息存储 :JWT 载荷仅保留用户 ID、账号等基础标识,禁止存放手机号、身份证、权限列表、密码等数据,规避 Base64 解码带来的泄露风险。
- 全生命周期管控 :配置合理的令牌过期、刷新、吊销机制,支持主动登出、强制下线、异地登录挤退,实现会话全局统一管控。
2.1.3 用户侧身份核心避坑要点
- 禁止业务服务自定义登录与会话逻辑,全量收敛至统一认证中心,避免身份孤岛、校验口径不统一;
- 身份认证仅作为准入条件,不依赖前端隐藏按钮、路由拦截做权限管控,细粒度权限交由授权与访问控制体系兜底;
- 高权限账号必须强制开启 MFA,杜绝弱密码、长期不更换密码等隐患。
2.2 服务侧身份认证:面向机器的后台认证
服务身份认证是前篇弱化、但微服务安全至关重要的核心能力。微服务最大的安全误区就是默认内网流量可信 ,导致服务间无校验、横向渗透风险极高。
服务侧认证的核心目标是:破除内网信任假设,为每个后台服务分配独立可信身份,实现机器主体的可识别、可管控、可审计。
2.2.1 为什么必须做服务身份认证
微服务拆分后,服务数量多、调用关系复杂,传统的边界安全思维认为「内网流量都是可信的」,服务之间调用不需要鉴权。但实际上:
- 攻击者只要攻陷任意一个对外暴露的服务,就可以在内网横向移动,访问所有内部服务;
- 服务之间没有身份标识,出现问题无法追溯调用来源;
- 无法做细粒度的服务权限控制,任意服务可以调用其他所有服务的接口。
服务身份认证就是给每个服务分配唯一的身份凭证,服务之间调用必须携带并校验身份,最小化攻击面,实现「内网也不信任」的零信任思路。
2.2.2 四类主流服务认证方案对比
微服务场景下,主流的服务身份认证方案有四种,在安全性、性能、复杂度、适用场景上各有优劣:
|----------------|---------------------------------------------------------|---------|----------------|-----------|--------------------------|
| 认证方案 | 核心原理 | 安全性 | 性能表现 | 实现复杂度 | 适用场景 |
| OAuth2 客户端凭证模式 | 服务使用 client_id/client_secret 向统一授权中心申请服务级 Token,调用时携带校验 | 高 | 中(Token 可本地缓存) | 中 | 有统一认证中心的微服务体系、跨团队异构系统 |
| AppKey+HMAC 签名 | 调用方用密钥对请求参数 + 时间戳 + 请求路径做 HMAC 哈希签名,接收方用相同算法验签 | 中高 | 高(无额外网络请求) | 低 | 内部简单服务调用、低延迟场景、无统一认证中心 |
| 非对称加密认证 | 每个服务生成公私钥对,调用方用私钥签名请求,接收方用公钥验签 | 高 | 中 | 中高 | 跨部门、跨公司服务调用,密钥不能共享的场景 |
| mTLS 双向证书 | TLS 握手阶段客户端与服务端互相校验证书,链路层身份认证 | 极高 | 中(TLS 握手有开销) | 高 | 云原生 K8s 环境、服务网格架构、极高安全要求 |
2.2.3 方案详解与落地要点
- OAuth2 客户端凭证模式(推荐主流方案)
这是微服务体系最主流的服务身份方案,和用户侧 OIDC 共用同一套授权中心,体系统一,管理成本低。
- 核心逻辑:服务作为 OAuth2 的客户端,使用client_credentials授权类型,向授权服务器申请 Access Token。这个 Token 代表服务自身的身份,不携带任何用户信息。
- 落地要点:
- Token 本地缓存,避免每次调用都请求授权中心,提前 30-60 秒刷新,防止过期;
- 每个服务分配独立的 client_id 和 client_secret,禁止共用;
- 按最小权限原则分配服务 Scope,比如订单服务只给库存查询权限,不给修改权限。
核心实现代码(基于 Spring OAuth2 Client,带本地缓存):
java
@Service
public class ServiceTokenService {
private final OAuth2AuthorizedClientManager authorizedClientManager;
private final LoadingCache<String, OAuth2AccessToken> tokenCache;
public ServiceTokenService(OAuth2AuthorizedClientManager authorizedClientManager) {
this.authorizedClientManager = authorizedClientManager;
// 本地缓存:90分钟过期,提前30分钟刷新
this.tokenCache = CacheBuilder.newBuilder()
.expireAfterWrite(90, TimeUnit.MINUTES)
.removalListener(notification -> refreshToken((String) notification.getKey()))
.build(new CacheLoader<String, OAuth2AccessToken>() {
@Override
public OAuth2AccessToken load(String clientRegistrationId) {
return requestNewToken(clientRegistrationId);
}
});
}
/**
* 获取服务级令牌
*/
public String getServiceToken(String clientRegistrationId) {
try {
return tokenCache.get(clientRegistrationId).getTokenValue();
} catch (ExecutionException e) {
throw new BizException("获取服务令牌失败", e);
}
}
private OAuth2AccessToken requestNewToken(String clientRegistrationId) {
OAuth2AuthorizeRequest request = OAuth2AuthorizeRequest
.withClientRegistrationId(clientRegistrationId)
.principal(new AnonymousAuthenticationToken("service", "service"))
.build();
OAuth2AuthorizedClient client = authorizedClientManager.authorize(request);
if (client == null || client.getAccessToken() == null) {
throw new BizException("授权服务器返回令牌为空");
}
return client.getAccessToken();
}
private void refreshToken(String clientRegistrationId) {
try {
tokenCache.put(clientRegistrationId, requestNewToken(clientRegistrationId));
} catch (Exception e) {
log.error("刷新服务令牌失败, clientId:{}", clientRegistrationId, e);
}
}
}
2. AppKey+HMAC 签名
轻量级方案,不需要中心化授权服务器,适合内部小型微服务集群。
- 核心逻辑:给每个服务分配一对 AppKey 和 Secret,调用时将请求方法、路径、时间戳、AppKey 拼接成签名串,用 Secret 做 HMAC-SHA256 计算签名;接收方用同样的算法重新计算,对比一致则认证通过。
- 优点:无额外网络请求,性能高,实现简单;
- 缺点:密钥分散管理,轮换麻烦,权限粒度粗。
3. 非对称加密认证
基于公私钥体系,安全性更高,适合跨组织、跨公司的服务调用。
- 核心逻辑:每个服务生成自己的公私钥对,公钥统一发布到密钥中心,私钥自己保管。调用方用私钥对请求签名,接收方从密钥中心获取对方公钥验签。
- 优点:密钥不需要共享,安全性高,身份不可抵赖;
- 缺点:密钥管理复杂,验签性能比 HMAC 低。
4. mTLS 双向 TLS
链路层的身份认证方案,云原生场景下的主流方向,通常结合服务网格(Istio)实现。
- 核心逻辑:在 TLS 握手阶段,不仅服务端要提供证书,客户端也要提供证书,双方互相校验证书的合法性,完成身份认证。
- 优点:业务代码零侵入,链路层加密 + 认证一体化,安全性极高;
- 缺点:证书管理复杂,TLS 握手有性能开销,需要配套的证书颁发体系。
2.2.4 核心 场景的身份流转 规范
严格区分用户上下文与机器上下文,杜绝身份混用、令牌透传等违规操作:
- 同步服务调用(有用户上下文) :前端请求携带用户令牌完成用户认证,服务间调用禁止透传用户 AccessToken ,必须使用自身服务机器令牌完成内网鉴权,仅通过请求头透传用户 ID 用于业务逻辑,实现用户身份与服务身份分离。
- 异步消息调用(无用户上下文) :Kafka/RabbitMQ 消费、定时任务、批量处理等场景,全程使用独立机器账号 + 服务令牌认证,杜绝伪造用户身份、身份缺失问题。
三、授权体系:权限规则的定义与两级架构
授权是安全体系的「司令部」,核心是定义谁能访问哪些资源 ,为访问控制提供规则依据。
3.1 授权与认证、访问控制的边界
- 身份认证 :解决「你是谁」,确认主体身份,是授权的前提;
- 授权 :解决「你能做什么」,是规则的定义、分配与存储,属于立法环节;
- 访问控制 :解决「这次请求放不放行」,是授权规则的执行环节,属于执法环节。
三者是严格的前后依赖关系:先认证,再授权,最后执行访问控制。
3.2 微服务两级授权架构
微服务场景下,授权体系必须分层设计,统一认证中心不可能维护所有业务的细粒度权限,业务系统也不能各自为政定义权限。行业最佳实践是两级授权架构 :客户端级授权(认证中心侧)+ 业务级授权(业务系统侧)。
3.2.1 第一级:客户端级授权(认证中心侧)
客户端级授权是粗粒度的资源范围授权,由统一认证中心负责管理,对应 OAuth2 的 Scope 机制。
- 管控对象:前端应用、后端服务等客户端;
- 核心作用:控制一个客户端能够访问哪些资源服务的哪些大类接口;
- 命名规范:建议按「资源类型:操作」命名,比如user:read、order:create、inventory:query,分层清晰,易于管理;
- 落地要点:
- Scope 最小化原则,给客户端分配刚好够用的权限,不要给*全量权限;
- 客户端白名单机制,只有注册过的客户端才能申请令牌;
- 授权服务器签发 Token 时,把 Scope 写入 JWT 载荷,资源服务器校验 Scope。
3.2.2 第二级:业务级授权(业务系统侧)
业务级授权是细粒度的功能与数据授权,由业务系统或独立的权限中心负责管理,是真正的业务权限核心。
- 管控对象:用户、服务账号;
- 权限维度:
- 功能权限 :菜单、按钮、接口、操作等功能级别的访问权限;
- 数据权限 :行级权限(能看到哪些数据)、列级权限(能看到哪些字段)、租户级权限(能访问哪个租户的数据)。
3.3 主流权限模型与微服务适配
业务级授权有四种主流模型,各有优劣,适配不同的业务场景:
|-------------|-------------------------|-----------------|------------------|-------------------------|
| 权限模型 | 核心思想 | 核心优势 | 主要劣势 | 微服务适用场景 |
| RBAC(基于角色) | 用户 → 角色 → 权限,通过角色间接分配权限 | 简单直观、易管理、性能好 | 权限粒度粗、动态性差 | 常规业务系统、后台管理系统、绝大多数企业微服务 |
| ABAC(基于属性) | 基于用户属性、资源属性、环境属性动态判断权限 | 灵活度高、细粒度、支持动态场景 | 实现复杂、性能开销大、规则难维护 | 多租户 SaaS、复杂业务、动态权限场景 |
| ACL(访问控制列表) | 每个资源维护可访问的主体列表 | 精准、面向资源、细粒度 | 管理分散、权限爆炸、难以批量维护 | 资源型系统、文件服务、配置中心 |
| TBAC(基于任务) | 基于业务流程的临时授权,任务完成权限回收 | 上下文相关、最小权限、职责分离 | 强依赖业务流程、通用性差 | 工作流系统、审批场景、临时授权 |
3.3.1 RBAC:最通用的企业级方案
RBAC(Role-Based Access Control)是目前应用最广泛的权限模型,核心是「角色」作为中间层,用户通过分配角色获得权限,而不是直接给用户分配权限。
- 经典模型:RBAC0(基础模型)、RBAC1(角色继承)、RBAC2(角色约束)、RBAC3(统一模型);
- 微服务落地建议:
- 集中部署权限中心,统一维护用户、角色、权限关系,提供权限查询接口;
- 业务服务调用权限中心获取用户权限列表,本地缓存,减少重复调用;
- 适合大多数业务场景,比如电商的买家、卖家、客服、管理员角色体系。
- 优点:概念简单,运维成本低,性能好,权限变更方便;
- 局限:无法应对复杂的动态权限场景,比如「工作时间才能访问」。
3.3.2 ABAC:灵活的动态权限方案
ABAC(Attribute-Based Access Control)基于属性做权限判断,通过组合用户属性、资源属性、环境属性,用规则表达式动态计算是否有权限。
- 属性维度:
- 用户属性:部门、职位、级别、地域等;
- 资源属性:数据所属部门、数据级别、创建人等;
- 环境属性:访问时间、IP 地址、设备风险等级等;
- 示例规则:用户.部门 == 订单.所属部门 AND 时间在工作日9:00-18:00 AND 用户.职位 in '经理', '主管'
- 微服务落地:可以用表达式引擎(如 SpEL、QLExpress)实现规则解析,适合多租户 SaaS 系统、复杂企业管理系统;
- 优点:灵活性极强,支持复杂动态场景,不需要给每个用户分配角色;
- 缺点:实现复杂度高,规则调试困难,性能比 RBAC 低。
3.3.3 ACL 与 TBAC:特定场景补充
- ACL :面向资源的权限模型,每个资源维护自己的访问列表,比如文件系统、共享文档、配置项的权限。适合资源型微服务,不适合业务系统的全局权限管理。
- TBAC :基于任务的临时授权,权限和业务流程绑定,比如审批流程中,审批人只有在审批任务期间才有对应数据的查看权限,任务结束权限自动回收。适合工作流引擎、审批系统。
3.4 典型授权漏洞与风险
授权环节是安全漏洞的高发区,以下是微服务中最常见的四类授权漏洞:
1. 过度授权
- 现象:客户端 Scope 配置过大,服务账号被授予管理员权限,用户角色权限溢出;
- 案例:某个内部工具服务的客户端 Scope 配置了*,可以访问所有业务接口;
- 危害:一旦该服务被攻破,攻击者可以访问全部系统资源,攻击面被无限放大。
2. 水平越权
- 现象:同级别用户之间可以互相访问对方的数据;
- 案例:用户 A 查询自己的订单接口是/order/{orderId},把 orderId 改成用户 B 的订单 ID,就能成功查询到别人的订单;
- 原因:只校验了用户登录状态,没有校验数据的归属权,也就是缺少数据权限控制。
3. 垂直越权
- 现象:低权限用户可以访问高权限功能;
- 案例:普通用户直接调用管理员接口/user/list,成功获取所有用户列表;
- 原因:接口只校验了 Token 有效性,没有校验功能权限,前端只是隐藏了菜单入口,后端没有真正拦截。
4. 诱导授权与职责未分离
- 诱导授权:OAuth2 授权页面被篡改,诱导用户授权给恶意客户端,常见于第三方开放平台;
- 职责未分离:同一个账号既可以发起转账,又可以审核转账,违反最小权限与职责分离原则。
四、访问控制体系:多层执行与纵深兜底
访问控制是授权规则的「执行官」,在请求链路的各个节点,根据主体的身份和权限,决定请求放行还是拦截。访问控制的核心原则是纵深防御、每层独立校验、不依赖上层、默认拒绝 。
4.1 四层访问控制防线总览
微服务全链路设置四道访问控制防线,从流量到数据层层拦截,单层失效不影响整体安全:
|-----------|-----------|----------------------------|----------------|------------------|
| 防线层级 | 管控对象 | 核心能力 | 执行位置 | 防护目标 |
| 接入层访问控制 | 外部入站流量 | IP 黑白名单、WAF 攻击拦截、限流熔断、端口收敛 | 防火墙、WAF、API 网关 | 清洗恶意流量,拦截网络层攻击 |
| 网关层访问控制 | 路由级请求 | 令牌有效性初验、路由级 Scope 校验、令牌黑名单 | API 网关 | 粗粒度身份校验,拦截明显非法请求 |
| 业务服务层访问控制 | 接口级、功能级请求 | 令牌验签、功能权限校验、操作权限判断 | 业务服务拦截器、AOP | 细粒度功能权限控制 |
| 数据访问层访问控制 | 数据级操作 | 行级数据过滤、敏感字段脱敏、多租户隔离 | ORM 拦截器、数据权限插件 | 数据级兜底,防止越权取数 |
4.2 逐层详解与落地实现
4.2.1 接入层访问控制:最外层的流量清洗
接入层是系统对外的第一道门,核心目标是把恶意流量直接拦在系统外面,减少后端的攻击面。
- 端口收敛 :只暴露 API 网关端口,所有业务服务端口不对外网开放,只能通过内网访问;
- HTTPS 加密 :全站 HTTPS,SSL 证书在网关终止,强制 HTTP 请求 301 跳转到 HTTPS;
- WAF 防护 :部署 Web 应用防火墙,拦截 SQL 注入、XSS 跨站脚本、命令注入、CSRF 等常见 Web 攻击;
- IP 访问控制 :配置 IP 黑白名单,封禁恶意 IP;内部系统开启地理围栏,禁止境外 IP 访问;
- 限流熔断 :针对 IP、接口、用户维度限流,抵御 CC 攻击、恶意爬虫,保护后端服务可用性。
4.2.2 网关层访问控制:统一入口的粗粒度校验
API 网关是所有请求的统一入口,适合做粗粒度的身份初验,把明显无效的请求直接拦在网关,不用转发到业务服务,减少后端压力。
核心能力与实现:
- 白名单路径放行 :登录接口、回调接口、公开接口等不需要认证的路径直接放行;
- 令牌有效性初验 :校验 JWT 的签名、过期时间,无效直接返回 401;
- 令牌黑名单校验 :主动吊销的 Token 存入 Redis 黑名单,网关层直接拦截;
- 路由级 Scope 校验 :每个路由配置对应的 Scope 要求,不满足直接返回 403;
- 身份信息透传 :解析后的用户 ID、Scope 等信息放入请求头,透传给下游业务服务。
Spring Cloud Gateway 核心实现代码:
java
@Component
public class TokenAuthGlobalFilter implements GlobalFilter, Ordered {
private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String BEARER_PREFIX = "Bearer ";
@Autowired
private ReactiveRedisTemplate<String, String> redisTemplate;
@Autowired
private JwtProperties jwtProperties;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
// 1. 白名单路径直接放行
if (PathMatcherUtil.match(path, jwtProperties.getWhitePaths())) {
return chain.filter(exchange);
}
// 2. 提取Bearer Token
String token = extractBearerToken(request);
if (StringUtils.isBlank(token)) {
return buildErrorResponse(exchange, HttpStatus.UNAUTHORIZED, "缺少访问令牌");
}
// 3. 校验令牌黑名单
Boolean isBlack = redisTemplate.hasKey(RedisKeyConstants.TOKEN_BLACKLIST + token).block();
if (Boolean.TRUE.equals(isBlack)) {
return buildErrorResponse(exchange, HttpStatus.UNAUTHORIZED, "令牌已被吊销");
}
// 4. 解析并校验令牌
Claims claims;
try {
claims = JwtUtil.parseToken(token, jwtProperties.getPublicKey());
} catch (ExpiredJwtException e) {
return buildErrorResponse(exchange, HttpStatus.UNAUTHORIZED, "令牌已过期");
} catch (Exception e) {
return buildErrorResponse(exchange, HttpStatus.UNAUTHORIZED, "令牌无效");
}
// 5. 路由级Scope校验
String requiredScope = RouteScopeConfig.getRequiredScope(path);
if (StringUtils.isNotBlank(requiredScope)) {
List<String> scopes = claims.get("scope", List.class);
if (CollectionUtils.isEmpty(scopes) || !scopes.contains(requiredScope)) {
return buildErrorResponse(exchange, HttpStatus.FORBIDDEN, "接口权限不足");
}
}
// 6. 身份信息透传到下游
ServerHttpRequest modifiedRequest = request.mutate()
.header("X-User-Id", claims.getSubject())
.header("X-Scopes", String.join(",", claims.get("scope", List.class)))
.header("X-Token", token)
.build();
return chain.filter(exchange.mutate().request(modifiedRequest).build());
}
private String extractBearerToken(ServerHttpRequest request) {
String bearerToken = request.getHeaders().getFirst(AUTHORIZATION_HEADER);
if (StringUtils.isNotBlank(bearerToken) && bearerToken.startsWith(BEARER_PREFIX)) {
return bearerToken.substring(BEARER_PREFIX.length());
}
return null;
}
private Mono<Void> buildErrorResponse(ServerWebExchange exchange, HttpStatus status, String message) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(status);
response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
String body = JSON.toJSONString(Result.error(status.value(), message));
DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(buffer));
}
@Override
public int getOrder() {
return -100;
}
}
注意:网关层的校验是粗粒度的补充,绝对不能替代业务服务层的细粒度校验。内网环境下,攻击者可以绕过网关直接访问业务服务,业务服务必须自己做鉴权。
4.2.3 业务服务层访问控制:细粒度权限执行
业务服务层是访问控制的核心执行层,负责接口级、功能级的细粒度权限校验,也是绝大多数业务权限的落地位置。
标准执行流程:
- 从请求头或 Token 中解析身份,区分是用户调用还是服务调用;
- 根据身份加载对应的权限集合(功能权限 + 数据权限);
- 校验当前接口 / 操作是否在权限范围内;
- 将权限上下文存入线程变量,供业务逻辑和数据层使用;
- 请求结束后清理上下文。
业务服务权限拦截器实现(区分用户与服务双身份):
java
@Component
public class BizPermissionInterceptor extends HandlerInterceptorAdapter {
@Autowired
private PermissionService permissionService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 如果是方法级注解,优先取注解配置
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
RequiresPermission annotation = handlerMethod.getMethodAnnotation(RequiresPermission.class);
if (annotation == null) {
// 没有注解说明不需要权限校验,放行
return true;
}
}
// 1. 获取身份:优先从请求头取(网关透传),也可以自己解析Token
String userId = request.getHeader("X-User-Id");
String serviceId = request.getHeader("X-Service-Id");
String scopes = request.getHeader("X-Scopes");
// 2. 身份合法性校验
if (StringUtils.isBlank(userId) && StringUtils.isBlank(serviceId)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
writeErrorResponse(response, "身份信息缺失");
return false;
}
// 3. 用户身份调用:加载用户完整权限
if (StringUtils.isNotBlank(userId)) {
UserPermission userPermission = permissionService.getUserPermission(userId);
if (userPermission == null) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
writeErrorResponse(response, "用户权限不存在");
return false;
}
// 校验接口功能权限
String requestUri = request.getRequestURI();
String method = request.getMethod();
if (!userPermission.hasApiPermission(requestUri, method)) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
writeErrorResponse(response, "接口权限不足");
return false;
}
// 权限上下文存入线程变量
PermissionContextHolder.setUserPermission(userPermission);
}
// 4. 服务身份调用:校验服务级Scope
else {
List<String> scopeList = Arrays.asList(scopes.split(","));
String requiredServiceScope = ServiceScopeConfig.getRequiredScope(request.getRequestURI());
if (StringUtils.isNotBlank(requiredServiceScope) && !scopeList.contains(requiredServiceScope)) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
writeErrorResponse(response, "服务调用权限不足");
return false;
}
// 服务身份上下文
ServiceIdentity serviceIdentity = new ServiceIdentity(serviceId, scopeList);
PermissionContextHolder.setServiceIdentity(serviceIdentity);
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
PermissionContextHolder.clear();
}
private void writeErrorResponse(HttpServletResponse response, String message) throws IOException {
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
response.getWriter().write(JSON.toJSONString(Result.error(403, message)));
}
}
除了拦截器,还可以通过自定义注解 + AOP 实现更灵活的方法级权限控制:
java
// 自定义权限注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermission {
// 权限标识
String value();
// 数据权限范围
DataScope dataScope() default DataScope.OWN;
}
// AOP切面实现
@Aspect
@Component
public class PermissionAspect {
@Around("@annotation(requiresPermission)")
public Object around(ProceedingJoinPoint point, RequiresPermission requiresPermission) throws Throwable {
UserPermission permission = PermissionContextHolder.getUserPermission();
if (permission == null) {
throw new BizException("未获取到权限上下文");
}
// 校验功能权限
if (!permission.hasPermission(requiresPermission.value())) {
throw new BizException("权限不足");
}
// 校验数据权限,可在数据层进一步处理
permission.setCurrentDataScope(requiresPermission.dataScope());
return point.proceed();
}
}
4.2.4 数据访问层访问控制:最后一道安全防线
数据层是安全的最后一道防线,也是最容易被忽略的一层。一旦接口层被绕过、出现 SQL 注入漏洞,或者内部人员直连数据库,数据就完全裸奔。
数据层访问控制的核心目标是:即便上层鉴权全部被绕过,访问数据库时依然做数据隔离,接口放行也拿不到不属于自己的数据 。
三大核心能力:
- 行级数据过滤 :自动在 SQL 中拼接数据权限条件,比如用户只能查自己创建的数据,部门经理只能查本部门数据;
- 字段脱敏 :敏感字段(手机号、身份证、密码、地址)在查询时自动脱敏,返回脱敏后的结果;
- 多租户隔离 :多租户场景下自动拼接租户 ID 条件,防止跨租户数据泄露。
MyBatis 行级数据过滤拦截器实现:
java
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
@Component
public class DataPermissionInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
BoundSql boundSql = statementHandler.getBoundSql();
String originalSql = boundSql.getSql();
// 获取当前权限上下文
UserPermission permission = PermissionContextHolder.getUserPermission();
// 没有上下文或者是管理员,不过滤
if (permission == null || permission.isAdmin()) {
return invocation.proceed();
}
// 获取Mapper方法上的数据权限注解
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
DataScope dataScope = getDataScopeAnnotation(mappedStatement);
if (dataScope == null) {
return invocation.proceed();
}
// 拼接数据过滤条件
String newSql = appendDataScopeCondition(originalSql, permission, dataScope);
// 修改SQL
metaObject.setValue("delegate.boundSql.sql", newSql);
return invocation.proceed();
}
private String appendDataScopeCondition(String sql, UserPermission permission, DataScope dataScope) {
switch (dataScope.value()) {
case OWN:
// 仅本人数据:拼接创建人条件
return sql + " AND create_user = '" + permission.getUserId() + "'";
case DEPT:
// 本部门数据:拼接部门条件
return sql + " AND dept_id = " + permission.getDeptId();
case DEPT_AND_CHILD:
// 本部门及下级部门
String deptIds = StringUtils.join(permission.getDeptAndChildIds(), ",");
return sql + " AND dept_id in (" + deptIds + ")";
case ALL:
return sql;
default:
return sql;
}
}
private DataScope getDataScopeAnnotation(MappedStatement mappedStatement) {
try {
String id = mappedStatement.getId();
String className = id.substring(0, id.lastIndexOf("."));
String methodName = id.substring(id.lastIndexOf(".") + 1);
Class<?> mapperClass = Class.forName(className);
Method[] methods = mapperClass.getMethods();
for (Method method : methods) {
if (method.getName().equals(methodName) && method.isAnnotationPresent(DataScope.class)) {
return method.getAnnotation(DataScope.class);
}
}
} catch (ClassNotFoundException e) {
log.warn("获取DataScope注解失败", e);
}
return null;
}
}
敏感字段序列化脱敏实现:
java
/**
* 敏感信息序列化器
*/
public class SensitiveInfoSerializer extends JsonSerializer<String> {
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (StringUtils.isBlank(value)) {
gen.writeString(StringUtils.EMPTY);
return;
}
// 管理员可以看明文
UserPermission permission = PermissionContextHolder.getUserPermission();
if (permission != null && permission.isAdmin()) {
gen.writeString(value);
return;
}
// 手机号脱敏
if (value.length() == 11) {
gen.writeString(value.substring(0, 3) + "****" + value.substring(7));
return;
}
// 身份证号脱敏
if (value.length() == 18) {
gen.writeString(value.substring(0, 6) + "********" + value.substring(14));
return;
}
// 默认脱敏
gen.writeString("***");
}
}
4.3 访问控制失效的典型场景
- 只校验身份,不校验权限 :拦截器只判断 Token 是否有效,不判断用户有没有对应接口的权限,登录成功就能访问所有功能,这是最普遍的低级错误。
- 前端传参控制权限 :后端完全信任前端传的角色、权限标识,比如前端传role=admin就认为是管理员,导致任意用户都可以越权。
- 权限硬编码 :权限判断写死在代码里,比如if(userId == 1) return true;,修改权限需要改代码发版,维护成本极高。
- 忽略服务间调用 :只做了用户侧的访问控制,服务之间调用完全不鉴权,导致一个服务被攻破就全内网裸奔。
- 数据层无兜底 :只在接口层做权限判断,数据层没有任何隔离,攻击者通过 SQL 注入、绕过接口就能拿到全量数据。
五、工程落地:体系化实践与避坑指南
5.1 架构边界与职责划分
微服务安全体系要做到「认证集中、权限集中、执行分布」,各个组件职责清晰,避免越界:
|--------|----------------------------------------------------|----------------|
| 组件 | 核心职责 | 不负责 |
| 统一认证中心 | 用户身份认证、登录会话管理、客户端注册与管理、令牌签发与吊销、客户端 Scope 授权、服务身份颁发 | 业务细粒度权限、数据权限 |
| 权限中心 | 用户角色权限管理、服务权限管理、权限分配与变更、权限查询接口 | 令牌管理、登录认证 |
| API 网关 | 流量清洗、WAF、限流、令牌初验、路由级 Scope 校验、请求转发 | 业务细粒度权限校验、数据权限 |
| 业务服务 | 令牌验签、接口级访问控制、业务逻辑内权限判断、数据层访问控制 | 认证逻辑、令牌签发 |
5.2 两条核心链路完整时序
链路一:用户 Web 访问全链路

链路二:服务间(异步)调用全链路(Kafka 场景)

5.3 落地避坑十大红线
- JWT 载荷不存权限 :只存用户 ID、服务 ID 等身份标识,绝对不存完整权限列表。一是 JWT 可解码不安全,二是权限变更无法实时生效。
- 用户 Token 不透传服务调用 :服务之间必须用独立的服务身份 Token,不能直接转发用户 Token。用户 Token 是给人用的,服务有自己的身份和权限边界。
- 网关校验不替代业务校验 :网关只做粗粒度初验,业务服务必须做二次细粒度校验。内网环境下网关可以被绕过,业务层鉴权不能省。
- 异步场景必须用服务身份 :消息队列、定时任务、批处理等无用户上下文的场景,禁止伪造用户身份,必须使用独立的服务账号。
- 数据层兜底不能省 :无论上层做了多少权限校验,数据层的行级过滤、租户隔离必须做。这是最后一道防线,也是内部数据安全的底线。
- 密钥禁止硬编码 :client_secret、AppKey、私钥等敏感信息,不能明文写在代码、配置文件里,必须使用密钥管理系统(如 Vault)、配置中心加密存储。
- 回调地址严格白名单 :OAuth2 授权服务器必须严格校验回调地址,禁止使用通配符,防止重定向劫持攻击。
- 服务账号最小权限 :每个服务的账号只分配需要的最小 Scope,遵循最小权限原则,不要给全局权限。
- 全链路 HTTPS 加密 :不仅外网要 HTTPS,内网服务之间调用也建议开启 HTTPS,防止内网流量嗅探、令牌窃取。
- 审计日志不可少 :所有登录、权限变更、权限拒绝、越权尝试都要记录审计日志,不可篡改,便于追溯和应急响应。
5.4 安全审计与可观测
安全体系不是搭完就完事,必须有审计和可观测能力,才能发现异常、追溯问题、持续优化。
审计日志分类:
- 认证审计:登录成功 / 失败、登出、令牌签发 / 刷新 / 吊销、MFA 验证结果;
- 授权审计:角色变更、权限分配、权限回收、临时授权申请与审批;
- 访问审计:接口访问记录、权限拒绝事件、越权尝试、跨服务调用链路;
- 数据审计:敏感数据访问、批量数据导出、核心数据修改。
可观测建设:
- 建立安全监控大盘,监控异常登录地点、高频失败登录、高频权限拒绝、越权尝试告警;
- 全链路追踪集成身份信息,每个请求的调用链都能看到用户身份、服务身份;
- 定期安全审计,排查过度授权、权限溢出、弱密码等风险。
六、体系化总结
6.1 知识框架收口
微服务安全是一个体系工程,不是靠一个框架、一个协议就能解决的。我们可以用「一横一纵」来总结整个知识体系:
- 一横 :五层防御架构是横向的物理骨架,从网关到数据层层设防;
- 一纵 :身份认证、授权、访问控制是纵向的核心能力,贯穿每一层,层层独立校验、层层兜底。
前篇的五层防御文章,聚焦的是「统一认证层」这个单点的协议与实现;本文则把三大能力纵向打通,覆盖用户与服务两类主体,构建了完整的安全闭环。
核心逻辑永远不变:先认证身份,再匹配权限,最后执行访问控制 。
6.2 核心复习要点
- 两类身份主体 :用户侧 SSO+OIDC + 多因素认证;服务侧客户端凭证、HMAC、非对称、mTLS 四种方案选型;
- 两级授权架构 :客户端级 Scope 授权(认证中心)+ 业务级细粒度授权(权限中心);
- 四种权限模型 :RBAC、ABAC、ACL、TBAC 的适用场景与优劣对比;
- 四层访问控制 :接入层、网关层、业务层、数据层,层层兜底,数据层是最后防线;
- 十大落地红线 :避开最常见的安全坑,少走弯路;
- 两条核心链路 :用户访问、服务异步调用的完整安全流转流程。
微服务安全没有银弹,也没有一劳永逸的方案。它需要架构师从体系层面设计,开发人员在每一层落地,运维人员持续运营优化。
📚 我的技术博客导航:点击进入一站式查看所有干货