Spring Security授权机制详解定位第 03 篇讲透授权规则声明、角色与权限建模、方法级安全注解与 SpEL 表达式体系适用版本Spring Security 6.xSpring Boot 3.x目录一、授权规则URL 级二、角色与权限三、方法级安全四、表达式体系五、授权判定落地六、总结七、常见高频面试题一、授权规则URL 级1.1 声明式规则http.authorizeHttpRequests(auth-auth.requestMatchers(/api/public/**).permitAll()// 公开.requestMatchers(/api/admin/**).hasRole(ADMIN)// 需角色.requestMatchers(HttpMethod.DELETE,/api/**).hasAuthority(user:delete).anyRequest().authenticated());// 兜底需登录1.2 规则语义要点要点说明顺序敏感规则按声明顺序匹配第一条命中即生效——精确规则放前宽泛放后anyRequest兜底规则必须放最后permitAll放行仍经过过滤链如 CSRFanonymous仅匿名用户可访问少见匹配器粒度可带 HttpMethod按方法路径细化1.3 常见错误顺序颠倒.anyRequest().authenticated() 写在前面 → 后面规则永不生效 路径漏配放行用了 /** 过宽 → 意外公开二、角色与权限2.1 RBAC 建模用户 ──多对多── 角色 ──多对多── 权限 如 ADMIN 如 user:delete概念对应说明权限AuthorityGrantedAuthority最小授权单元细粒度操作角色Role带ROLE_前缀的特殊权限权限的集合粗粒度2.2 Role 与 Authority 的区分高频坑hasRole(ADMIN) → 实际检查 ROLE_ADMIN自动补前缀 hasAuthority(ROLE_ADMIN) → 与上面等价 授权数据源SimpleGrantedAuthority(ROLE_ADMIN) 或 authority(user:delete)纪律授予用户的权限字符串与判定时用的口径一致——要么统一带ROLE_前缀用 Role API要么统一不带用 Authority API混用是明明有权限却 403的常见原因。2.3 权限设计建议① 权限命名结构化资源:操作order:read、order:delete ② 角色是权限的打包业务变化加权限改角色不动代码 ③ 超级权限慎用*:*需要时单独审计三、方法级安全3.1 开启与注解EnableMethodSecurity// 6.x替代 EnableGlobalMethodSecurity注解时机用途PreAuthorize方法执行前最常用进入条件PostAuthorize方法执行后按返回值判定PreFilter执行前过滤集合入参PostFilter执行后过滤集合返回值PreAuthorize(hasRole(ADMIN))voiddeleteUser(Longid){...}PreAuthorize(#id authentication.principal.id)// 只能查自己UsergetById(Longid){...}3.2 实现原理方法级安全是AOP 代理被注解方法被代理包裹执行前求值表达式不满足抛AccessDeniedException403。所以同样受代理限制——自调用绕过呼应 FW-03。3.3 URL 级与方法级的分工层适用URL 级粗粒度路由保护模块级方法级细粒度业务规则“只能操作自己的数据”生产常两层配合URL 挡模块方法控细节。四、表达式体系授权条件用 SpEL 表达常用表达式表达式含义hasRole(ADMIN)/hasAuthority(x)角色/权限isAuthenticated()/isAnonymous()认证状态principal/authentication当前用户/认证对象permChecker.check(authentication, #id)调用自定义 Bean 判定#result.owner authentication.name访问返回值PostAuthorize自定义判定 Bean是复杂规则的出口——把是否本人资源、是否在白名单等逻辑写成组件表达式里引用保持声明式可读。五、授权判定落地5.1 AuthorizationManager6.x 统一抽象规则 → AuthorizationManager 判定返回决定 原因 URL 级链末的 AuthorizationFilter 调用 方法级AOP 拦截器调用 拒绝 → AccessDeniedException → ExceptionTranslationFilter → 4036.x 用AuthorizationManager统一了 URL 与方法两处的判定接口替代旧的 AccessDecisionManager 体系。5.2 403 的处理匿名被拒 → 先触发认证入口401/登录 已认证被拒 → 403可配 accessDeniedHandler 返回统一错误体六、总结URL 级规则authorizeHttpRequests 声明式顺序敏感、anyRequest 兜底匹配器可到方法路径。角色权限权限最小单元、角色是打包hasRole自动补ROLE_前缀——口径不一致是 403 常见根因权限命名资源:操作。方法级安全EnableMethodSecurity PreAuthorize 等AOP 实现受代理限制自调用绕过URL 挡粗、方法控细。表达式SpEL 支持角色/状态/主体/返回值/自定义 Bean 判定复杂规则组件化。落地6.x 统一 AuthorizationManager拒绝经 ExceptionTranslationFilter 转 401/403。七、常见高频面试题1. hasRole 和 hasAuthority 的区别为什么有角色却 403要点hasRole(“ADMIN”) 会自动补前缀检查 ROLE_ADMINhasAuthority 按字面匹配。若授予的权限字符串是 “ADMIN” 而判定用 hasRole找 ROLE_ADMIN或反之就匹配不上导致 403——这是口径不一致的典型坑。解法统一约定角色统一带 ROLE_ 前缀走 Role API细粒度权限不带前缀走 Authority API并保证 UserDetailsService 授予的字符串与判定口径一致。2. 方法级安全注解有哪些原理是什么要点PreAuthorize执行前判定最常用、PostAuthorize执行后按返回值判定、PreFilter/PostFilter过滤集合参数/返回值。原理是 AOP 代理被注解方法被代理包裹执行前/后用 SpEL 求值条件不满足抛 AccessDeniedException。需 EnableMethodSecurity 开启。受代理限制——同类自调用绕过注解且方法必须是 Bean 的 public 方法。3. URL 级授权和方法级授权怎么选要点两层互补。URL 级authorizeHttpRequests做粗粒度模块保护——某路径段整体需要什么角色方法级PreAuthorize做细粒度业务规则——如只能操作本人数据、依赖入参/返回值的判定。生产组合URL 挡模块入口方法控具体规则。只用 URL 表达不了数据归属类规则只用方法级则配置分散难审计。4. PreAuthorize 怎么写只能访问自己的资源要点用表达式比较参数与当前用户。如 PreAuthorize(“#id authentication.principal.id”)参数即本人或查资源归属PreAuthorize(“permChecker.isOwner(authentication, #id)”)把查库判断归属的逻辑放自定义 Bean。PostAuthorize 可基于返回值判定#result.owner authentication.name但方法已执行适合读场景。复杂归属规则组件化是清晰做法。5. 授权规则的顺序为什么重要要点规则按声明顺序匹配第一条命中的生效后面不再看。若把宽泛规则如 anyRequest().authenticated() 或 /** 的 permitAll写在前面具体规则永远不会生效——常见错误是兜底写前面导致 403/意外放行。纪律精确规则在前、宽泛在后、anyRequest 最后配置后要用测试覆盖关键路径验证顺序正确。6. RBAC 模型在 Security 里怎么落地要点数据模型用户-角色-权限三层多对多。登录时 UserDetailsService 查用户把其角色的权限集合装入 UserDetails 的 authorities角色加 ROLE_ 前缀。授权时用 hasRole/hasAuthority 判定。业务变化新权限/调整角色权限改数据库配置即可不动代码。超级权限*要慎用并审计角色不要无限膨胀。7. 匿名用户和已认证用户被拒响应一样吗要点不一样。匿名未认证访问受保护资源——本质是缺身份Security 触发认证入口返回 401 或重定向登录页已认证但无权限——返回 403。这是 401/403 语义的区别401 引导去认证403 表示身份有效但授权不足。客户端据此决定跳登录还是提示无权限。8. 如何自定义授权判定逻辑要点两个层次。简单SpEL 表达式组合内置函数。复杂写一个 Spring Bean 实现判定方法如检查数据归属、白名单、时间窗口表达式中用 beanName.method(…) 调用——把业务规则组件化、可测试、可复用。更底层可实现 AuthorizationManager 注册到特定规则。原则声明式配置保持可读复杂逻辑下沉到组件。9. EnableMethodSecurity 和旧版 EnableGlobalMethodSecurity 的区别要点EnableMethodSecurity 是 6.x 的新注解默认开启 PreAuthorize/PostAuthorize 等prePostEnabled 默认为 true配置更简洁EnableGlobalMethodSecurity 是旧注解5.6 起弃用需要显式 prePostEnabledtrue。迁移时替换注解名并检查参数默认值差异。配套地方法安全的实现也统一到新的 AuthorizationManager 体系。10. 权限校验放在哪一层只做前端控制行不行要点必须服务端校验前端控制隐藏按钮/路由守卫只是体验优化不能当安全边界——请求可绕过前端直接发。分层网关/URL 级挡模块访问方法级控数据归属与细粒度规则必要时业务代码内再做领域校验防越权的最后防线尤其涉及横向越权。原则安全判定永远在服务端且纵深多层。