Search code examples
javaspring-securityjwttokenhamcrest

MockMVC - How to check the content of a JWT token in a spring security integration test with org.hamcrest.Matcher


I amd getting JWT tokens as response from a MockMvc request. I want to check the content of this response:

mockMvc.perform(post("/authorize")
        .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeEmailAndPassword("[email protected]", "1111"))
        .contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE)
        .params(params)
        .accept(MediaType.APPLICATION_JSON))
    .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
    .andDo(print())
    .andExpect(status().isOk())
;

The result will be:

{
    "id_token": "............(long Base64 string)"
}

When we decode the token with JWT.io, we see this:

{
  "sub": "cc15a160-2d62-4091-b89a-117e77346a58",
  "nbf": 1543846725,
  "auth_level": "trusted",
  "iss": "http://localhost:9090/",
  "exp": 1543847724,
  "iat": 1543846725,
  "nonce": "random_string",
  "jti": "64b8b6e3-5cd0-4242-bcea-2c5d498d64c1"
}

All is fine, but I want to do something like:

.andExpect(jsonPath("$.id_token", Matchers.not(null)))
.andExpect(decodeJWT(jsonPath("$.id_token")).getValueOf("nonce"), Matchers.is("random_string"));

How can I do this?


Solution

  • Well I find the answer myself... Basically, with org.hamcrest.Matcher is not possible, but we can separate the response into pieces and map them to DTOs.

    First, I do some status and basic check, then return the response as MvcResult:

    MvcResult result = mockMvc.perform(post("/authorize")
                    .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeEmailAndPassword("[email protected]", "1111"))
                    .contentType(MediaType.APPLICATION_JSON_UTF8)
                    .content(content)
                    .accept(MediaType.APPLICATION_JSON))
                .andDo(print())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id_token", Matchers.notNullValue()))
                .andReturn();
    

    Then, I create some DTOs for Jackson deserialization:(remember to create the class not as inner class, because Jackson would complain about "only can use no-arg constructor of inner non-static class")

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
    class TokenResponseDTO implements Serializable {
        //@JsonProperty("id_token")
        private String idToken;
    }
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
    class JWTPayloadDTO implements Serializable {
        private String aud;
        private String sub;
        private String nbf;
        private String authLevel;
        private String iss;
        private Long exp;
        private Long iat;
        private String nonce;
        private String jti;
    }
    

    At last, JWT token parsing is much easier than I thought:

    String token = mapper.readValue(result.getResponse().getContentAsString(), TokenResponseDTO.class).getIdToken();
    JWSObject jwsObject = JWSObject.parse(token);
    JWTPayloadDTO payload = mapper.readValue(jwsObject.getPayload().toString(), JWTPayloadDTO.class);
    
    Assert.assertEquals("random_string", payload.getNonce());
    ... // other checks