I am trying to have HMAC in springBoot for REST API. The request I send from Postman is
{
"name":"xyz",
"description":"hello world",
"phone":"123456",
"id":"1"
}
it reached my controller and then to the service where I have a function to validate HMAC. In the controller I pass the signature as the header and payload in the requestBody
@RestController
public class UserController {
@Autowired
UserInterface userInterface;
@PostMapping(value = "/" ,consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public void createUser(@RequestBody User user, @RequestHeader Map<String, String> headers) {
userInterface.hmacValidation(user, headers);
}
}
@Service
public class UserService implements UserInterface {
public void hmacValidation(User requestBody, Map<String, String> header) {
var headerSignature = header.get("signature");
var payload = getRequestBodyAsString(requestBody);
String result = Hashing.hmacSha256("12345".getBytes(StandardCharsets.UTF_8)).hashString(payload,StandardCharsets.UTF_8).toString();
}
private String getRequestBodyAsString(User requestBody) {
var mapper = new ObjectMapper();
String payload = null;
try {
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
payload = mapper.writeValueAsString(requestBody);
} catch (JsonProcessingException e) {
}
return payload;
}
}
here from the getRequestBodyAsString(User requestbody) function the output I get is a shuffled/rearranged JSON request which generates different Signature which then mismatches the signature client is sending.
the payload that is converted back from UserObject:
{"name":"xyz","id":"1","description":"hello world","phone":"123456"}
public class User {
private String name;
private String id;
private String description;
private String phone;
}
The client can send the request in any order but I have to validate signature regardless of the order the request comes in
Is there any other way to validate HMAC?
You should not deserialize if you want to take hash value. Use string or byte for the request. And map it to your pojo later on once you have the hash
For ex :
public @ResponseBody String controllerMethod(HttpServletRequest httpReq,
HttpServletResponse httpResponse) {
BufferedReader bufferedReader;
StringBuilder sb;
sb = new StringBuilder();
bufferedReader = httpReq.getReader();
char[] charBuffer = new char[128];
int bytesRead;
while ((bytesRead = bufferedReader.read(charBuffer)) != -1) {
sb.append(charBuffer, 0, bytesRead);
}
String reqBody = sb.toString();
}
Use reqBody
to get your hashed value.