Как предотвратить NestedServletException при тестировании конечных точек Spring?

Я пытаюсь проверить конфигурацию безопасности некоторых своих конечных точек, которые защищены с помощью @PreAuthorize(#oauth2.hasScope('scope'). При доступе к такой конечной точке через Postman с токеном доступа, который не имеет требуемой области, возвращается следующее с кодом состояния HTTP 403 (запрещено):

{
    "error": "insufficient_scope",
    "error_description": "Insufficient scope for this resource",
    "scope": "scope"
}

Это ожидаемое поведение, которого я хочу.

При попытке протестировать эту конфигурацию Springs NestedServletException вмешивается в мой тестовый пример до того, как он завершится с моим ожидаемым результатом.

Это упрощенная версия контроллера, который я хочу протестировать:

@RestController
@RequestMapping(value = "/api")
public class OauthTestingResource {

    @PreAuthorize(#oauth2.hasScope('scope'))
    @RequestMapping(value = "/scope", method = RequestMethod.GET)
    public void endpoint() {
        // ...
    }
}

И это соответствующий тестовый пример:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = MyApplication.class)
@WebAppConfiguration
public class AuthorizationTest {

    @Autowired
    protected WebApplicationContext webApplicationContext;

    protected SecurityContext securityContext = Mockito.mock(SecurityContext.class);

    @Before
    public void setup() throws Exception {

        this.mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
        SecurityContextHolder.setContext(securityContext);
    }

    protected Authentication createMockAuth(Client client) {

        final List<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority("ROLE_USER"));

        final Authentication pwAuth = new UsernamePasswordAuthenticationToken("testuser", "testpw", authorities);

        final TokenRequest request = new TokenRequest(new HashMap<>(), client.getClientId(), client.getScopes(), "password");

        final OAuthClient oauthClient = new OAuthClient(client, GrantType.PASSWORD);

        return new OAuth2Authentication(request.createOAuth2Request(oauthClient), pwAuth);
    }
    @Test
    public void testAppScope() throws Exception {

        final Client client = new Client("id1", "secret1");

        client.setScope("scope");
        Mockito.when(securityContext.getAuthentication()).thenReturn(createMockAuth(client));
        // this test passes
        mvc.perform(get("/api/scope")).andExpect(status().isOk()); 

        client.setScope("other_scope");
        Mockito.when(securityContext.getAuthentication()).thenReturn(createMockAuth(client));
        // NestedServletException thrown here
        mvc.perform(get("/api/scope")).andExpect(status().isForbidden()); 
    }
}

Возникает следующее исключение (которое ожидается):

org.springframework.web.util.NestedServletException: Ошибка обработки запроса; вложенное исключение - org.springframework.security.access.AccessDeniedException: недостаточная область действия для этого ресурса

У меня вопрос: как я могу предотвратить вмешательство этого исключения в мой тестовый пример?


person Philipp Jahoda    schedule 04.07.2017    source источник
comment
В моем случае проблема заключалась в двух экземплярах @Configuration, которые расширяют WebSecurityConfigurerAdapter. При этом вызывается его метод init, и может быть случай, когда у вас есть два SecurityFilterChains и ExceptionTranslationFilter не вызывается mockMvc. Этот фильтр отвечает за преобразование между AccessDeniedException и желаемым кодом ответа.   -  person Michal Borek    schedule 24.07.2018


Ответы (3)


Я выполнил весенние тесты безопасности, следуя этому ссылка. Все работало нормально, за исключением этой проблемы с вложением исходного исключения в NestedServletException. Я не нашел прямого способа понять это, но AspectJ помог мне справиться с этим более чистым способом.

Мы можем использовать статический метод assertThatThrownBy () класса Assertions. Этот метод возвращает объект AbstractThrowableAssert, который мы можем использовать для записи утверждений для возникшего исключения.

Код, фиксирующий исключение, вызванное методом methodThatThrowsException (), выглядит следующим образом:

assertThatThrownBy(() -> methodThatThrowsException())
.isExactlyInstanceOf(DuplicateEmailException.class);

Благодаря это отличное блог, где вы можете найти дополнительную информацию.

То, как я справился с этим в моем тестовом примере, было бы (взяв кодовую строку вашего тестового примера):

org.assertj.core.api.Assertions.assertThatThrownBy(() -> mvc.perform(get("/api/scope")).andExpect(status().isOk())).hasCause(new AccessDeniedException("Access is denied"));

Таким образом, ваш тестовый пример сможет подтвердить фактическое исключение AccessDeniedException, вложенное в NestedServletException.

person Vishal    schedule 01.08.2017

У меня был аналогичный случай, и я подавил исключение NestedServletException с помощью @Test(expected = NestedServletException.class), а затем я смог получить MvcResult и выполнить дальнейшие утверждения на нем, как и в других тестах, например:

// then
MvcResult result = resultActions.andExpect(status().isServiceUnavailable()).andReturn();
String message = result.getResponse().getContentAsString();
assertThat(message).contains("ABC");
assertThat(result.getResolvedException().getClass()).isEqualTo(XYZ.class);

Казалось, сработало.

person ariadni.papageorgiou    schedule 17.09.2020

Я исправил это, добавив @ExceptionHandler для этого исключения. Похоже, что если MockMvc генерирует фактическое исключение, это означает, что вы не «обрабатываете» этот случай, что не идеально.

person Sam    schedule 24.08.2019