Search code examples
javaspring-bootspring-securitygraalvm-native-image

@PreAuthorize with spEL not working in Graal native image


I have this RestController where I use @PreAuthorize to check if the request contains the same 'persnr' as the user.

@PreAuthorize("@authorization.hasPermission(principal, #request)")
@PostMapping("/items")
public ResponseEntity<CalendarResponse> getCalendarItems(@Valid @NotNull @RequestBody CalendarRequest request)
{
   ...
}

This is the @Bean with the actual 'hasPermission' method the spEL in @PreAutorize refers to

@Component
public class Authorization
{
    public boolean hasPermission(User user, /* null in native image */ CalendarRequest request) 
    {
        return user.getPersnr().equals(request.getPersnr());
    }
}

This is the body of the request

public class CalendarRequest
{
    @NotBlank(message = "\"persnr darf nicht leer sein")
    private String persnr; 

    public String getPersnr()
    {
        return persnr;
    }
    // ...
}

In a JVM this works perfectly fine but as a native image the '#request' in the spEL is null and i get a NullPointerException in 'hasPermission'.

As a workaround I tried to register a WebExpressionAuthorizationManager with the same spEL but it's not working either

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception
{
    http.authorizeHttpRequests(auth -> 
            auth
                //...
                .dispatcherTypeMatchers(DispatcherType.ERROR).permitAll()                               
                .requestMatchers("/calendar/items").access(new WebExpressionAuthorizationManager("@authorization.hasPermission(principal, #request)"))
                .anyRequest().authenticated());
    
    //...

    return http.build();
}

I also tried using a custom AuthorizationManager but i don't see any possibility to access the json body

Is there another way to implement method level authorization?

Edit: I set'prePostEnabled' to false and implemented a custom AuthorizationManager with the same behavior like @PreAuthorize but the '#request' parameter is still null:

@Configuration
@EnableMethodSecurity(prePostEnabled = false, securedEnabled = false, jsr250Enabled = false)
public class AuthorizationConfig
{
    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    Advisor preAuthorize(CustomAuthorizationManager manager) 
    {
        Pointcut pointcut = new AnnotationMatchingPointcut(null, PreAuthorize.class, true);
        AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor(pointcut, manager);
        interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE.getOrder());
        return interceptor;
    }
}

@Component
public class CustomAuthorizationManager implements AuthorizationManager<MethodInvocation>
{
    @Autowired
    ApplicationContext context;

    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation mi)
    {
        PreAuthorize annotation = mi.getMethod().getAnnotation(PreAuthorize.class);
    
        DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
        expressionHandler.setApplicationContext(context);
        Expression expression = expressionHandler.getExpressionParser().parseExpression(annotation.value());
    
        EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication, mi);
        boolean granted = ExpressionUtils.evaluateAsBoolean(expression, ctx);
        return new ExpressionAuthorizationDecision(granted, expression);
    }
}

Solution

  • In the depth of spring I found out that the parameter name of the method which was retrieved by reflection was 'arg0', so it couldn't be mapped to the '#request' literal in the spEL expression. I compiled with the compiler arg -parameter which preserves the parameter names and now everything works fine.

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source>${java-version}</source>
                    <target>${java-version}</target>
                    <compilerArgs>
                        <arg>-parameters</arg> <---- this did the magic
                    </compilerArgs>
                </configuration>
            </plugin>