测试Spring端点时如何防止NestedServletException?

时间:2017-07-04 08:20:55

标签: spring unit-testing integration-testing mockmvc

我正在尝试测试一些使用@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:Request   处理失败;嵌套异常是   org.springframework.security.access.AccessDeniedException:   该资源的范围不足

我的问题是如何防止此异常干扰我的测试用例?

3 个答案:

答案 0 :(得分:9)

我按照link执行了春季安全测试用例。除了在NestedServletException中嵌套原始异常的问题之外,事情很好。 我没有找到任何直接解决方法,但AspectJ帮助我以更清洁的方式处理这个问题。

我们可以使用Assertions类的静态assertThatThrownBy()方法。此方法返回一个AbstractThrowableAssert对象,我们可以使用该对象为抛出的异常编写断言。

捕获methodThatThrowsException()方法抛出的异常的代码如下所示:

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

感谢this优秀的博客,您可以在其中找到更多详细信息。

我在测试用例中处理此问题的方法是(通过测试用例代码行):

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

这样你的测试用例就可以断言嵌套在NestedServletException中的实际AccessDeniedException。

答案 1 :(得分:2)

我有类似的情况,并通过使用抑制了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);

似乎可行。

答案 2 :(得分:1)

我通过为该异常添加@ExceptionHandler来解决了该问题。似乎MockMvc如果抛出实际异常,则意味着您不“处理”这种不理想的情况。