Home > database >  Failed to evaluate expression from inside of PreAuthorize during tests
Failed to evaluate expression from inside of PreAuthorize during tests

Time:10-08

I am trying to implement authorization for app endpoints. I am working on using custom methods inside @PreAuthorize annotation.

@RestController
@RequestMapping("test")
@RequiredArgsConstructor
public class TestController {
    private final TestService testService;

    @GetMapping("/{userId}")
    @ResponseStatus(HttpStatus.OK)
    @PreAuthorize("@SecurityService.hasAccessToUser(#userId)")
    List<TestDTO> getUserData(@PathVariable String userId) {
        return testService.fetchUserData(userId);
    }
}

sidenote - presented below hasAccessToUser method could be replaced by simply using @PreAuthorize("authorization.principal == #userId") but it's just a stripped-down version of SecurityService

@Service
@RequiredArgsConstructor
public class SecurityService {

    private Authentication getAuthentication() {
        return SecurityContextHolder.getContext().getAuthentication();
    }

    private String getUserId() {
        return (String) getAuthentication().getPrincipal();
    }

    public boolean hasAccessToUser(String userId) {
        return getUserId().equals(userId);
    }
}

This authorization implementation works well on launched application, BUT it breaks while testing:

@WebMvcTest(TestController.class)
public class TestControllerTest {

    private static final String BASE_URL = "/test";

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private TestService testService;

    @MockBean
    private SecurityService securityService;

    @MockBean
    private TestRepository testRepository;


    @SneakyThrows
    @Test
    void shouldGetUserDataGivenUserId() {
        mockMvc.perform(asUser(get(BASE_URL   "/"   TEST_USER_ID)))
                .andExpect(status().is(Response.SC_OK));

        verify(testService).fetchUserData(TEST_USER_ID);
    }
}

When I look into a body of a response to the failed request it has lengthy log ending with:

Internal Server Error: Failed to evaluate expression '@SecurityService.hasAccessToUser(#userId)'"}]
 at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:59)
 at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:122)
 at org.springframework.test.web.servlet.result.ContentResultMatchers.lambda$string$4(ContentResultMatchers.java:148)
 at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:212)
 at com.test.controller.TestControllerTest.shouldGetUserDataGivenUserId(TestControllerTest.java:87)
 at java.base/java.util.ArrayList.forEach(Unknown Source)

So, Spring does not recognize @SecurityService while running tests. Is there any way of making @SecurityService work inside @PreAuthorize during tests?

CodePudding user response:

The expression @SecurityService.hasAccessToUser(#userId) will look for a bean with the name SecurityService . But you are now using @MockBean to define this bean which by default its bean name will be auto generated. There are no beans which the name is SecurityService and so it throws this error.

You can explicitly configure its bean name by :

   @MockBean(name="SecurityService")
   private SecurityService securityService;

which should solve your problem.

But better yet , don't mock SecurityService because it is a part of your application security setting and is not a kind of collaborator or dependency of the controller under test. Use the real instance can make the test case more close to the actual production codes.

@WebMvcTest(TestController.class)
@Import({TestController.Config.class})
public class TestControllerTest {
     @Configuration
     public static class Config {
        @Bean(name="SecurityService")
        public SecurityService securityService() {
            return new SecurityService();
        }
     }
}
  • Related