You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 05:28:14