spring-mvcspring-securityspring-aopspring-expression

Spring 6 upgrade @PreAuthorize not working with ArgumentResolver


I'm doing some testing on a Spring 6 upgrade and when we promoted to an environment with a public access pattern, a PreAuthorize decorator is failing.

  1. A user submits a form with a save request.
  2. the /save endpoint has a @PreAuthorize decorator that uses a helper method, AuthorizationHelper.IS_STATUS_VALID
  3. The endpoint also specifies an object in the method signature that is used in the PreAuthorize decorator, which is resolved with a custom ArgumentResolver. The code looks like this
    @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) {
          // do page logic
          return some page view;
     }

I have confirmed that the ArgumentResolver is correctly registered and that the authDto is correctly initialized. In this one environment, when the AuthorizationHelper isStatusValid method is invoked through what I believe is a Spring SpEl expression, the authDto is as null. I am new with this application and looking for some help on why that might happen. My research suggests it may be a problem with the environment configuration, but I want to know what I am looking for to fail. This is what the AuthorizationHelper code looks like.

@Component
public class AuthorizationHelper {
    // constructor
    @Autowired
    public AuthorizationHelper(){
     // init any services needed
    }

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

    public boolean isStatusValid(AuthorizationDto authDto) {
        logger.info("AuthorizationHelper :: authDto: " + authDto); // is null
        // do logic
        return result;
    }

}

And for good measure, I will post the aurgement resolver code as well, although i have confirmed this is working.

@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();
        // some logic
        logger.info("AuthorizationDto after setting properties: {}", authDto); // not null. is invoked when the /save endpoint is hit, before the PreAuthorize code is executed
        return authDto;
    }
}

Again, I will start digging into any environment configuration differences. The biggest one I'm aware of is this environment has a different access pattern than the dev environment. My initial research indicated it could be a proxy issue. I appreciate any help! Here is the relevant snippet of the stack trace

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

Solution

  • The issue was our new relic jar needed to be updated. It was incompatible with the other spring updates we did and we didn't use it in the lower environments.