目录一、先把Spring Security、JWT 与 OAuth 2.0放回各自的位置一一句给系统加一套权限里混着的三类需求二执行框架、凭证载体与发放协议三详细展开二、Spring Security 的骨架过滤器链、认证三元组与安全上下文一一次请求穿过的过滤器链二认证三元组管理器、认证器与用户明细服务三安全上下文的线程边界四声明式配置的书写方式三、认证落地从口令存储到登录防线一会话与令牌状态放在哪代价就在哪二口令存储慢哈希与委托编码器三登录接口按被攻击来设计四、JWT一段能离线验证的签名快照一三段式结构编码可读签名防改二签名算法看泄露半径不看算法名气三载荷设计公开快照只放不变的最小集四资源服务的完整校验清单五、令牌生命周期签发、刷新、撤销与换钥一双令牌模型快与可控各归其位二刷新令牌的轮换与重用检测三注销三种方案与一个推荐组合四密钥轮换与密钥集端点六、OAuth 2.0 与 OIDC把授权做成协议一四个角色与两类客户端二授权码模式与校验码三模式选型两问一答四OIDC认证与授权各拿各的令牌五Spring 生态里的三种角色落地1、资源服务器2、客户端3、授权服务器七、接口鉴权从 URL 规则到方法注解一三道闸按粒度递进二URL 级规则的两条纪律三方法级注解表达式、判定时机与代理边界1、前置判定与后置判定2、表达式能用的三类事实3、代理边界注解会静默失效八、细粒度权限模型、数据与生效时刻一RBAC角色是配置权限点才是代码二数据权限行级过滤与归属校验1、行级过滤的三种实现1.1 业务代码手写条件1.2 访问层统一改写1.3 独立策略引擎2、写操作的归属校验三权限变更的生效链路九、工程化防线存放、架构与门禁一令牌的存放与传输二网关统一鉴权与服务内二次校验三上线前的十六条门禁结语参考资料干货分享感谢您的阅读一个请求带着一枚签名合法的令牌抵达接口这件事只证明了一点它出示的凭证没有被篡改。它是谁签的、给谁用的、现在还算不算数、允许调这个接口吗、允许看这行数据吗、它背后的授权上周被撤销了吗——这六个问题里的任何一个答错都是一次真实的越权。这次我们把 Spring Security、JWT 与 OAuth 2.0 这三个常被并列却各司其职的名词放回各自的位置沿着骨架、认证、令牌、协议、接口鉴权、细粒度权限、工程防线七段推进给出一套能被评审、能被脚本验证的权限工程方法。一、先把Spring Security、JWT 与 OAuth 2.0放回各自的位置一一句给系统加一套权限里混着的三类需求权限体系的设计事故多数不是从写错代码开始的而是从需求评审上那句给系统加一套权限开始的。这句话至少压着三类协议不同、载体不同、失效形态也完全不同的需求。第一类是认证回答你是谁。输入是口令、验证码或某个外部身份系统的凭证输出是一个可信的主体标识。它的失效形态是冒用、爆破与凭证泄露。第二类是授权回答你能做什么。输入是主体、资源与操作三元组输出是放行或拒绝。它的失效形态是越权——垂直越权拿到更高的操作权水平越权碰到别人的数据。第三类是委托授权回答第三方能替你做什么。一个日程应用想读取你的邮箱日历它不该拿到你的邮箱口令而应拿到一张由你明示同意、范围受限、可以单独撤销的令牌。它的失效形态是授权范围过宽与令牌外泄。三类需求分开之后选型讨论才有落点认证由 Spring Security 的认证器体系承接授权由 URL 规则、方法注解与数据层过滤承接委托授权由 OAuth 2.0 与 OIDC 承接。最常见的设计事故不是某一段做错了而是拿其中一段的工具去硬扛另一段的需求——比如拿 OAuth 的令牌格式去顶替数据权限模型或者以为引入了 JWT 就等于有了权限体系。二执行框架、凭证载体与发放协议把三个名词各自解决什么、不解决什么的边界画清楚是全文所有后续判断的基础。Spring Security 是嵌在请求路径上的执行框架。它规定检查发生在哪里过滤器链的哪一站、身份如何被确认认证器体系、放行与拒绝如何裁决授权组件。但它不规定凭证长什么样也不规定凭证从哪里来。JWT 是一种自包含的凭证载体。它把主体、有效期与签名装进一段可以离线验证的字符串让服务端不必存储会话就能校验身份。但它不负责发放与收回更不等于一套权限模型——一枚 JWT 里放什么、放多久、怎么作废全部是使用者自己的设计责任。OAuth 2.0 是一套授权发放协议规定用户如何向客户端授权、令牌经由哪些信道流转OIDC 在它之上补出身份层。但协议不规定令牌内部格式也不管你的服务内部怎么做方法级鉴权。三者是执行框架 × 凭证载体 × 发放协议的正交组合而不是三选一。单体应用可能只需要第一个前后端分离加多服务需要前两个开放平台三个都要。把这个组合关系说清楚是这套体系设计里最先要过的一道关。三详细展开一套完整的权限体系最终要能回答六个问题你是谁这一次请求凭什么信这个接口你能不能调这行数据你能不能看第三方替你做到哪一步授权变了多快生效。六个问题里后三个最少被写进需求文档却贡献了最贵的事故。接口级越权在测试环境用扫描器就能查出来数据级越权只有在换一个用户身份重放同一请求时才显形功能测试全绿也拦不住它而权限改了、旧令牌还在放行这类问题往往在离职、转岗、封禁这些运营动作之后才暴露——那时它已经不是缺陷而是事故。本文按骨架、认证、令牌、协议、接口鉴权、细粒度、防线的次序把六个问题逐一落到可评审的判据上。二、Spring Security 的骨架过滤器链、认证三元组与安全上下文一一次请求穿过的过滤器链Spring Security 的全部行为都发生在一条过滤器链上。请求从 Servlet 容器进来先经过上下文装载的过滤器恢复或新建安全上下文再经过跨站请求防护然后到达认证类过滤器——表单登录场景里它解析登录表单无状态令牌场景里它从请求头取出令牌、验签并构造已认证主体。之后是异常翻译过滤器把认证异常与授权异常翻成 401 与 403最后是授权判定过滤器URL 级规则在这里求值。全部通过请求才进入 DispatcherServlet 与业务代码。这条链有两个工程含义比记住每个过滤器的名字重要得多。其一链上位置即语义自定义过滤器必须显式声明加在哪一站之前或之后加错位置要么拿不到上下文要么被前面的站点短路。其二链上任何一站抛出的异常都不会进入业务侧的全局异常处理器——它们发生在 DispatcherServlet 之前。想统一 401/403 的响应格式要配置的是认证入口点AuthenticationEntryPoint与拒绝处理器AccessDeniedHandler而不是给控制器加异常通知。链条还给出了排障的第一条线索401 产生在认证类过滤器含义是不知道你是谁正确动作是引导重新认证或刷新令牌403 产生在授权判定含义是知道你是谁但规则不放行正确动作是核对角色与权限点配置。把这两个状态码用混的系统客户端只能靠猜来决定重新登录还是提示无权限。二认证三元组管理器、认证器与用户明细服务认证类过滤器只做一件事收集凭证。真正的身份确认由三个组件协作完成——认证管理器AuthenticationManager接过未认证的凭证对象按凭证类型分派给能处理它的认证器AuthenticationProvider数据库口令场景下认证器再调用用户明细服务UserDetailsService装载用户交由口令编码器比对口令比对通过后返回一个已认证的凭证对象其中原文口令已被抹除、权限集已经填好最后写入安全上下文。这套分派结构的价值在扩展点的位置上新增一种认证方式——短信验证码、扫码、企业单点登录——等于新增一个认证器过滤器与管理器都不动。反过来说把新认证方式的逻辑塞进过滤器里堆分支是这套体系里最典型的反模式它让每种认证方式都要重新处理一遍成功后做什么、失败后做什么而这些恰恰是框架已经做对了的部分。三安全上下文的线程边界认证结果放在 SecurityContextHolder 里默认策略是线程私有存储。这带来一个随手可得的便利控制器、服务层、审计切面请求线程上的任何位置都能取到当前用户。也带来两个必须直面的边界。第一个边界是清理。请求结束时上下文必须清除因为线程要还给线程池——不清除下一个复用这条线程的请求就会捡到上一个用户。框架的过滤器会做这件事但绕开框架自建入口比如某些内部调度入口的代码要自己负责。第二个边界是跨线程。异步注解、并行流、自建线程池、CompletableFuture 的默认执行器都会离开请求线程——线程私有存储里的上下文不会跟过去当前用户悄悄变成空。更隐蔽的形态是拷贝了上下文却不清理让池中线程带着上一个用户的身份运行下一个任务。把身份带过线程边界的方式按推荐度排序显式传参最直白也最可测——提交任务前把用户标识取出来当普通参数传进去其次是用安全上下文感知的执行器包装线程池提交时快照、执行时恢复、结束时清理可继承的线程私有变量模式只对新建线程生效对池化线程会造成身份错乱线上服务基本不可用。评审判据只有一句凡在非请求线程里读当前用户的代码必须能指出身份从哪条路径传进来。四声明式配置的书写方式骨架的最后一块是配置。现代 Spring Security 用 Lambda 风格声明一条过滤器链一段典型的无状态令牌配置如下Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(AbstractHttpConfigurer::disable) // 无浏览器会话跨站防护按需关闭 .sessionManagement(s - s.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth - auth .requestMatchers(/api/public/**, /actuator/health).permitAll() .requestMatchers(/api/admin/**).hasAuthority(SCOPE_admin) .anyRequest().authenticated()) // 默认拒绝收底 .oauth2ResourceServer(rs - rs.jwt(Customizer.withDefaults())) .exceptionHandling(e - e .authenticationEntryPoint(unifiedEntryPoint) // 统一 401 响应 .accessDeniedHandler(unifiedDeniedHandler)); // 统一 403 响应 return http.build(); }这段配置里每一行都对应前文的一个论点无状态会话策略对应令牌方案anyRequest().authenticated()对应默认拒绝资源服务器一行对应第四章的整套令牌校验异常处理两行对应 401/403 的统一响应。配置不是模板是决策的落盘。三、认证落地从口令存储到登录防线一会话与令牌状态放在哪代价就在哪在写第一行登录代码之前要先做一个决定认证状态放在服务端会话还是编码进凭证本体令牌。两者不是新旧关系而是同一道选择题的两个答案各自的代价清清楚楚。会话方案里服务端存状态注销即时生效删掉会话就行但多实例需要会话共享或粘性路由令牌方案里任意实例可独立验签、零存储访问水平扩展与跨服务校验几乎免费但注销从一个删除操作变成了一个设计难题——令牌签出去就收不回来在到期之前它天然有效。选型判据是三个问题有几个服务要验证身份——只有一个会话最省事一群服务令牌免去会话共享。注销要多快生效——秒级强制下线是硬需求的场景资金操作、后台管理纯无状态令牌撑不住必须配撤销机制。凭证要不要跨域跨端——开放接口、移动端、小程序共用一套身份时令牌是唯一现实解。本文后续按无状态令牌为主、以撤销机制补足注销的主线展开这是多服务架构下最常见的落点。二口令存储慢哈希与委托编码器口令存储的第一原则数据库里永远不出现可逆的口令。正确形态是代价可调的慢哈希——bcrypt、argon2 这一类。它们与通用哈希的区别是设计目标相反通用哈希追求快口令哈希刻意慢。攻击者拿到库之后做的是离线穷举每次哈希十毫秒的代价对登录用户无感对要试几十亿次的攻击者是灾难。慢哈希自带随机盐同一口令两次编码结果不同彩虹表失效代价因子还能随硬件升级调大。Spring Security 的委托式口令编码器把算法可升级做成了机制库里每条口令带着算法前缀如{bcrypt}、{argon2}校验时按前缀路由到对应实现写入时统一用当前默认算法。算法升级于是有了一条不打扰任何用户的路径改一行默认算法配置存量用户登录成功的那一刻用新算法重新编码并回写观察前缀分布存量清零后移除老实现。全程没有一次强制改密也没有停机窗口。反过来用 MD5 加固定盐、用可逆加密以便找回密码的系统在第一次拖库时就会把全部用户的通用口令送出去——口令复用的现实让这类泄露的伤害范围远超一个系统。三登录接口按被攻击来设计登录是全站唯一一个攻击者可以合法反复调用的写接口它的设计基准不是正常用户怎么用而是攻击者会怎么打。五道防线依次是第一不可枚举。账号不存在与口令错误必须返回同一句文案、相近的响应耗时否则登录接口就是一台免费的账号存在性查询器撞库攻击的第一步就是拿它筛出有效账号。第二失败计数与锁定。按账号计数阈值内递增延迟阈值外临时锁定并通知用户计数必须放在集中存储——放在实例内存里双实例部署下爆破流量在两台机器上各数各的谁也数不满阈值。第三人机验证。失败次数过阈值后升级出题而不是每次登录都出题打扰全体用户对成规模的撞库流量还要结合设备指纹与地理特征。第四双维度限流。按来源地址限总量防扫号按账号限频次防定点爆破缺一个维度另一个就会被绕开。第五留痕与告警。成功与失败都记录来源、设备与结果失败风暴触发告警异地登录与新设备登录走通知或二次确认。这五道防线的共同点是全部可以脚本化验证枚举脚本、爆破脚本、并发脚本各跑一轮比任何一轮人工评审都可靠。登录安全不是加了验证码一句话而是五道防线各自有阈值、有存储位置、有验证脚本。四、JWT一段能离线验证的签名快照一三段式结构编码可读签名防改一枚 JWT 是三段用点号连接的字符串头部声明签名算法与密钥编号载荷装着签发方iss、主体sub、受众aud、过期时刻exp、签发时刻iat、令牌编号jti与授权摘要签名对前两段整体签名。前两段只是地址安全的 Base64Url 编码——是编码不是加密任何拿到令牌的人都能解码阅读。签名保证的是完整性与来源改动任何一个字节验签即失败。但要把它不保证的两件事同样说清楚它不保证机密性载荷全世界可读也拦不住整枚令牌被复制——谁拿到令牌谁就是令牌的主人这是后文全部生命周期治理的出发点。自包含的真正含义是验证方拿到令牌本体加签发方公钥即可离线校验不用回源查询。这换来了多服务架构下的横向扩展代价是签发方失去了逐枚收回的能力。二签名算法看泄露半径不看算法名气对称签名HS256 一族用同一把密钥签发与验证速度快、部署简单非对称签名RS256、ES256 一族私钥签发、公钥验证。选型的关键不是算法强度对比而是信任模型验证方拿到的那份材料能不能用来伪造令牌。对称方案里每个验证方都持有那把能签发任意身份的密钥泄露半径等于所有持钥服务中最弱的那一个——十个微服务共享一把对称密钥等于把签发权复制了十份。非对称方案里验证方只有公钥被攻破也伪造不了令牌泄露半径收敛到签发服务一处重点设防一处即可。选型规则一条验证方数量大于一或未来会大于一直接用非对称。配套两条硬纪律验证端写死算法白名单拒绝令牌头部自报的算法取值——历史上多个 JWT 库的经典漏洞就是攻击者把算法改成无签名或把非对称换成对称、用公开的公钥当共享密钥伪造签名密钥不进代码与配置文件进凭据管理服务进程启动时拉取。三载荷设计公开快照只放不变的最小集载荷是一份会被到处转发、任何人可读、签发之后改不了的公开快照。三档取舍如下该放的是校验与路由所需——主体标识内部编号而非手机号、签发方、受众、过期与签发时刻、令牌编号、会话版本号、租户标识。慎放的是授权摘要——粗粒度范围如order:read与少量角色摘要可以放供网关粗筛细粒度权限点清单不要放一是体积失控二是权限一变快照全过时。禁放的是敏感与易变数据——手机号、身份证号、姓名明文、口令密钥、余额额度放进载荷等于贴在公告栏上。配一条体积纪律整枚令牌控制在一千字节以内。它出现在每个请求头里、被网关与每一跳服务解析、被写进访问日志。把权限清单塞进令牌的团队最终都会遇到请求头超限与改了权限不生效这两个必然结果。正确分工是令牌里放你是谁加粗粒度范围细粒度权限由资源服务按主体标识实时查询或短缓存获取。四资源服务的完整校验清单资源服务收到令牌后的校验有七步次序固定格式解析、算法白名单、验签名按密钥编号选公钥、时间窗过期与生效留几十秒时钟偏移、签发方、受众、映射权限。只验签名的实现等于把其余五道检查让给了运气。三个最常被跳过的检查各自对应一类真实事故。跳过受众检查为服务甲签发的令牌拿到服务乙也能用一个低敏服务的令牌打开了高敏服务的门。跳过算法白名单前文说过的算法混淆伪造。时间窗不留偏移签发机与验证机时钟相差数秒刚签出的令牌被判尚未生效线上出现随机 401这类问题因为难以复现而格外消耗排查时间。工程落点是少手写。Spring Security 的资源服务器组件把七步做成声明式配置——给出签发方地址与受众名框架拉取密钥集并执行全套校验Bean public JwtDecoder jwtDecoder() { NimbusJwtDecoder decoder JwtDecoders.fromIssuerLocation(issuerUri); // 拉取密钥集并锁定签发方 OAuth2TokenValidatorJwt withAudience new DelegatingOAuth2TokenValidator( JwtValidators.createDefaultWithIssuer(issuerUri), // 签名、时间窗、签发方 new JwtClaimValidatorListString(AUD, aud - aud ! null aud.contains(order-service))); // 受众必须包含本服务 decoder.setJwtValidator(withAudience); return decoder; }自己手写解析的团队几乎必然漏掉受众与算法白名单。能用框架的地方不要手写。五、令牌生命周期签发、刷新、撤销与换钥一双令牌模型快与可控各归其位无状态令牌的快与注销需求的可控是一对天然矛盾双令牌模型把它拆到两枚令牌上解决。访问令牌短命分钟级、无状态、随每个请求到处飞资源服务本地验签即可放行刷新令牌长命天级、有状态、只出现在与认证服务的换发请求里服务端哈希落库、绑定设备、可随时吊销。访问令牌过期后客户端拿刷新令牌换一对新令牌用户无感。这个结构的本质是风险再分配把不可收回限制在一个分钟级的窗口里把可控性集中到一个只见一个端点、可以从容设防的长期凭证上。两枚令牌的有效期是两个独立的决策变量——访问令牌看业务敏感度资金类五分钟一般业务十五到三十分钟刷新令牌看重新登录的容忍度移动端常给十四到三十天配合活跃续期。二刷新令牌的轮换与重用检测刷新令牌本质是一份长期口令值得两道额外防线。第一道是轮换每次刷新都签发新的刷新令牌旧的立即作废——每枚刷新令牌只能用一次形成一条家族链。第二道是重用检测一枚已经被轮换掉的刷新令牌再次到达认证服务说明它同时存在于两个地方——真用户手里一份、攻击者手里一份至于哪份是偷来的无从判断所以正确动作是吊销整个家族让双方都掉线、真用户重新登录。为什么重用几乎必然意味着泄露正常客户端每次刷新后都会立刻改用新令牌旧令牌不会再出现在任何请求里。轮换让被偷这件事自己暴露自己——这是无状态体系里少有的、能主动发现凭证泄露的机制值得在任何生产系统里默认开启。落库纪律同样明确刷新令牌只存哈希每条记录挂家族标识、设备信息与上次使用时刻吊销与审计都以家族为单位。三注销三种方案与一个推荐组合用户点了退出、管理员点了封禁然后呢——无状态令牌收不回来注销只能靠让验证方不再认它来实现。三种方案的取舍如下。令牌黑名单注销时把令牌编号写入集中缓存有效期切齐令牌剩余寿命验证时先查名单。代价是每次校验多一次缓存查询好处是黑名单只存被注销的少数令牌体量小。会话版本号给用户一个版本号存进库或缓存令牌携带签发时的版本改口令、封禁、强制下线时版本加一所有旧令牌一次性全部失效。粒度是用户而不是单枚令牌一键踢所有设备下线的场景必备。纯短有效期把访问令牌压到分钟级不做任何服务端状态靠自然过期收敛——注销后仍有分钟级放行窗口只适合敏感度低的读场景。推荐组合是三层递进而不是三选一分钟级访问令牌打底把风险窗口先压小会话版本号兜住改口令、封禁、强制下线这三类必须即时的注销黑名单只处理单枚令牌被举报泄露的精确打击。评审判据一句话说我们用了无状态令牌的系统必须能回答封禁一个账号后多少秒内全部接口拒绝它——答不出这个数字的系统注销功能只存在于界面上。四密钥轮换与密钥集端点签名密钥要能换而且要能在不打扰任何用户的前提下换。轮换不是换锁而是新旧两把钥匙有秩序的交接引入新钥后进入双钥并存窗口密钥集端点JWKS同时发布两把公钥新签发全部用新钥存量令牌按头部的密钥编号kid找旧钥验证等最长寿命的存量令牌自然过期旧钥才能安全下线。验证方的缓存策略有一个细节决定轮换是否无感遇到未知密钥编号时先刷新一次密钥集再判失败。做对了这一条轮换对全部验证方完全透明。反面是没有轮换能力的系统密钥一旦泄露只剩全站令牌立刻作废、所有用户掉线一个选项。轮换能力必须在和平时期建好并演练过——出事之后再搭窗口期里每一分钟都在裸奔。六、OAuth 2.0 与 OIDC把授权做成协议一四个角色与两类客户端OAuth 2.0 的全部内容就是规定四方之间谁可以把什么凭证交给谁。资源拥有者通常是终端用户是授权的唯一来源客户端是想代用户行事的应用授权服务器认证用户、征得同意、签发令牌资源服务器持有数据、校验令牌、执行范围。协议最关键的一条底线是用户的口令只交给授权服务器永远不经过客户端——这正是它相对把口令给第三方这种上古做法的全部进步所在。客户端分两类分界决定了它能用哪种流程机密客户端有服务端环境、能保管密钥典型是传统 Web 应用的后端公共客户端保管不了任何秘密——单页应用的代码在浏览器里、移动应用的包可以被反编译写进去的密钥等于公开。公共客户端的防线由下一节的校验码机制补上。二授权码模式与校验码授权码模式是有用户参与场景的标准流程它的设计核心是两条信道的分离授权码经由用户侧的前端信道浏览器重定向返回客户端令牌经由客户端与授权服务器之间的后端信道换取。前端信道可能被浏览历史、跳转日志、恶意插件窥见所以只让一次性、分钟级有效的授权码经过它令牌永远走后端信道。校验码机制PKCE再把两条信道锁成一对客户端在发起授权前生成随机校验原文把它的散列作为质询带上授权请求换令牌时提交原文授权服务器验证散列匹配。截到授权码的人没有原文换不出令牌原文从不出现在前端信道散列又不可逆推。这一机制没有任何额外部署成本当前的最佳实践是所有客户端一律启用不再区分机密与公共——OAuth 2.1 草案已把它写成授权码模式的必选项。三模式选型两问一答授权模式的选择在今天已经大幅简化两个问题定一个模式。第一问有没有终端用户参与没有——服务与服务之间的机器调用用客户端凭证模式客户端以自身身份取令牌令牌代表服务而非任何用户范围按最小授权收紧。有用户进入第二问客户端所在环境能不能弹出浏览器能——网页、单页应用、移动端一律授权码加校验码不能——电视、命令行、IoT 设备用设备码模式设备上显示短码用户到手机上完成登录与授权。两个模式不再是选项密码模式让用户把口令交给第三方客户端摧毁了协议存在的意义多因子与单点登录也随之全部失效新一代规范已将其删除隐式模式把令牌直接暴露在前端信道已由授权码加校验码全面替代。在新系统里看到这两个模式应当按技术债处理而不是按备选项讨论。四OIDC认证与授权各拿各的令牌OAuth 2.0 只发能做什么的通行证它没有定义你是谁的标准表达——OIDC 在其上补出这个身份层。核心增量是身份令牌ID Token规范规定它必须是 JWT受众是客户端自己客户端验签后读取这个用户是谁、什么时候认证的。访问令牌的受众则是资源服务器客户端只负责转交、不应解析它的内容。两枚令牌不可互换这是 OIDC 场景里最高频的两类误用拿访问令牌当登录凭证——资源服务器的令牌证明不了用户此刻在场重放一枚偷来的访问令牌就能冒充登录拿身份令牌去调接口——受众是客户端的令牌资源服务器根本不该接受。企业内的扫码登录第三方系统用统一账号登录内部平台本质都是 OIDC 的客户端角色落地。五Spring 生态里的三种角色落地同一套 Spring Security按系统扮演的协议角色装配三套不同的组件。1、资源服务器多数业务服务的角色。引入spring-boot-starter-oauth2-resource-server配置签发方地址与受众名框架完成密钥集拉取与七步校验范围自动映射为SCOPE_前缀的权限进入鉴权体系。这是三种角色里成本最低、也最应该标准化的一种。2、客户端需要代用户调用外部接口、或把登录委托给统一身份平台的系统。引入spring-boot-starter-oauth2-client声明注册信息客户端标识、授权端点、范围框架接管授权码流程、令牌存储与自动刷新。3、授权服务器签发令牌的那一个系统。Spring 生态由独立的 Spring Authorization Server 项目承担提供授权端点、令牌端点、密钥集端点与用户同意页的全套实现。中小团队更常见的选择是托管方案或成熟开源实现如 Keycloak——授权服务器是安全焦点中的焦点自研的维护成本要按长期养一个安全产品来估算。七、接口鉴权从 URL 规则到方法注解一三道闸按粒度递进接口鉴权不是一道闸而是三道按粒度递进的闸各拦各的越权。URL 级规则在过滤器链的授权站求值按路径与动词做粗粒度放行圈定匿名区与公开区方法级注解在进入方法前按表达式求值主体、参数、返回值都能参与判定是权限点判定的主战场数据级过滤在查询与更新语句上收口是水平越权唯一可靠的拦截点——前两道闸都回答不了这个订单是不是他的。三道闸不是三选一。只有 URL 规则的系统接口内部的资源归属靠自觉只有方法注解的系统一个漏配注解的新接口就是裸奔三道齐备、且最后一道落在数据访问层的系统才谈得上纵深防御。二URL 级规则的两条纪律URL 规则按声明顺序求值命中即停。由此推出第一条纪律具体规则在前通配规则在后——把/api/admin/的收紧规则写在/api/的放行规则之后等于没写。第二条纪律默认拒绝收底规则清单最后一条永远是其余请求一律要求认证。它的价值在于改变遗忘的代价方向新接口忘了配置规则时默认拒绝的系统表现为该放的没放一次调试就会发现默认放行的系统表现为该拦的没拦往往由安全事件来发现。两种遗忘的代价不对称配置就应该偏向代价小的那一边。三方法级注解表达式、判定时机与代理边界1、前置判定与后置判定PreAuthorize在方法执行前求值是绝对主力PostAuthorize在方法执行后对返回值求值适合先查出来才知道归属的读场景但副作用已经发生写操作不可用它兜底。启用方式是配置类上加EnableMethodSecurity。PreAuthorize(hasAuthority(order:refund)) // 权限点判定 public RefundResult refund(long orderId) { ... } PreAuthorize(hasAuthority(order:read) and #userId authentication.name) public ListOrder listOrders(String userId) { ... } // 权限点 参数归属双重判定 PostAuthorize(returnObject.ownerId authentication.name) public OrderDetail getDetail(long orderId) { ... } // 返回值归属判定只适合读2、表达式能用的三类事实安全表达式能触达三类事实主体authentication及其权限集、方法入参#参数名、返回值returnObject。复杂判定不要在注解里堆长表达式而是注册自定义的鉴权组件把逻辑收进可测试的 Java 方法PreAuthorize(orderAuth.canRefund(authentication, #orderId))。注解里留一行语义清晰的调用逻辑本身获得单元测试与断点。3、代理边界注解会静默失效方法级注解由容器代理在调用边界上执行这与事务注解是同一个机制也共享同一类失效同一个对象内部的自调用不经过代理注解形同虚设非公开方法上的注解默认不生效。最危险的性质是它不报错——方法照常执行只是没人检查权限。功能测试全部通过安全检查一次都没发生。评审时对每个带鉴权注解的方法问一句谁调用它是成本最低的一道人工防线更硬的做法是静态扫描搜出所有同类内部调用带鉴权注解方法的代码点一处都不该有。八、细粒度权限模型、数据与生效时刻一RBAC角色是配置权限点才是代码细粒度权限的地基是一个两层的 RBAC 结构用户与角色多对多角色与权限点多对多。权限点用资源:操作命名order:read、order:refund、report:export由开发定义并随代码演进角色订单客服、财务审核、区域主管由运营与安全在管理端配置用户与角色的绑定由人事流程驱动——入职授予、转岗替换、离职回收最好与人事系统联动而不是靠人记。这个结构里有一条必须守住的纪律代码里只判权限点不判角色。判断写成有没有order:refund而不是是不是客服主管——角色是组织的映射组织一年重组几次权限点是系统能力的映射只随功能演进。把角色名写死进代码的系统每次组织调整都要发版。配套两条角色继承最多两层层级一深谁到底有什么权限就说不清高危权限点做周期性盘点谁拥有、多久没用过长期不用即回收——盘点报表同时是审计的硬要求。二数据权限行级过滤与归属校验1、行级过滤的三种实现1.1 业务代码手写条件每个查询手工拼上归属条件部门、租户、本人。直观、无框架依赖但每个新查询都可能漏写漏一处就是一个越权口子。只适合查询入口很少的小系统。1.2 访问层统一改写在 ORM 或 MyBatis 的拦截器层按规则给语句自动追加归属条件白名单放行例外如管理端全量查询、跨部门报表。一次接入、全局生效从机制上防漏写需要管好例外清单与联表场景。这是多数业务系统的默认解。1.3 独立策略引擎主体、资源、环境属性统一进策略引擎策略集中管理、动态下发表达力最强可以做到属性级ABAC控制代价是引入一套引擎的运维与调试成本。适合策略复杂多变的中大型平台小系统用它属于杀鸡用牛刀。2、写操作的归属校验行级过滤讨论最多的是查询最容易出事的却是写。查询漏了过滤泄露的是数据更新与删除漏了归属校验破坏的是别人的数据。按主键更新之前必须先以主键加归属条件查一次或在更新语句里直接带上归属条件——只查主键就更新是高危写法。水平越权的验收同样要脚本化用甲的令牌重放乙的资源标识遍历全部读写接口。这类扫描一次编写长期有效比任何一轮人工测试都更能兜住新接口忘了带归属条件。三权限变更的生效链路管理端把某人移出财务审核角色数据库即刻更新——但旧的授权快照还活在三个地方令牌里签发时写入的角色与范围在过期前一直有效资源服务内为加速判定而做的用户到权限点缓存网关层如果缓存了粗筛结论同样是一份会过时的快照。权限变更的生效速度取决于三个快照里收敛最慢的那一个。给生效速度定一个明确的服务等级是这一段设计的收口普通权限调整分钟级收敛可接受——靠令牌短有效期与缓存过期自然完成封禁、离职、泄密处置必须秒级——走会话版本号强制全端下线同时主动清掉服务内权限缓存。网关的对策则是釜底抽薪只做验签等无状态检查不缓存授权结论。最被低估的事故形态值得单独点名离职员工的令牌与缓存在回收流程走完之后还替他工作了一整个有效期。九、工程化防线存放、架构与门禁一令牌的存放与传输浏览器端的令牌存放是一道没有完美答案、只有取舍清楚的选择题。放 localStorage脚本可读一次跨站脚本注入XSS就能把令牌整枚偷走放 HttpOnly Cookie脚本读不到防住了窃取但引回跨站请求伪造CSRF需要 SameSite 属性与防伪令牌配合。当前较稳的组合是访问令牌放内存不落任何持久存储刷新页面靠静默续期恢复刷新令牌放 HttpOnly、Secure、SameSiteLax 的 Cookie 并限定路径只发往刷新端点。传输侧没有讨论余地全链路 HTTPS令牌不进 URL——地址栏、浏览历史、跳转来源头与访问日志都会留下它。移动端放系统级安全存储钥匙串或密钥库。所有端共同的纪律日志脱敏——请求头里的令牌在网关与应用日志里一律掩码全文令牌进日志等于把凭证写进了保留期最长、访问面最广的存储。二网关统一鉴权与服务内二次校验多服务架构下把验签、有效期、黑名单、限流这类无状态检查收敛到统一网关是正确的减负改一处、全局生效各业务服务不必各自维护密钥集拉取。网关通过后把主体信息注入内部请求头下游直接读头即可。但统一鉴权常被过度引申为服务内不用再管权限这是危险的误读。服务内有三件事外包不了方法级权限点判定网关不懂业务语义、数据归属校验网关看不见数据、以及校验内部请求头的防伪签名。第三件事的原因是服务间的横向调用不经过网关——只信网关注入头的服务在内网里任何一方都能用一个伪造的请求头冒充任意用户。职责边界三条硬规则网关只做无状态检查不做业务级授权、不缓存授权结论主体信息经防伪头传递内部签名或双向认证二选一服务永远兜底——网关被绕过内网调用、运维直连时防线依然完整。三上线前的十六条门禁把全文的判据收敛成十六条可执行的门禁按四条链分组认证链四条慢哈希、不可枚举、集中计数、上下文显式传递、令牌链四条短寿命加轮换、算法白名单、全量校验、密钥治理、鉴权链四条默认拒绝、注解位置、自调用禁令、状态码语义、数据与治理四条行级过滤加归属校验、越权脚本、秒级收敛、权限盘点。门禁的执行方式决定它们是不是摆设静态可查的注解位置、规则顺序、口令存储方式进代码扫描与评审清单运行期才能验证的越权重放、枚举与爆破、版本号收敛时长做成脚本进回归与发布流水线。落地次序也有讲究先把注销与越权这两类看不见的债还掉再谈细粒度模型的精致化——一个能秒级踢人下线、扫不出水平越权的朴素系统比一个策略引擎华丽但令牌收不回的系统安全得多。结语这篇文章的主线可以收成一句话凭证证明的是来源权限来自它身后的整套治理。一枚签名合法的令牌只回答了六个问题里的第一个半——它没被改过以及它声称的主体是谁。剩下的部分全部依赖结构受众与签发方的校验决定它能不能跨服务滥用有效期、轮换与版本号决定授权被撤销后它还能活多久URL 规则、方法注解与行级过滤决定它在系统内部能走多远密钥治理与轮换决定整个信任体系的根有多牢。这些结构没有一件是引入 JWT或接了 OAuth本身能带来的。因此判断一套权限体系是否完工不看它用了哪些名词而看四个问题能不能当场答出数字与位置封禁一个账号多少秒内全部接口拒绝他用甲的令牌请求乙的资源哪一层拦住、日志落在哪里每个带鉴权注解的方法由谁调用签名密钥泄露时换钥流程走过几次演练。四个问题都有答案的系统可以上生产答不出来的无论架构图画得多完整权限都还只是一种大概管用的装饰。参考资料Spring Security 官方文档Servlet 架构与过滤器链Spring Security 官方文档认证体系架构Spring Security 官方文档口令存储与 DelegatingPasswordEncoderSpring Security 官方文档授权体系架构Spring Security 官方文档HTTP 请求级授权Spring Security 官方文档方法级安全Spring Security 官方文档OAuth 2.0 资源服务器与 JWTSpring Security 官方文档OAuth 2.0 客户端Spring Security 官方文档SecurityContextHolder 与并发支持Spring Authorization Server 官方文档RFC 6749OAuth 2.0 授权框架RFC 6750OAuth 2.0 Bearer Token 用法RFC 7519JSON Web TokenJWTRFC 7517JSON Web KeyJWKRFC 7636授权码模式的校验码扩展PKCERFC 8628OAuth 2.0 设备授权模式RFC 9700OAuth 2.0 安全最佳实践OAuth 2.1 草案整合与废弃项说明OpenID Connect Core 1.0 规范RFC 8725JSON Web Token 最佳实践OWASP认证安全速查表OWASPJSON Web Token 安全速查表OWASP会话管理速查表OWASP Top 10失效的访问控制NIST SP 800-63B数字身份指南——认证与生命周期管理Keycloak 官方文档开源身份与访问管理微服务模式访问令牌
阅读完成 · 觉得有帮助?