必须替换 FilterInvocationSecurityMetadataSource 实现动态权限校验,因其是授权阶段唯一决定请求所需 ConfigAttribute 的入口;其他位置如 OAuth2AuthenticationProcessingFilter 或 JwtAuthenticationConverter 均无法满足 URL 级动态绑定需求。

直接在 OAuth2 流程中注入动态权限校验,不能靠改 AuthorizationFilter 或硬塞进 OAuth2AuthorizedClientService,而必须从资源服务器端的权限决策入口切入——也就是替换掉默认的 FilterInvocationSecurityMetadataSource 实现。
为什么不能在 OAuth2AuthenticationProcessingFilter 里加逻辑
这个过滤器只负责解析 token、构建 OAuth2AuthenticationToken,它不参与“该不该放行”这个判断。等它执行完,请求已经进入 Spring Security 的授权链,此时权限校验由 FilterSecurityInterceptor 驱动,而它的数据源就是 FilterInvocationSecurityMetadataSource。
- 你在该过滤器里手动 setAuthority 不生效:
SecurityContextHolder中的认证对象会被后续拦截器覆盖或忽略 - 它不感知 URL 路径与权限资源的映射关系,没法做「/api/user/delete → ROLE_ADMIN」这类动态绑定
- Spring Security 6+ 已移除该类,用
BearerTokenAuthenticationFilter替代,更不可扩展
FilterInvocationSecurityMetadataSource 是唯一可控入口
这是 Spring Security 授权阶段真正决定「当前请求需要哪些 ConfigAttribute(比如 ROLE_ADMIN、SCOPE_write)」的组件。所有基于路径的权限控制(包括 @PreAuthorize 以外的声明式配置)最终都经过它。
- 必须实现
getAttributes(Object)方法,入参是FilterInvocation,从中可提取getRequestUrl()和 HTTP 方法 - 返回值是
Collection,即该 URL 所需的权限标识集合,例如new SecurityConfig("ROLE_USER") - 数据库加载时机要谨慎:不能每次请求都查库,推荐用懒加载 + 缓存(如
ConcurrentHashMap+ 定时刷新或事件触发清空)
示例片段:
public Collection<configattribute> getAttributes(Object object) throws IllegalArgumentException {
String url = ((FilterInvocation) object).getRequestUrl();
HttpMethod method = ((FilterInvocation) object).getHttpRequest().getMethod();
String key = method.name() + ":" + url;
// 从缓存或 DB 查出该路径所需权限标识
return configAttributeMap.getOrDefault(key, Collections.emptyList());
}</configattribute>
如何让自定义实现被 Spring Security 识别
不能只写个实现类就完事。必须把它注册为 Bean,并确保它被 FilterSecurityInterceptor 使用——这取决于你是否使用了 HttpSecurity.authorizeHttpRequests() 的新 DSL 风格。
- Spring Security 5.7+(推荐):在
SecurityFilterChain配置中显式传入,通过http.authorizeHttpRequests(auth -> auth.withObjectPostProcessor(...)) - 老版本(
WebSecurityConfigurerAdapter):重写configure(HttpSecurity),调用http.authorizeRequests().withObjectPostProcessor(new ObjectPostProcessor(){...}) - 关键点:替换的是
FilterSecurityInterceptor内部的securityMetadataSource字段,不是全局 Bean 名称
容易踩的坑:JWT token 解析和权限字段不一致
OAuth2 资源服务器默认从 JWT 的 scope 或 authorities 字段提取权限,但你的数据库权限表可能是 resc_code、permission_name 这类字段。这两者之间没有自动映射。
- 不要试图在
JwtAuthenticationConverter里把数据库权限塞进GrantedAuthority:它只在认证阶段运行一次,无法响应 URL 变化 - 正确做法是:
FilterInvocationSecurityMetadataSource返回的ConfigAttribute必须和你最终AccessDecisionManager判定时用的字符串完全一致(比如都用"PERM_USER_DELETE") - 如果你沿用
RoleVoter,那ConfigAttribute必须以"ROLE_"开头;否则得自定义AccessDecisionManager或改用AuthenticatedVoter+ 表达式
动态权限真正的复杂点不在代码怎么写,而在于缓存失效策略和权限变更时的实时性取舍——数据库改了一条权限记录,是立刻广播清空所有节点缓存,还是接受最多 30 秒延迟?这个边界得结合业务定,框架本身不替你决定。











