Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@PreAuthorize should not apply method security to inherited concrete methods #15352

Closed
MartinHaeusler opened this issue Jul 3, 2024 · 4 comments
Assignees
Labels
in: core An issue in spring-security-core type: bug A general bug
Milestone

Comments

@MartinHaeusler
Copy link

Describe the bug
I don't know the exact spring security patch version where this behavior changed, but in 6.2.4 I had a setup like this:

@RestController
@PreAuthorize("hasAdminRole()") // expression doesn't matter
public class MyRestController {

    @ResponseBody
    @GetMapping("/status")
    public String someEndpoint(){
        return "foo";
    }

   @ExceptionHandler
   public ResponseEntity<String> handleAccessDeniedException(ServletRequets request, AccessDeniedException e){
        // .... build some nice string... 
   }

}

In Spring Security 6.2.4, if an request comes in that does not match the @PreAuthorize condition, then Spring Security creates an AccessDeniedException, and hands it over to the handleAccessDeniedException(...) method. Everything was fine.

In Spring Security 6.3.1 (and maybe earlier as well, not sure, this is the version I'm currently upgrading to), something else happens:

  • The REST call comes in
  • The @PreAuthorize expression is evaluated and fails
  • An AccessDeniedException is created
  • Spring Security attempts to call handleAccessDeniedException...
  • ... but gets intercepted by the method security again
  • ... which again throws an AccessDeniedException
  • ... which causes the original handler invocation to fail, logging the second AccessDeniedException as a warning with "Failure in @ExceptionHandler handleAccessDeniedException"

Overall, this results in a HTTP 403 for the client, no matter what the handleAccessDeniedException method would have done, because it never gets called.

The workaround is to move all @ExceptionHandler methods to an external @ControllerAdvice class, as those are not subject to the method security imposed by the class-level @PreAuthorize annotation. In Spring Security 6.2.4 this worked out of the box.

I think this is a weird breaking change and a potential pitfall for developers. Nobody would think that spring would intercept itself, applying method security on an exception handler method.

@MartinHaeusler MartinHaeusler added status: waiting-for-triage An issue we've not yet triaged type: bug A general bug labels Jul 3, 2024
@marcusdacoregio marcusdacoregio self-assigned this Jul 3, 2024
@marcusdacoregio marcusdacoregio added in: core An issue in spring-security-core and removed status: waiting-for-triage An issue we've not yet triaged labels Jul 3, 2024
@marcusdacoregio
Copy link
Contributor

Hi @MartinHaeusler, thanks for the report.

Are you sure that it used to work in Spring Security 6.2.x? I'm trying a sample with Spring Boot 3.2.4, therefore Spring Security 6.2.4 and I see the handler method not being invoked:

Failure in @ExceptionHandler com.example.springsecurityexamples.MyRestController#handleAccessDeniedException(AccessDeniedException)

Are you able to provide a minimal, reproducible sample where I can just change versions to simulate the problem?

@marcusdacoregio marcusdacoregio added the status: waiting-for-feedback We need additional information before we can continue label Jul 3, 2024
kse-music added a commit to kse-music/spring-security that referenced this issue Jul 4, 2024
kse-music added a commit to kse-music/spring-security that referenced this issue Jul 4, 2024
@spring-projects-issues spring-projects-issues added the status: feedback-reminder We've sent a reminder that we need additional information before we can continue label Jul 10, 2024
@marcusdacoregio marcusdacoregio removed status: waiting-for-feedback We need additional information before we can continue status: feedback-reminder We've sent a reminder that we need additional information before we can continue labels Jul 10, 2024
@marcusdacoregio
Copy link
Contributor

I just checked against all the supported branches and that doesn't work in any of them.

@MartinHaeusler
Copy link
Author

@marcusdacoregio I apologize for the delay, I've been on vacation for the last two weeks. There is one detail we had in our codebase which I didn't include in the example (because I thought it doesn't matter):

We had our @ExceptionHandler methods defined in an abstract class AbstractRestController from which all our REST controllers derived. This used to be our way to share the handler logic. Our AbstractRestController had no spring security annotations at all. With the upgrade from 6.2.4 to 6.3.1, those handlers stopped working for the reasons listed in the opening post. This may or may not have been an intentional change which is why I thought I should report it.

Also, the popular tutorial site Baeldung recommends the abstract base class approach.

We've moved all our @ExceptionHandlers into a @ControllerAdvice class and eliminated our AbstractRestController, it works fine that way.

In general, I would question if it is a good idea to apply any method security rules to a method that is annotated as @ExceptionHandler. I could imagine that this trips up developers and the resulting exceptions are quite hard to understand.

@kse-music
Copy link
Contributor

kse-music commented Jul 31, 2024

I just checked against all the supported branches and that doesn't work in any of them.

This bug maybe exist in all branches.

When an exception occurs, the ExceptionHandler defined in Controller is used first, so when the handleAccessDeniedException method is executed, it is also intercepted by AuthorizationManagerBeforeMethodInterceptor

Here is the test code

@RequestMapping("test")
@RestController
@PreAuthorize("hasRole('admin')")
@EnableMethodSecurity
@SpringBootApplication
public class TestApplication {

    public static void main(String[] args) {
        SpringApplication.run(TestApplication.class, args);
    }

    @Bean
    static SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                .authorizeHttpRequests(c -> c.anyRequest().permitAll())
                .build();
    }

    @GetMapping("list")
    public String list(String query) {
        return query;
    }

    @ExceptionHandler
    public ResponseEntity<String> handleAccessDeniedException(AccessDeniedException e){
        return ResponseEntity.ok(e.getMessage());
    }

}

jzheaux added a commit that referenced this issue Jul 31, 2024
For backward compatibility, this commit changes the annotation traversal
logic to match what is found in PrePostAnnotationSecurityMetadataSource.

This reverts gh-13783 which is a feature that unfortunately regressess
pre-existing behavior like that found in gh-15352. As such, that
functionality has been removed.

Issue gh-15352
@jzheaux jzheaux moved this to Done in Spring Security Team Aug 6, 2024
@jzheaux jzheaux added this to the 6.3.2 milestone Aug 29, 2024
@jzheaux jzheaux changed the title @PreAuthorize on controller class also applies method security to @ExceptionHandler methods in Spring Security 6.3.1. @PreAuthorize on controller class also applies method security inherited concrete methods Aug 29, 2024
@jzheaux jzheaux changed the title @PreAuthorize on controller class also applies method security inherited concrete methods @PreAuthorize should not apply method security to inherited concrete methods Aug 29, 2024
jzheaux added a commit to jzheaux/spring-security that referenced this issue Sep 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core An issue in spring-security-core type: bug A general bug
Projects
Status: Done
Development

No branches or pull requests

5 participants