Springboot Java AuthorisationHeader error 401 for Linux

Hi, My name is Jorge and I'm trying to connect with World check API. The issue is: we are always getting a 401 authorisation error, but just for linux.

We have our service running in windows and we dont have any issue running it in our local environment (Windows).

When we deploy it to our DEV environment which is in Linux we are getting a 401 error, the same keys, the same code, without any change.

We already tried some different logics. Seems to be a problem with break line - "\n" in the Data to Sign string. We tried "\n" or System.lineSeparator().

//we need to keep System.lineSeparator for linux, we cannot put "\n" directly in the string.
String breakLine = System.lineSeparator();
switch (method){
case GET:
requestDataToSign = "(request-target): get " + gatewayUrl + path + breakLine +
"host: " + gatewayHost + breakLine +
"date: " + date;
break;
case POST:
requestDataToSign = "(request-target): post " + gatewayUrl + path + breakLine +
"host: " + gatewayHost + breakLine +
"date: " + date;
if(StringUtils.hasLength(bodyString)){
requestDataToSign +=
breakLine + "content-type: " + MediaType.APPLICATION_JSON_VALUE + breakLine +
"content-length: " + contentLength + breakLine +
bodyString;
requestDataHeaders=Constants.REQUEST_HEADERS_WITH_BODY;
}

}
log.debug("Request sign {}", requestDataToSign);
return signRequestData(requestDataToSign,requestDataHeaders,apiKey,apiSecret);


private String signRequestData(String dataToSign, String requestHeaders, String apiKey, String apiSecret) {
String hmac = generateAuthHeader(dataToSign, apiSecret);
return "Signature keyId=\"" + apiKey + "\",algorithm=\"hmac-sha256\",headers=\"(request-target) " + requestHeaders + "\",signature=\"" + hmac + "\"";
}


private String generateAuthHeader(String dataToSign, String apiSecret)
{
String hash = "";
try {

Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(apiSecret.getBytes(), "HmacSHA256");
sha256_HMAC.init(secret_key);
log.info("Charset.defaultCharset().toString() {}" , Charset.defaultCharset().toString());
hash = Base64.getEncoder().encodeToString(sha256_HMAC.doFinal(dataToSign.getBytes(StandardCharsets.UTF_8)));
}
catch (Exception e){
log.error(e.getMessage(), (Object[]) e.getStackTrace());
}
return(hash);
}


How do you suggest to create this Authorization String? to work in windows and linux.

Would be great if it's possible to have a teams call to be easier to show and communicate this issue. We are struggling with this and we have a data for the deploy.


Thank you

Jorge Medina


Best Answer

  • Hi @jorgemedina

    Thanks for sharing the requested details.

    When you get the 401 which means that the request has failed an authorization check. This can happen for a variety of reasons, such as an invalid or expired API key, an invalid HMAC signature or a request timing issue/problem with the Date header value.

    Therefore, I have validated your Linux request header and response header and found out that the clock is not synchronized as you can see that there is ~2 minutes difference between the request header and the response header (it shouldn't be more than 30 seconds), hence you are getting the 401 error. You should ensure a correctly synchronized clock is used to generate request timestamps.
    1640171881986.png

    Thanks
    Vivek Kumar Singh

Answers

  • Hi @jorgemedina

    Thanks for reaching out to us!

    We are working with our internal team on above reported issue. We will get back to with our findings Shortly.

    In the meantime, can you please help us with the "Complete request header and Response header"

    Thanks
    Vivek Kumar Singh

  • Hi Kumar,

    Sure,

    Windows:

     ---> GET https://api-worldcheck.refinitiv.com/v2/groups HTTP/1.1
    Accept: application/json
    Authorization: Signature keyId="af0806ab-982c-4b42-bd2a-edd82f17b5d1",algorithm="hmac-sha256",headers="(request-target) host date",signature="XueN09p/CsTFrlDy3cOHFYj7YbuknikE0Vd8amZCdII="
    Cache-Control: no-cache
    Content-Type: application/json
    Date: Wed, 22 Dec 2021 10:45:12 GMT
    END HTTP (0-byte body)

    <--- HTTP/1.1 200 (578ms)
    cache-control: no-cache, no-store, max-age=0, must-revalidate
    connection: keep-alive
    content-type: application/json;charset=UTF-8
    date: Wed, 22 Dec 2021 10:45:17 GMT
    expires: 0
    pragma: no-cache
    strict-transport-security: max-age=15552000, includeSubdomains
    transfer-encoding: chunked
    x-content-type-options: nosniff
    x-frame-options: DENY
    x-xss-protection: 1; mode=block

    [{"id":"XXXXXXXXXXXXXXX","name":"KPMG & Associados - Sociedade de Revisores Oficiais de Contas S.A.- API - (Pilot)","parentId":null,"hasChildren":false,"status":"ACTIVE","children":[]}]

    Linux:

    ---> GET https://api-worldcheck.refinitiv.com/v2/groups HTTP/1.1
    Accept: application/json
    Authorization: Signature keyId="af0806ab-982c-4b42-bd2a-edd82f17b5d1",algorithm="hmac-sha256",headers="(request-target) host date",signature="UivhRONBMcIZ4EFzxLZe9JfEHc0Urp7rmTtgTpzH1yA="
    Cache-Control: no-cache
    Content-Type: application/json
    Date: Wed, 22 Dec 2021 11:43:44 GMT
    ---> END HTTP (0-byte body)

    <--- HTTP/1.1 401 (246ms)

    authorization: WWW-Authenticate: Signature realm="World-Check One API",algorithm="hmac-sha256",headers="(request-target) host date content-type content-length"
    connection: keep-alive
    date: Wed, 22 Dec 2021 10:45:16 GMT
    strict-transport-security: max-age=15552000, includeSubdomains
    transfer-encoding: chunked

    <--- END HTTP (0-byte body)

    The request was made to both machines, as you can see by the Date. (Our linux machine (Dev environment) is 1 hour ahead against our local environment (Windows)).


    The following code is the one who trigger the first block of code in my question, where we have "String breakLine = System.lineSeparator(); ":

    public void apply(RequestTemplate requestTemplate) {
    //Get the current date
    String date = apiAuthorisationManager.getDateHeader();
    String authorisationSignatureString = "";
    //Check if exist body or not in the request
    String bodyString = null;
    if(requestTemplate.body() != null && requestTemplate.body().length > 0){
    bodyString = new String(requestTemplate.body());
    }
    Collection<String> contentLengthValues = requestTemplate.headers().get(HttpHeaders.CONTENT_LENGTH);
    authorisationSignatureString = apiAuthorisationManager.generateAuthorisationHeader(RequestMethod.valueOf(requestTemplate.method()),
    requestTemplate.path(),
    date,
    bodyString,
    contentLengthValues != null ? contentLengthValues.stream().findFirst().orElse(null):"");

    requestTemplate.header(HttpHeaders.CACHE_CONTROL, "no-cache");
    requestTemplate.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
    requestTemplate.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
    requestTemplate.header(HttpHeaders.AUTHORIZATION, authorisationSignatureString);
    requestTemplate.header(HttpHeaders.DATE, date);

    }

    The main difference now that we have is:

    "String breakLine = "\n";" 

    instead of

    String breakLine = System.lineSeparator();

    Because with "String breakLine = System.lineSeparator(); " wont work using our windows environment and either using our linux environment.

  • Hi Kumar,


    Thank you for your time.

    We thought that the Data which we have in the request header was validated against the date inside the signature string.

    Yes you are right and we will work according your suggestion.

    We already did some tests to confirm it, and it works.


    Once again thank you for your time.

  • Hi @jorgemedina


    Glad to know that our suggestion resolved your issue.

    Thanks
    Vivek Kumar Singh