Spring 6升级后@PreAuthorize结合ArgumentResolver时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
相关产品推荐
相关产品推荐

