Retrofit2 response code=401, message=Unauthorized. How to solve? - java

I use Retrofit2 to make REST API requests. I have my dummy server (that runs with spring boot) on my machine:
#RestController
class SecureServiceController {
private int counter = 1;
#RequestMapping(value = "/nnrf-nfm/v1/nf-instances/bee75393-2ac3-4e60-9503-854e733309d4", method = RequestMethod.PUT)
public ResponseEntity<NFProfile> nNrfNfManagementNfRegister() {
System.out.println(counter++ + ". Got NrfClient register request. " + new Date());
NFProfile nfProfile = new NFProfile();
nfProfile.setHeartBeatTimer(2);
ResponseEntity<NFProfile> responseEntity = ResponseEntity.status(201).body(nfProfile);
return responseEntity;
}
}
When client make request from the same machine it works. But when client make request from remote machine I have error response:
Response{protocol=http/1.1, code=401, message=Unauthorized, url=https://myhostname:8443/nnrf-nfm/v1/nf-instances/bee75393-2ac3-4e60-9503-854e733309d4}
Response error body: <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"><html><head><title>Error</title></head><body><h1>Error</h1></body></html>
I've read that such error means that client don't have the rights to access and need to add access token. But my server does not ask any access token (at least explicitly) and it should not ask it.
How to solve this problem?
My apiClient:
public class ApiClient {
private Map<String, Interceptor> apiAuthorizations;
private Builder okBuilder;
private retrofit2.Retrofit.Builder adapterBuilder;
private JSON json;
//a lot setters and getters
public <S> S createService(Class<S> serviceClass) {
return this.adapterBuilder.client(this.okBuilder.build()).build().create(serviceClass);
}
public void configureFromOkclient(OkHttpClient okClient) {
this.okBuilder = okClient.newBuilder();
this.addAuthsToOkBuilder(this.okBuilder);
}
}
my interface:
public interface NfInstanceIdDocumentApi {
#Headers({"Content-Type:application/json"})
#PUT("nf-instances/{nfInstanceID}")
Call<NFProfile> registerNFInstance(#Body NFProfile body, #Path("nfInstanceID") UUID nfInstanceID, #Header("Content-Encoding") String contentEncoding, #Header("Accept-Encoding") String acceptEncoding);
}
How I do call:
OkHttpClient okHttpClient= ClientFactory.createClient();
ApiClient client = new ApiClient();
client.configureFromOkclient(okHttpClient);
NFProfile body = getNfProfile();
String baseUri = getBaseUri();
UUID uuid = getUUID();
//create call
client.getAdapterBuilder().baseUrl(baseUri);
NfInstanceIdDocumentApi service = client.createService(NfInstanceIdDocumentApi.class);
Call<NFProfile> call = service.registerNFInstance(body, uuid, null, null);
//make call
Response<NFProfile> response = call.execute();
UPD
I found the problem. Server was running on Windows machine and firewall blocked incoming requests.

Related

How to provide credentials for Spring app via OkHttp and Retrofit

I develop spring non-web service which has webflux basic authentication.
Its working and i am able to successfully use cURL to reach certain endpoints like:
curl -I --user user:password http://localhost:5051/service/v1/health
or
curl -I http://user:password#localhost:5051/service/v1/health
But now im trying to send post via other services which use OkHttp and Retrofit to communicate with my spring service.
This process is more complicated, in main apllication, the OkHttpCllient is created and then separate, Retrofit service client provider is called.
The main application:
httpUrl = url
.newBuilder()
.username(Username) // first approach
.password(Password)
{..}
.build();
ClientProvider
.getInstance(httpUrl, Username, Password)
.subscribeOn(Schedulers.io())
.subscribe(new ProfiledSingleObserver<Event>() {
#Override
public void profiledOnError(#NotNull Throwable e) {
LOG.error("Transport layer error", e);
resultFuture.completeExceptionally(e);
}
#Override
public void profiledOnSuccess(#NotNull Event event) {
resultFuture.complete(Collections.singleton(event));
}
});
public class ClientProvider {
private static HttpUrl httpUrl = null;
private static Service instance = null;
private ClientProvider() {
}
public static Service getInstance(final HttpUrl url, String username, String password) {
instance = Client.createService(url, HttpClient.getInstance(username, password));
return instance;
}
}
public static OkHttpClient getInstance(String username, String password) {
if (instance == null) {
synchronized (lock) {
instance = new OkHttpClient.Builder()
// .authenticator(new Authenticator() { // second approach
// #Override
// public Request authenticate(#Nullable Route route, #NotNull Response response) throws IOException {
// return response
// .request().newBuilder()
// .header("Authorization", Credentials.basic(username, password))
// .build();
// }
// })
// .addInterceptor(new BasicAuthInterceptor(username,password)) // third approach
.addInterceptor(createHttpBodyLoggingInterceptor())
.addInterceptor(createHttpBasicLoggingInterceptor())
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS)
.dispatcher(createDispatcher())
.connectionPool(createConnectionPool())
.build();
}
}
return instance;
}
public class BasicAuthInterceptor implements Interceptor {
private final String credentials;
public BasicAuthInterceptor(String user, String password) {
this.credentials = Credentials.basic(user, password);
}
#NotNull
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request authenticatedRequest = request.newBuilder()
.header(HttpHeaders.AUTHORIZATION, credentials).build();
return chain.proceed(authenticatedRequest);
}
}
And the Retrofit service client provider:
public static Service createService(final HttpUrl baseUrl, final OkHttpClient okHttpClient) {
return createRetrofit(baseUrl, okHttpClient).create(Service.class);
}
protected static Retrofit createRetrofit(HttpUrl baseUrl, OkHttpClient client) {
return new Retrofit
.Builder()
.baseUrl(baseUrl)
.client(client)
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.addConverterFactory(JacksonConverterFactory.create())
.build();
}
I was taking 3 different approach to solve this problem, as u can see in the comments next to them.
First was just passing username and password via url to look something like:
http://user:password#localhost:5051/service/v1/health
but even if curl with this notation is working, authentication did not pass.
Second approach was with creating Authenticator in OkHttpClient.Builder().
Still the same results.
And third one was with creating Interceptor also in the OkHttpClient.
In this approach i was able to pass unit tests with stubbed server,
which was impossible in other solutions ( Status 404, Not found):
#BeforeClass
public static void setUpClass() {
serviceStubServer = new StubServer();
whenHttp(serviceStubServer)
.match(Condition.basicAuth("user", "pass"), Condition.post("/service/v1/health"))
.then(
Action.status(HttpStatus.OK_200),
);
serviceStubServer.run();
}
But still i was unable to send records to my Spring service via my main appplication( Status 401, Unauthorized).
My question is, whats the correct way to pass credentials thru OkHttp and Retrofit to be able to reach endpoints in Spring non-web, webflux basic authentication secure application?
Seems like the problem was with singleton approach to create OkHttpClient. This system was operating with two different Spring services and remembered one of the credentials, so he was unable to correctly reach second service.
To solve this problem i create second OkHttpClient class and use the "Interceptor" method to provide authorization data.

Spring boot Async rest call

I am trying to create a scheduler that sends a get request to a web service and gets the count of the items we need. then divide the Total by per_page then send any number of requests needed but requests are Async.
my code is working perfectly fine in my test class but in the main Application im getting two error based on the JDK version
this is my API service :
public interface RestService {
#Async
CompletableFuture<UpcomingEventsResponse> getUpcomingEvents(int page,String day, String token);
UpcomingEventsResponse getUpcomingEvents(String day,String token);
}
my RestService Impl:
#Service
#RequiredArgsConstructor
#Slf4j
public class RestServiceImpl implements RestService {
public static final String UPCOMING_EVENTS_URL = "HTTP://localhost/test";
private final RestTemplate restTemplate;
#Override
public CompletableFuture<UpcomingEventsResponse> getUpcomingEvents(int page,String day, String token) {
String url = createUrl(UPCOMING_EVENTS_URL,createQuery("day",day),createQuery("page", page), createQuery("token", token));
return makeCallAsync(url,HttpMethod.GET,null,UpcomingEventsResponse.class);
}
#Override
public UpcomingEventsResponse getUpcomingEvents(String day,String token) {
String url = createUrl(UPCOMING_EVENTS_URL,createQuery("day",day), createQuery("page", 1), createQuery("token", token));
return makeCall(url,HttpMethod.GET,null,UpcomingEventsResponse.class);
}
private <T> T makeCall(String url,
HttpMethod method,
HttpEntity<Object> httpEntity,
Class<T> outClass) {
return restTemplate.exchange(url, method, httpEntity, outClass).getBody();
}
private <T> CompletableFuture<T> makeCallAsync(String url,
HttpMethod method,
HttpEntity<Object> httpEntity,
Class<T> outClass) {
return CompletableFuture.completedFuture(restTemplate.exchange(url, method, httpEntity, outClass).getBody());
}
}
and this is my scheduler class :
#Component
#RequiredArgsConstructor
#Slf4j
public class EventScheduler {
private final RestService restService;
//TODO change time
#Scheduled(cron = "0 */2 * * * *")
public void getAllEvents(){
long start = System.currentTimeMillis();
//TODO add token from database or env
UpcomingEventsResponse upcomingEvents = restService.getUpcomingEvents(null, "token");
List<ResultsItem> resultsItems = new ArrayList<>(upcomingEvents.getResults());
List<CompletableFuture<UpcomingEventsResponse>> completableFutures = new ArrayList<>();
int repeatTimes = upcomingEvents.getPager().getTotal() / upcomingEvents.getPager().getPerPage();
for (int i = 0; i < repeatTimes; i++) {
int page = i + 2;
CompletableFuture<UpcomingEventsResponse> events = restService.getUpcomingEvents(page, null, "token");
completableFutures.add(events);
}
CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0])).join();
log.info("Elapsed time: " + (System.currentTimeMillis() - start));
completableFutures.forEach(completableFuture -> {
try {
resultsItems.addAll(completableFuture.get().getResults());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
log.info("Size " + resultsItems.size());
log.info("Total " + upcomingEvents.getPager().getTotal());
}
}
and this is the error I'm getting in JDK 8:
peer not authenticated; nested exception is javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
and this is the error on JDK 10 or 11 :
javax.net.ssl.SSLException: No PSK available. Unable to resume
is there a better way to do this? and what is the problem? is this a bug?
The problem was in the Web Service, although I really can't understand the reason for the error in different JDKs. as far as I known this is a known bug and you can read more about it here
this implementation works just fine and you can use Apache HTTP Client with resttemplate but you can't use OkHttp or Apache HttpClient with Spring webflux WebService

How to send multiple HTTP requests from the same service in Java?

Java noob here. I'm trying to develop a web service as per the following diagram.
When a POST request is sent to the REST server, with certain values, the values (being read from a list, in a loop) get inserted in a table (new row with an id). Server returns HTTP 202 Accepted.
To ensure that the resource(with id from 1) is created, a GET request is issued that returns the POJO as Json.
Finally a PATCH request is sent to update a certain column.
I have written a service class that does all three tasks when each API is called individually. I need to implement something that would automatically execute steps 2 and 3 when a POST request is sent to the server. Here's my code so far.
#Path("attachments")
public class FilesService {
private TiedostoService tiedostoService;
private AttachmentService attachmentService;
#GET
#Path("{id}")
#Produces({MediaType.APPLICATION_JSON})
public Response listAttachmentsAsJson(#PathParam("id") Integer attachmentId) throws Exception {
attachmentService = new AttachmentService();
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
Attachment attachment = attachmentService.getAttachment(attachmentId);
String jsonString = gson.toJson(attachment.toString());
return Response.status(Response.Status.OK).entity(jsonString).build();
}
#PATCH
#Path("{id}")
#Produces({MediaType.APPLICATION_JSON})
public Response patchAttachments(#PathParam("id") Integer attachmentId) throws Exception {
attachmentService = new AttachmentService();
Integer update = attachmentService.update(attachmentId);
String jsonString = new Gson().toJson(update);
return Response.status(Response.Status.ACCEPTED).entity(jsonString).build();
}
#POST
#Produces({MediaType.APPLICATION_JSON})
public Response migrateToMinio(#Context UriInfo uriInfo) throws Exception {
Response response;
List<String> responseList = new ArrayList<>();
tiedostoService = new TiedostoService();
attachmentService = new AttachmentService();
List<Tiedosto> tiedostoList = tiedostoService.getAllFiles();
String responseString = null;
int i = 1;
for (Tiedosto tiedosto : tiedostoList) {
Attachment attachment = new Attachment();
attachment.setCustomerId(tiedosto.getCustomerId());
attachment.setSize(tiedosto.getFileSize());
Integer id = attachmentService.createNew(attachment);
if (id == 1) {
UriBuilder builder = uriInfo.getAbsolutePathBuilder();
builder.path(Integer.toString(i));
response = Response.created(builder.build()).build();
System.out.println(response);
responseString = response.toString();
}
responseList.add(responseString);
i++;
}
String jsonString = new Gson().toJson(responseList);
return Response.status(Response.Status.OK).entity(jsonString).build();
}
}
when I test the individual endpoints with curl or postman, they work as expected, but I got stuck on how to execute GET and PATCH automatically after POST. I'd really appreciate some advice/suggestions/help.

post request in Unirest Java result on a 302 http result

I m training my self on creating a restful server and a desktop java based client.
my backend is Spring Boot based, I have the following controller :
#RestController
#RequestMapping(NiveauAccessController.URL)
public class NiveauAccessController extends GenericController{
public static final String URL = "/acl";
#Autowired
private NiveauAccessRepository niveauAccessRepository;
#PostMapping
private ServerResponse createACL(
#RequestParam("aclTitle") final String aclTitle,
#RequestParam("roles") final List<String> roles
){
if(isSessionValid()){
final MNG_NIVEAU_ACCEE mng_niveau_accee = new MNG_NIVEAU_ACCEE();
mng_niveau_accee.setAclTitle(aclTitle);
List<Role> enumRoles = new ArrayList();
roles.stream().forEach(role->{
enumRoles.add(Role.valueOf(role));
});
mng_niveau_accee.setRoles(enumRoles);
niveauAccessRepository.save(mng_niveau_accee);
initSuccessResponse(mng_niveau_accee);
return serverResponse;
}
initFailLoginResponse();
return serverResponse;
}
.
.
.
}
for my java client I m using this sample code to send a post request over my server :
#FXML
private void doAdd(ActionEvent event) throws UnirestException {
if (titleACL.getText().isEmpty()) {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.initModality(Modality.WINDOW_MODAL);
alert.initOwner(((Node) event.getSource()).getScene().getWindow());
alert.setContentText("Veuillez remplir le champ");
alert.showAndWait();
titleACL.requestFocus();
return;
}
String title = titleACL.getText();
Predicate<? super JFXCheckBox> selectedCheckboxes = checkbox -> {
return checkbox.isSelected();
};
List<JFXCheckBox> selectedCheckBoxesList = observableCheckBoxes.values().stream().filter(selectedCheckboxes).collect(Collectors.toList());
final List<String> roles = new ArrayList<>();
selectedCheckBoxesList.stream().forEach(checkbox -> {
roles.add(checkbox.getText());
});
HttpResponse<String> asString = Unirest.post(ACL_URL)
.header("accept", "application/json")
.field("aclTitle", title)
.field("roles", roles)
.asString();
System.out.println(asString.getStatus());
System.out.println(asString.getHeaders().values());
if (asString.getStatus() == 200) {
}
}
my output is :
302
[[0], [Thu, 10 May 2018 13:30:05 GMT], [https://localhost:8443/acl]]
I don't understand why I m getting the 302 status code which is for URL redirection.
I m trying to use this post to add data to my database.
What should I do to make my Server accept this request?
havins ssl enabled my request over 8080 got redirection to 8443 this is no issue using a web browser because it will handle redirection but in a javafx client you have to handle the redirect by your self so there is a possible solution
if (asString.getStatus() == 200) {
//your success handler code
}else if (asString.getStatus() == 302) {
// use your Rest api to do the request using the response body
}

Need Jersey client api way for posting a webrequest with json payload and headers

I am writing a client for one of my REST API using jersey(org.glassfish.jersey.client.*).
api url is : http://localhost:5676/searchws/search/getresults (POST)
this api returns a json response. i need to provide a payload using jersey client and thats where i am stuck. FOllowing is a sample extract of payload which i need to provide (preferably as string)
Question is how can i provide a payload (XML/JSON) as string or entity to my webtarget.
I saw the answer to providing payload mentioned by calden How to send Request payload to REST API in java? but i am looking for a way to do it in jersey client.
Here is my code till now which does not work fully for post requests.
public class RequestGenerator
{
private WebTarget target;
private ClientConfig config;
private Client client;
private Response response;
public RequestGenerator(Method RequestSendingMethod) throws Exception
{
switch (RequestSendingMethod)
{
case POST :
config = new ClientConfig();
client = ClientBuilder.newClient(config);
target = client.target("http://localhost:5676/searchws").path("search").path("getresults");
String payload = "{\"query\":\"(filter:(\\\"google\\\")) AND (count_options_availbale:[1 TO *])\"}"; //This is just a sample json payload actual one is pretty large
response = target.request(MediaType.APPLICATION_JSON_TYPE).post(Entity.json("")); // What to do here
String jsonLine = response.readEntity(String.class);
System.out.println(jsonLine);
}
}
You specify payload as the argument to Entity.json
String payload = "{\"query\":\"(filter:(\\\"google\\\")) AND (count_options_availbale:[1 TO *])\"}";
response = target.request(MediaType.APPLICATION_JSON_TYPE).post(Entity.json(payload));
I got this working using following code, Salil's code works fine as well(+1 with thanks to him), thanks everyone who contributed to this problem, loving stackoverflow:
public class RequestGenerator
{
private WebTarget target;
private ClientConfig config;
private Client client;
private Response response;
public RequestGenerator(Method RequestSendingMethod) throws Exception
{
switch (RequestSendingMethod)
{
case POST :
String payload = "\r\n{\r\n\"query\": \"google \",\r\n\"rows\": 50,\r\n\"return_docs\": true,\r\n\"is_facet\": true\r\n}"; //this is escapped json string in single line
config = new ClientConfig();
client = ClientBuilder.newClient(config);
target = client.target("http://localhost:7400/searchws/search/getresults");
response = target.request().accept(MediaType.APPLICATION_JSON).post(Entity.entity(payload, MediaType.APPLICATION_JSON), Response.class);
processresponse(response); //This could be any method which processes your json response and gets you your desired data.
System.out.println(response.readEntity(String.class));
break;
case GET :
config = new ClientConfig();
client = ClientBuilder.newClient(config);
target = client.target("http://localhost:7400/search-service/searchservice").path("search").path("results").path("tiger");
response = target.request().accept(MediaType.APPLICATION_JSON).get();
processresponse(response); //This could be any method which processes your json response and gets you your desired data.
System.out.println(response.readEntity(String.class));
}
}

Categories