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

Spring 6升级后@PreAuthorize结合ArgumentResolver时authDto为空问题排查

Spring 6升级后公网环境下@PreAuthorize验证时authDto为空问题排查

问题描述

Spring 6升级测试中,部署至公网访问模式环境时,/save端点的@PreAuthorize注解验证失败。具体情况:

  • 用户提交表单发起保存请求;
  • /save端点的@PreAuthorize使用AuthorizationHelper.IS_STATUS_VALID辅助方法;
  • 方法签名中的AuthorizationDto由自定义ArgumentResolver解析,已确认Resolver注册正确且authDto初始化成功,但SpEL调用AuthorizationHelper.isStatusValid时authDto为空;
  • 开发环境正常,公网环境与开发环境最大差异为访问模式(代理配置)。

相关代码

端点代码

@PreAuthorize("hasAnyAuthority('IS_APPLICANT', 'IS_ADMIN') and " + AuthorizationHelper.IS_STATUS_VALID)
@RequestMapping(value = "/save", method = RequestMethod.POST)
public String save(@ModelAttribute SomeModel sm, WebRequest request,
        HttpSession session, AuthorizationDto authDto) {
      // 页面逻辑处理
      return "some-page-view";
 }

AuthorizationHelper代码

@Component
public class AuthorizationHelper {
    @Autowired
    public AuthorizationHelper(){
     // 初始化所需服务
    }

    public static final String IS_STATUS_VALID = "@authorizationHelper.isStatusValid(#authDto)";

    public boolean isStatusValid(AuthorizationDto authDto) {
        logger.info("AuthorizationHelper :: authDto: " + authDto); // 公网环境此处为null
        // 验证逻辑
        return result;
    }
}

自定义ArgumentResolver代码

@Component("authResolver")
public class CustomAuthorizationResolver implements HandlerMethodArgumentResolver {

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        AuthorizationDto authDto = new AuthorizationDto();
        // 属性设置逻辑
        logger.info("AuthorizationDto after setting properties: {}", authDto); // 此处authDto不为null,且在PreAuthorize执行前调用
        return authDto;
    }
}

堆栈信息片段

java.lang.NullPointerException: Cannot invoke "org.example.app.dto.AuthorizationDto.doSomeValidation()" because "authDto" is null
at org.example.app.web.helpers.AuthorizationHelper.isStatusValid(AuthorizationHelper.java:49)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.expression.spel.support.ReflectiveMethodExecutor.execute(ReflectiveMethodExecutor.java:142)
at org.springframework.expression.spel.ast.MethodReference.getValueInternal(MethodReference.java:125)
at org.springframework.expression.spel.ast.MethodReference$MethodValueRef.getValue(MethodReference.java:401)
at org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:97)
at org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:222)
at org.springframework.expression.spel.ast.OpAnd.getBooleanValue(OpAnd.java:57)
at org.springframework.expression.spel.ast.OpAnd.getValueInternal(OpAnd.java:52)
at org.springframework.expression.spel.ast.SpelNodeImpl.getTypedValue(SpelNodeImpl.java:119)
at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:309)
at org.springframework.security.access.expression.ExpressionUtils.evaluateAsBoolean(ExpressionUtils.java:30)
at org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager.check(PreAuthorizeAuthorizationManager.java:68)
at org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager.check(PreAuthorizeAuthorizationManager.java:40)
at org.springframework.security.config.annotation.method.configuration.DeferringObservationAuthorizationManager.check(DeferringObservationAuthorizationManager.java:47)
at org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor.attemptAuthorization(AuthorizationManagerBeforeMethodInterceptor.java:251)
at org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor.invoke(AuthorizationManagerBeforeMethodInterceptor.java:197)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:768)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:720)
at org.example.app.web.controllers.ApplicantController$$SpringCGLIB$$0.save(<generated>)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(D

排查方向建议

  • 检查请求转发/重复处理:公网代理(如Nginx)可能存在请求重定向或二次转发,导致Spring Security拦截器在第一次请求时执行,但ArgumentResolver仅在第二次请求初始化参数。添加请求ID(如从请求头或生成唯一标识)日志,跟踪同一请求是否被多次处理。
  • 验证拦截器与Resolver执行顺序:Spring 6中AuthorizationManagerBeforeMethodInterceptor(方法安全拦截器)与自定义ArgumentResolver的执行顺序是否在公网环境发生变化。通过日志打印执行时机,对比开发环境,确认安全拦截是否在参数解析前执行(正常情况下参数解析应在安全验证前完成,但代理可能改变请求链)。
  • 代理环境下请求属性传递:公网代理可能替换或包装了NativeWebRequest对象,导致ArgumentResolver生成的authDto未被存入SpEL可访问的上下文。尝试在Resolver中将authDto存入mavContainer.addAttribute("authDto", authDto)或webRequest.setAttribute("authDto", authDto, RequestAttributes.SCOPE_REQUEST),然后修改SpEL表达式为@authorizationHelper.isStatusValid(#request.getAttribute('authDto'))测试是否能获取到。
  • Spring 6方法安全配置差异:Spring 6对方法级安全的实现进行了调整,公网环境的安全配置(如是否启用了上下文缓存、代理模式)可能与开发环境不同。检查@EnableMethodSecurity的配置参数,是否开启了proxyTargetClass或其他影响上下文的选项。
  • 细化日志与Debug:在公网环境增加日志,打印每个请求的sessionId、requestId,关联Resolver和AuthorizationHelper的日志,确认是否为同一请求;本地复现代理场景(如用Nginx转发到本地服务),Debug SpEL的EvaluationContext,查看#authDto是否存在于上下文变量中。

内容的提问来源于stack exchange,提问作者user1521567

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 06:44:53