Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to prevent NestedServletException when testing Spring endpoints?

Tags:

I am trying to test the security configuration of some of my endpoints which are secured with @PreAuthorize(#oauth2.hasScope('scope'). When accessing such an endpoint via Postman with a access token that does not have the required scope, the following is returned with HTTP status code 403 (forbidden):

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

Which is the expected behaviour that I want.

When trying to test this configuration, Springs NestedServletException interferes with my test case before it can complete with my expected result.

This is a simplified version of the controller I want to test:

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

And this is the corresponding test case:

@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());      } } 

The exception that is thrown is the following (which is expected):

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.security.access.AccessDeniedException: Insufficient scope for this resource

My question is how can I prevent this exception from interfering with my test case?

like image 956
Philipp Jahoda Avatar asked Jul 04 '17 08:07

Philipp Jahoda


People also ask

What is nested servlet exception?

Class NestedServletExceptionSubclass of ServletException that properly handles a root cause in terms of message and stacktrace, just like NestedChecked/RuntimeException does. Note that the plain ServletException doesn't expose its root cause at all, neither in the exception message nor in printed stack traces!

How do you write Junit for ExceptionHandler?

Here is the outline of the class: @Controller public class MyController{ @RequestMapping(..) public void myMethod(...) throws NotAuthorizedException{...} @ExceptionHandler(NotAuthorizedException.


2 Answers

I did spring security test cases by following this link. Things worked fine except this issue of nesting original exception in NestedServletException. I did not find any direct way to figure this out but AspectJ helped me in handling this in a cleaner way.

We can use the static assertThatThrownBy() method of the Assertions class. This method returns an AbstractThrowableAssert object that we can use to write assertions for the thrown exception.

The code that captures an exception thrown by the methodThatThrowsException() method looks as follows:

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

Thanks to this excellent blog where you can find additional details.

The way in which I handled this in my test case would be (by taking your test case codeline):

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

That way your test case would be able to assert actual AccessDeniedException that is nested in NestedServletException.

like image 51
Vishal Avatar answered Sep 20 '22 08:09

Vishal


I had a similar case and suppressed the NestedServletException by using @Test(expected = NestedServletException.class) and then I was able to get hold of the MvcResult and do further asserts on it as in other tests like:

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

It seemed to work.

like image 34
ariadni.papageorgiou Avatar answered Sep 20 '22 08:09

ariadni.papageorgiou