Describe the bug
While upgrading from Spring Boot 2.5.6 to 2.7.1 I noticed an issue with MockMvc and method-level @PreAuthorize annotations. The issue appears to be that the anonymous class that Mockito creates includes the same annotations on the methods as the original class. The new checks for duplicate annotations (introduced in 5.6) now see the same annotation twice, once on the original method and once on the anonymous override.
To Reproduce This is my test setup (only relevant parts included):
@RequiredArgsConstructor
@SpringBootTest
class MyTestClass {
@SpyBean private final ImportController importController;
private MockMvc mockMvc;
@BeforeEach
void setUp() {
mocks = MockitoAnnotations.openMocks(this);
mockMvc = MockMvcBuilders.webAppContextSetup(context).apply(springSecurity()).build();
}
@AfterEach
void tearDown() throws Exception {
mocks.close();
}
@Test
void uploadFile() throws Exception {
var file = new MockMultipartFile(/*...*/);
mockMvc
.perform(
multipart("/import")
.file(file)
.with(oidcLogin())
.with(csrf())
.with(user("user").roles("EXPERT")))
.andExpect(status().isOk());
/* verification etc. */
}
}
Running this test produces AnnotationConfigurationException in AuthorizationAnnotationUtils#findUniqueAnnotation due to a duplicate @PreAuthorize annotation on the uploadFile() method:
@PreAuthorize("hasRole('EXPERT')")
public UploadFileDto uploadFile(/*...*/) {
/*...*/
}
Expected behavior Identical annotations on overridden methods should be possible and not throw an exception.
Sample
MWE: https://github.com/theseion/spring-security-mwe
Versions (Spring Boot 5.7.2): Spring Security: 5.7.2 Spring Boot Test: 2.7.1 Spring Test 5.3.21
Comment From: marcusdacoregio
Hi @theseion, thanks for reaching out.
Before getting into the actual issue, I'd like to clarify if you are trying to do a unit or integration test?
I'm asking that because I see @SpringBootTest which is normally used to do integration tests, but then you are spying on your bean and will probably check if it is calling the other beans. I would like to suggest a change if that's the case:
@WebMvcTest(ImportController.class)
class MyTestClass {
@MockBean private MyBeanService myBeanService;
private MockMvc mockMvc;
@BeforeEach
void setUp() {
mocks = MockitoAnnotations.openMocks(this);
mockMvc = MockMvcBuilders.webAppContextSetup(context).apply(springSecurity()).build();
}
@AfterEach
void tearDown() throws Exception {
mocks.close();
}
@Test
void uploadFile() throws Exception {
var file = new MockMultipartFile(/*...*/);
mockMvc
.perform(
multipart("/import")
.file(file)
.with(oidcLogin())
.with(csrf())
.with(user("user").roles("EXPERT")))
.andExpect(status().isOk());
/* verification etc. */
// verify(myBeanService).method(...);
}
}
Using @WebMvcTest you can test your controller isolated from the other components. If you still want to use the former setup, please provide a reproducible example.
Comment From: theseion
Thanks for getting back to me @marcusdacoregio. It's really an integration test and I need to pull up the entire context. I've updated the issue with a link to an MWE.
Comment From: saugion
I'm having the same issue during the migration to spring boot 3.1.1, findUniqueAnnotation is throwing an exception saying it finds twice the @PreAuthorize annotation. After debugging I see twice the same proxy with the same method, it should not happen
Comment From: marcusdacoregio
I believe that this is the same problem as https://github.com/spring-projects/spring-security/issues/13490
Comment From: saugion
Yes, it seems so. In my case is PreAuthorize but the case is the same
Comment From: jzheaux
Thanks for the report, @theseion. I believe this was addressed in https://github.com/spring-projects/spring-security/issues/13625.
It updated the sample to the latest and saw the behavior corrected. The fix should already be available in the latest 5.8+ and 6.2+