sun.security.validator.ValidatorException while invoking graph API - spring

I am getting below exception while invoking the Microsoft graph API by using swagger in the local.
Getting below exception:
feign.RetryableException: sun.security.validator.ValidatorException: PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path
#Override
public int addUser(String token, AddUserRequest request) {
Response response = null;
int status = 0;
try {
long time1 = System.currentTimeMillis();
response = graphAPIFeignClient.addUser("Bearer " + token, request);
log.info("{}:: Time taken to call Graph Api is {}", loggingComponentName,
(System.currentTimeMillis() - time1));
if (response.status() == 200) {
status = response.status();
} else if (response.status() == 404) {
throw new SearchResultNotFoundException("get users not found");
} else {
log.error("{}:: Graph Api failed:: status code {}",
loggingComponentName, 500);
throw new AccessManagementException(HttpStatus.valueOf(500), "Graph Api failed");
}
} catch (FeignException ex) {
log.error("{}:: Graph Api failed:: status code {} & message {}",
loggingComponentName, ex.status(), ex.getMessage());
throw new AccessManagementException(HttpStatus.valueOf(ex.status()), "Graph Api failed");
}
return status;
}
//feign client declaration
#PostMapping(value = "/v1.0/users")
#RequestLine("POST v1.0/users")
//#Headers({"Authorization: {authorization}","Content-Type: application/json"})
Response addUser(#RequestHeader(HttpHeaders.AUTHORIZATION) String authorisation,
#RequestBody AddUserRequest request);
Same is working fine with the postman call.enter image description here

Related

Spring WebClient - Stop retrying if an exception is thrown in the doOnError

I have the following code to make a request that is going to be retried a max number of times. This request needs an authorization header and I'm caching this information to prevent this method to call the method to retrieve this information every time.
What I'm trying to do is:
When calling myMethod I first retrieve the login information for the service I'm calling, in most cases that will come from the cache when calling the getAuthorizationHeaderValue method.
In the web client, if the response to send this request returns a 4xx response I need to login again to the service I'm calling, before retrying the request. For that, I'm calling the tryToLoginAgain method to set the value for the header again.
After doing that the retry of the request should work now that the header has been set.
If by any chance the call to login again fails I need to stop retrying as there no use on retrying the request.
public <T> T myMethod(...) {
...
try {
AtomicReference<String> headerValue = new AtomicReference<>(loginService.getAuthorizationHeaderValue());
Mono<T> monoResult = webclient.get()
.uri(uri)
.accept(MediaType.APPLICATION_JSON)
.header(HttpHeaders.AUTHORIZATION, headerValue.get())
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response -> throwHttpClientLoginException())
.bodyToMono(type)
.doOnError(HttpClientLoginException.class, e -> tryToLoginAgain(headerValue))
.retryWhen(Retry.backoff(MAX_NUMBER_RETRIES, Duration.ofSeconds(5)));
result = monoResult.block();
} catch(Exception e) {
throw new HttpClientException("There was an error while sending the request");
}
return result;
}
...
private Mono<Throwable> throwHttpClientLoginException() {
return Mono.error(new HttpClientLoginException("Existing Authorization failed"));
}
private void tryToLoginAgain(AtomicReference<String> headerValue) {
loginService.removeAccessTokenFromCache();
headerValue.set(loginService.getAuthorizationHeaderValue());
}
I have some unit tests and the happy path works fine (unauthorized the first time, try to login again and send the request again) but the scenario where the login doesn't work at all is not working.
I thought that if the tryToLoginAgain method throws an Exception that would be caught by the catch I have in myMethod but it never reaches there, it just retries the request again. Is there any way to do what I want?
So at the end I found a way of doing what I wanted and now the code looks like this:
public <T> T myMethod() {
try {
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(getAuthorizationHeaderValue());
final RetryBackoffSpec retrySpec = Retry.backoff(MAX_NUMBER_RETRIES, Duration.ofSeconds(5))
.doBeforeRetry(retrySignal -> {
//When retrying, if this was a login error, try to login again
if (retrySignal.failure() instanceof HttpClientLoginException) {
tryToLoginAgain(headers);
}
});
Mono<T> monoResult = Mono.defer(() ->
getRequestFromMethod(httpMethod, uri, body, headers)
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response -> throwHttpClientLoginException())
.bodyToMono(type)
)
.retryWhen(retrySpec);
result = monoResult.block();
} catch (Exception e) {
String requestUri = uri != null ?
uri.toString() :
endpoint;
log.error("There was an error while sending the request [{}] [{}]", httpMethod.name(), requestUri);
throw new HttpClientException("There was an error while sending the request [" + httpMethod.name() +
"] [" + requestUri + "]");
}
return result;
}
private void tryToLoginAgain(HttpHeaders httpHeaders) {
//If there was an 4xx error, let's evict the cache to remove the existing access_token (if it exists)
loginService.removeAccessTokenFromCache();
//And let's try to login again
httpHeaders.setBearerAuth(getAuthorizationHeaderValue());
}
private Mono<Throwable> throwHttpClientLoginException() {
return Mono.error(new HttpClientLoginException("Existing Authorization failed"));
}
private WebClient.RequestHeadersSpec getRequestFromMethod(HttpMethod httpMethod, URI uri, Object body, HttpHeaders headers) {
switch (httpMethod) {
case GET:
return webClient.get()
.uri(uri)
.headers(httpHeaders -> httpHeaders.addAll(headers))
.accept(MediaType.APPLICATION_JSON);
case POST:
return body == null ?
webClient.post()
.uri(uri)
.headers(httpHeaders -> httpHeaders.addAll(headers))
.accept(MediaType.APPLICATION_JSON) :
webClient.post()
.uri(uri)
.headers(httpHeaders -> httpHeaders.addAll(headers))
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(body);
case PUT:
return body == null ?
webClient.put()
.uri(uri)
.headers(httpHeaders -> httpHeaders.addAll(headers))
.accept(MediaType.APPLICATION_JSON) :
webClient.put()
.uri(uri)
.headers(httpHeaders -> httpHeaders.addAll(headers))
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(body);
case DELETE:
return webClient.delete()
.uri(uri)
.headers(httpHeaders -> httpHeaders.addAll(headers))
.accept(MediaType.APPLICATION_JSON);
default:
log.error("Method [{}] is not supported", httpMethod.name());
throw new HttpClientException("Method [" + httpMethod.name() + "] is not supported");
}
}
private String getAuthorizationHeaderValue() {
return loginService.retrieveAccessToken();
}
By using Mono.defer() I can retry on that Mono and make sure I change the headers I'll use with the WebClient. The retry spec will check if the exception was of the HttpClientLoginException type, thrown when the request gets a 4xx status code and in that case it will try to login again and set the header for the next retry. If the status code was different it will retry again using the same authorization.
Also, if there's an error when we try to login again, that will be caught by the catch and it won't retry anymore.

RestTemple custom error handler not catching ResourceAccessException

I am using RestTemplate to make Http connection to get data from external APIs. For this I have implemented a custom error handler and set it on the restTemplate object. Below is my custom error handler
public class CustomResponseErrorHandler implements ResponseErrorHandler {
public boolean hasError(ClientHttpResponse response) throws IOException {
int rawStatusCode = response.getRawStatusCode();
if (rawStatusCode / 200 != 1) {
LOG.debug("HTTPS hasError - " + rawStatusCode + "; " + response.getStatusText() + "; " + response.getStatusCode());
return true;
}
return false;
}
public void handleError(ClientHttpResponse response) throws IOException {
int rawStatusCode = response.getRawStatusCode();
LOG.debug("HTTPS handleError - " + rawStatusCode + "; " + response.getStatusText() + "; " + response.getStatusCode());
}
}
and my RestTemplateUtils class looks like below
public class RestTemplateUtils {
RestTemplate restTemplate;
public ResponseEntity<String> restGet(String url) {
restTemplate.setErrorHandler(new CustomResponseErrorHandler());
ResponseEntity<String> response= restTemplate.getForEntity(url, String.class);
return response;
}
}
I expect that any error that gets thrown during the restTemplate.getForEntity() call should be caught and logged by the CustomResponseErrorHandler but that is not the case. When I pass in a non-existent url ResponseEntity<String> response= restTemplate.getForEntity(url, String.class); throws ResourceAccessException. What should I do if I want my custom error handler to catch a 404 in such a case? Am I missing something here or misunderstanding how custom error handler should work here?
If you completely give a non existing url then I don't think the code is going to the point where error handler is executed;
Looking at RestTemplate#doExecute
doExecute(URI url, #Nullable HttpMethod method, #Nullable RequestCallback requestCallback,
#Nullable ResponseExtractor<T> responseExtractor)
code
try {
ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null) {
requestCallback.doWithRequest(request);
}
response = request.execute();
handleResponse(url, method, response);
return (responseExtractor != null ? responseExtractor.extractData(response) : null);
}
handleResponse is where the error handler is looked for but I think yours is erroring out at request.execute();
Provide some non existing url on the server api path, then you would recieve a 404 from the server and your custom error handler gets executed.

how to decode error code in spring boot fiegn client

I have to implement a error decode for feign client I went to through this link
in that, decode function needs response but how to get this response from fiegn client, below is my feign client.
#FeignClient(name="userservice")
public interface UserClient {
#RequestMapping(
method= RequestMethod.GET,
path = "/userlist")
String getUserByid(#RequestParam(value ="id") String id);
}
I call feign client like this, whenever there is a error FeignException will be caught, but I want to get the proper error codes, like 400, 403 etc .
try {
String str = userClient.getUserByid(id);
return str;
}
catch(FeignException e)
{
logger.error("Failed to get user", id);
}
catch (Exception e)
{
logger.error("Failed to get user", id);
}

Redirect to another URL and back for Certifcate base Login

Currently i developing an application which have two routes which are in different domain.
Example :
Route 1: https://test.apps.com
Route 2: https://test.cert-apps.com
Users uses Route 1 to access the application. In the Login page there is an option for Certificate based login . But certificate based authentication is only enabled in route 2.
So how do i do the certificate based authentication by redirecting from Route 1 to Route 2 and once client is authenticated redirect to route 1.
Following are the code that i currently using which are not working:
#Controller
#RequestMapping("/login")
public class LoginContr {
#RequestMapping("/certificate")
public String message(HttpServletRequest request, HttpServletResponse response) {
try {
sendGET();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "login";
}
#RequestMapping("/extractcertificate")
private String extractEmailFromCertificate(HttpServletRequest request) {
String email = "";
//Code to extract email from certificate
return email;
}
private static void sendGET() throws IOException {
URL obj = new URL("https://test.cert-apps.com/login/extractcertificate");
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("GET");
int responseCode = con.getResponseCode();
System.out.println("GET Response Code :: " + responseCode);
if (responseCode == HttpURLConnection.HTTP_OK) { // success
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
// print result
System.out.println(response.toString());
} else {
System.out.println("GET request not worked");
}
}
}
The above code gives the error:
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
sendGet() method sends a request to Route 2 and calls /extractcertificate webservice to do certificate based authentication and once its done it will retrive the email address.
Technology using for app: Java,Spring,Angualar 4, Cloud Foundary
Is there any alternative to do this or any inbuilt implementation , any tips would be great advantage..

How get the bad request message in retrofit 2

My service return Status:
400 Bad Request
with a message:
Invalid credentials(wrong Password)
I can see this in Postman.
The problem is that i don't know how get the
"400 Bad Request message Invalid credentials(wrong Password)" from my android code.
Here is my code:
call.enqueue(new Callback<JWToken>() {
#Override
public void onResponse(Call<JWToken> call, Response<JWToken> response) {
String strMsg = response.message(); // I get message "Bad Request" without the string "Invalid credentials(wrong Password)"
}
How can I get the message "Invalid credentials(wrong Password)" that my web api returns and I can see in Postman?
Thanks a lot!
Try looking in the errorBody() of the Response. Note that errorBody will only be non-null if isSuccessful is false.
call.enqueue(new Callback<JWToken>() {
#Override
public void onResponse(Call<JWToken> call, Response<JWToken> response) {
if (!response.isSuccessful()) {
String strMsg = response.message();
try {
String errorContent = response.errorBody().string();
} catch (IOException e) {
e.printStackTrace();
}
}
}

Resources