Spring Boot 3 Webflux自定义方法安全表达式求值器问题排查
Spring Boot 3 Webflux 自定义方法安全表达式实现方案
问题说明
在Spring Boot 3 Webflux应用中尝试使用方法安全注解与自定义表达式求值器时遇到报错,希望确认是否可以用包含多个方法的Bean实现类似官方文档中Function Bean的功能,并解决当前异常问题。
现有代码
控制器端点代码
@GetMapping @PreAuthorize("@foo.canAccess()") // 注意:原代码缺少引号,需修正为字符串形式的SpEL表达式 public Mono<FooResponse> computeFoos(Mono<FooRequest> fooRequest) { ... }
自定义表达式求值器Bean
@Component("foo") public class FooSecurityExpressionEvaluator { public Mono<Boolean> canAccess() { return ReactiveSecurityContextHolder.getContext() .map(p -> true); // 这里替换为实际权限判断逻辑 } }
报错堆栈(中文翻译)
java.lang.IllegalStateException: 未找到MethodInvocation:请检查AOP调用是否正在进行,且ExposeInvocationInterceptor在拦截器链的最前端。特别注意,优先级为HIGHEST_PRECEDENCE的通知会导致此问题。 at org.springframework.aop.interceptor.ExposeInvocationInterceptor.currentInvocation(ExposeInvocationInterceptor.java:74) Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 错误出现在以下环节: *__checkpoint ⇢ 处理器 com.foo.FooController#computeFoos(Mono, ServerWebExchange) [DispatcherHandler] *__checkpoint ⇢ org.springframework.security.web.server.authorization.AuthorizationWebFilter [DefaultWebFilterChain] *__checkpoint ⇢ org.springframework.security.web.server.authorization.ExceptionTranslationWebFilter [DefaultWebFilterChain] *__checkpoint ⇢ org.springframework.security.web.server.authentication.logout.LogoutWebFilter [DefaultWebFilterChain] *__checkpoint ⇢ org.springframework.security.web.server.savedrequest.ServerRequestCacheWebFilter [DefaultWebFilterChain] *__checkpoint ⇢ org.springframework.security.web.server.context.SecurityContextServerWebExchangeWebFilter [DefaultWebFilterChain] *__checkpoint ⇢ org.springframework.security.web.server.authentication.AuthenticationWebFilter [DefaultWebFilterChain] *__checkpoint ⇢ org.springframework.security.web.server.context.ReactorContextWebFilter [DefaultWebFilterChain] *__checkpoint ⇢ org.springframework.security.web.server.header.HttpHeaderWriterWebFilter [DefaultWebFilterChain] *__checkpoint ⇢ org.springframework.security.config.web.server.ServerHttpSecurity$ServerWebExchangeReactorContextWebFilter [DefaultWebFilterChain] *__checkpoint ⇢ org.springframework.security.web.server.WebFilterChainProxy [DefaultWebFilterChain] *__checkpoint ⇢ org.springframework.web.filter.reactive.ServerHttpObservationFilter [DefaultWebFilterChain] *__checkpoint ⇢ HTTP POST "/org" [ExceptionHandlingWebHandler] 原始堆栈跟踪: at org.springframework.aop.interceptor.ExposeInvocationInterceptor.currentInvocation(ExposeInvocationInterceptor.java:74) at org.springframework.aop.aspectj.AbstractAspectJAdvice.getJoinPointMatch(AbstractAspectJAdvice.java:655) at org.springframework.aop.aspectj.AspectJMethodBeforeAdvice.before(AspectJMethodBeforeAdvice.java:44) at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:57) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:173) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) at org.springframework.security.authorization.method.ReactiveMethodInvocationUtils.proceed(ReactiveMethodInvocationUtils.java:32) at org.springframework.security.authorization.method.AuthorizationManagerBeforeReactiveMethodInterceptor.lambda$invoke$2(AuthorizationManagerBeforeReactiveMethodInterceptor.java:113) at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:44)
解决方案
1. 启用Reactive方法安全支持
在Spring配置类上添加@EnableReactiveMethodSecurity注解,该注解会自动配置Reactive环境下的方法安全拦截器,替代传统AOP的拦截逻辑,避免ExposeInvocationInterceptor相关异常:
@Configuration @EnableReactiveMethodSecurity public class SecurityConfig { // 可添加其他安全配置,如AuthenticationManager、SecurityWebFilterChain等 }
2. 修正@PreAuthorize注解写法
确保@PreAuthorize的参数是字符串形式的SpEL表达式,调用Bean的方法时格式为@beanId.methodName():
@GetMapping @PreAuthorize("@foo.canAccess()") public Mono<FooResponse> computeFoos(Mono<FooRequest> fooRequest) { ... }
3. 扩展自定义表达式Bean
完全可以在同一个Bean中添加多个权限判断方法,例如:
@Component("foo") public class FooSecurityExpressionEvaluator { public Mono<Boolean> canAccess() { return ReactiveSecurityContextHolder.getContext() .map(context -> { Authentication auth = context.getAuthentication(); // 实际权限判断逻辑,比如检查用户角色 return auth.getAuthorities().stream() .anyMatch(granted -> granted.getAuthority().equals("ROLE_USER")); }) .defaultIfEmpty(false); } public Mono<Boolean> canEdit() { return ReactiveSecurityContextHolder.getContext() .map(context -> { Authentication auth = context.getAuthentication(); return auth.getAuthorities().stream() .anyMatch(granted -> granted.getAuthority().equals("ROLE_ADMIN")); }) .defaultIfEmpty(false); } }
之后在控制器方法中直接使用:
@PutMapping @PreAuthorize("@foo.canEdit()") public Mono<FooResponse> editFoos(Mono<FooRequest> fooRequest) { ... }
异常原因说明
报错是因为未正确启用Reactive方法安全,导致Spring尝试使用传统AOP的拦截逻辑,而Reactive环境下无法获取到MethodInvocation实例。@EnableReactiveMethodSecurity会启用专门的Reactive方法安全拦截器,适配Webflux的响应式模型,避免此类问题。
内容的提问来源于stack exchange,提问作者Mark
相关产品推荐
相关产品推荐

