Send Request Parameters in Spring Web client - java

I want to send link Request Parameters in Spring WebClient request link. For example:
https://www.test.com/notification?con=41280440000097&sec=1232
I tried this code:
WebClient client;
Map<String, String> map = new HashMap<>();
public Mono<Response> execute(Transaction transaction) {
map.put("some_key", "some_value");
Mono<PaymentTransaction> transactionMono = Mono.just(transaction);
return client.post().uri("/notification", token)
.accept(MediaType.APPLICATION_XML)
.contentType(MediaType.APPLICATION_XML)
.body(transactionMono, Transaction.class)
.attributes(Consumer<map>)
.retrieve()
.bodyToMono(Response.class);
}
But when I try to set the map I get Syntax error on token ">", Expression expected after this
What is the proper way to implement this without hardcoding the values into the address?

Does this work?
public Mono<PaymentResponse> execute(PaymentTransaction transaction, WebClient client) {
long conn = 1L;
int sec = 1232;
Mono<PaymentTransaction> transactionMono = Mono.just(transaction);
return client.post()
.uri(uriBuilder -> uriBuilder.scheme("https").host("www.test.com")
.path("notification")
.queryParam("con", conn)
.queryParam("sec", sec)
.build())
.accept(MediaType.APPLICATION_XML)
.contentType(MediaType.APPLICATION_XML)
.body(transactionMono, PaymentTransaction.class)
.retrieve()
.bodyToMono(PaymentResponse.class);
}

This works for me
WebClient client;
Map<String, String> map = new HashMap<>();
public Mono<Response> execute(Transaction transaction) {
Map<String,String> attributeMap = new HashMap<>();
attributeMap.put("some_key", "some_value");
Mono<PaymentTransaction> transactionMono = Mono.just(transaction);
return client.post().uri("/notification", token)
.accept(MediaType.APPLICATION_XML)
.contentType(MediaType.APPLICATION_XML)
.body(transactionMono, Transaction.class)
.attributes(map -> map.putAll(attributeMap))
.retrieve()
.bodyToMono(Response.class);
}

Related

Throws java.lang.IllegalStateException: block()/blockFirst()/blockLast() when retrying invalid token

I am trying to update the token when I get a response with 401 status code.
In order to do that, I used web client. I know that this mainly used to do reactive development but since resttemplate will soon be deprecated I went for this option.
The issue I am facing is that when it does call the api endpoint to get the new token, it throws a 'java.lang.IllegalStateException: block()/blockFirst()/blockLast() '. And make sense as it stated in the exception message It is not supported in thread reactor-http-nio-3.
I saw that there is a map and flatmap option, but I couldn't figure out how to use it inside the doBeforeRetry() to make it process in a different stream.
I need to have that new token before retrying.
So the question is : How can I get the token via another call and then still do the retry ?
I was able to make it work by using a try catch but I would like to find the solution how to use it inside that retry method.
I also try to block the token request by replacing the token response by a Mono and block it by using myMono.toFuture().get() as stated here block()/blockFirst()/blockLast() are blocking error when calling bodyToMono AFTER exchange()
Here is the code :
Method responsible for the call :
public String getValueFromApi(HashMap<String, Object> filter) {
String response = "";
response = webclient
.post()
.uri(endpoint)
.header("token", token.getToken())
.bodyValue(filter)
.retrieve()
.bodyToMono(String.class)
.retryWhen(Retry.max(3).doBeforeRetry(
retrySignal -> tokenService.getTokenFromApi(env)
).filter(InvalidTokenException.class::isInstance))
.block();
return response;
}
Method that retrieve the token :
public void getTokenFromApi(Environment env) {
HashMap<String, String> requestBody = new HashMap<>();
requestBody.put("name", "name");
requestBody.put("password", "password");
String response = WebClient
.builder()
.baseUrl(BASE_PATH)
.defaultHeader(HttpHeaders.CONTENT_TYPE, "application/json")
.build()
.post()
.uri(tokenUri)
.body(BodyInserters.fromValue(requestBody))
.retrieve()
.bodyToMono(String.class)
.block();
getTokenFromResponse(response);
}
private void getTokenFromResponse(String reponse) {
JsonObject tokenObject = new Gson().fromJson(reponse, JsonObject.class);
setToken(tokenObject.get("token").getAsString());
}
WebClient Builder :
#Bean
public WebClient webClientForApi(WebClient.Builder webClientBuilder) {
return webClientBuilder
.clientConnector(new ReactorClientHttpConnector(httpClient))
.filter(errorHandler())
.filter(logRequest())
.clone()
.baseUrl(BASE_PATH)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.ACCEPT, "application/json")
.build();
}
public ExchangeFilterFunction errorHandler() {
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
if (clientResponse.statusCode().equals(HttpStatus.UNAUTHORIZED)) {
return Mono.error(InvalidTokenException::new);
} else if (clientResponse.statusCode() == HttpStatus.INTERNAL_SERVER_ERROR) {
return Mono.error(ApiInternalServerException::new);
} else {
return Mono.just(clientResponse);
}
});
}
private ExchangeFilterFunction logRequest() {
return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
logger.info("Request: {} {}", clientRequest.method(), clientRequest.url());
clientRequest.headers().forEach((name, values) -> values.forEach(value -> logger.info("{}={}", name, value)));
return Mono.just(clientRequest);
});
}

How to get Access Token from Keycloak over SpringBoot?

I'm trying to get an Access Token from Keycloak over SpringBoot and did try the following example. But the KeycloakAuthenticationToken token is null.
Does someone know another approach to get an Access Token?
#GetMapping("/token")
public String getToken(HttpServletRequest request) throws IOException {
KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) request.getUserPrincipal();
RefreshableKeycloakSecurityContext session = (RefreshableKeycloakSecurityContext) token.getAccount().getKeycloakSecurityContext();
KeycloakSecurityContext context = token.getAccount().getKeycloakSecurityContext();
String accessTokenPretty = JsonSerialization.writeValueAsPrettyString(session.getToken());
String idTokenPretty = JsonSerialization.writeValueAsPrettyString(session.getIdToken());
RefreshToken refreshToken;
try {
refreshToken = new JWSInput(session.getRefreshToken()).readJsonContent(RefreshToken.class);
} catch (JWSInputException e) {
throw new IOException(e);
}
String refreshTokenPretty = JsonSerialization.writeValueAsPrettyString(refreshToken);
return refreshTokenPretty;
}
Seems like I can get a token like this with ('org.keycloak:keycloak-admin-client'):
Keycloak keycloak = KeycloakBuilder.builder() //
.serverUrl(serverUrl) //
.realm(realm) //
.grantType(OAuth2Constants.PASSWORD) //
.clientId(clientId) //
.clientSecret(clientSecret) //
.username(userName) //
.password(password) //
.build();
AccessTokenResponse tok = keycloak.tokenManager().getAccessToken();
If someone knows a more elegant way, I would appreciate if you let me know :)
Thanks in advance!
Try the following:
HttpEntity<MultiValueMap<String, String>> request =
new TokenRequest.Builder(clientID, OAuth2Constants.PASSWORD)
.add("username", userName)
.add("password", password)
.build();
ResponseEntity<String> response = restTemplate.postForEntity( postUrl, request , String.class );
return response.getBody();
and the helper class:
public class TokenRequest {
public static class Builder{
MultiValueMap<String, String> data;
public Builder(String clientID, String grant_type){
data = new LinkedMultiValueMap<>();
data.put("client_id", Collections.singletonList(clientID));
data.put("grant_type", Collections.singletonList(grant_type));
}
public Builder add(String key, String value){
data.put(key, Collections.singletonList(value));
return this;
}
public HttpEntity<MultiValueMap<String, String>> build(){
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
return new HttpEntity<>(data, headers);
}
}
private TokenRequest(){
}
}
Try this:
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
KeycloakAuthenticationToken keycloakAuthenticationToken = (KeycloakAuthenticationToken) request.getUserPrincipal();
KeycloakPrincipal<KeycloakSecurityContext> principal = (KeycloakPrincipal) keycloakAuthenticationToken.getPrincipal();
String token = principal.getKeycloakSecurityContext().getIdTokenString();

How to update dynamic date on body for post params within JVM PACT contract?

I have a POST request that takes date as a param from within my contract file for a PACT test.
return builder
.uponReceiving("A request to create a Zoom meeting")
.path(createMeeting)
.method("POST")
.headers(headers)
.body("{\"title\":\"My title\",\"start_time\":\"2020-08-28T14:30:00Z+01:00\",\"duration\":30,\"provider\":\"ZOOM\"}")
.willRespondWith()
.body(body)
.toPact();
But I'd like this to be dynamic, having perhaps today's or tomorrow's date, otherwise it would have an expired date. Could you please advise on how to do do this and if possible to keep it from the consumer side.
These are both Consumer and Provider samples for my request.
Consumer
#ExtendWith(PactConsumerTestExt.class)
public class PACTConsumerEdUiVcTest {
Map<String, String> headers = new HashMap<>();
String createMeeting = "/manage/create-meeting";
#Pact(provider = VC, consumer = ED_UI)
public RequestResponsePact createPact(PactDslWithProvider builder) {
headers.put("Content-Type", "application/json");
DslPart body = new PactDslJsonBody()
.date("start_time", "yyyy-MM-dd'T'HH:mm:ss.000'Z'", new Date());
return builder
.uponReceiving("A request to create a Zoom meeting")
.path(createMeeting)
.method("POST")
.headers(headers)
.body("{\"title\":\"My title\",\"start_time\":\"2020-08-28T14:30:00Z+01:00\",\"duration\":30,\"provider\":\"ZOOM\"}")
.willRespondWith()
.body(body)
.toPact();
}
#Test
#PactTestFor(providerName = VC, port = "8080")
public void runTest() {
//Mock url
RestAssured.baseURI = "http://localhost:8080";
Response response = RestAssured //todo: dynamic start time that won't expire. 27/08/2020
.given()
.headers(headers)
.when()
.body("{\"title\":\"My title\",\"start_time\":\"2020-08-28T14:30:00Z+01:00\",\"duration\":30,\"provider\":\"ZOOM\"}")
.post(createMeeting);
assert (response.getStatusCode() == 200);
}
}
Provider
#Provider(VC)
#PactFolder("target/pacts")
public class PactProviderEdUiVcTest {
#TestTemplate
#ExtendWith(PactVerificationInvocationContextProvider.class)
void pactTestTemplate(PactVerificationContext context, HttpRequest request) {
request.addHeader("Authorization", AUTHORIZATION_TOKEN);
context.verifyInteraction();
}
#BeforeEach
void before(PactVerificationContext context) {
context.setTarget(new HttpsTestTarget(BASE_PACT_VC_URL, 443, "/"));
getAuthorizationToken(UserType.TEACHER);
}
#State("A request to create a Zoom meeting")
public void sampleState() {
}
}
Many thanks.
Able to make it working changing the RestAssured implementation to accept a map and use the date as its value.
map.put("start_time", new Date().toString());
Here the full piece.
#Test
#PactTestFor(providerName = VC, port = "8080")
public void runTest() {
//Mock url
RestAssured.baseURI = "http://localhost:8080";
Map<String, Object> map = new HashMap<>();
map.put("title", "MyTitle");
map.put("start_time", new Date().toString());
map.put("duration", 30);
map.put("provider", "ZOOM");
Response response = RestAssured //todo: dynamic start time that won't expire. 27/08/2020
.given()
.headers(headers)
.when()
.body(map)
.post(createMeeting);
assert (response.getStatusCode() == 200);
}

WebClient - adding defaultHeaders

I'm trying to put multiple headers into defaultHeaders(), But I don't have idea how to create Consumer object from return of createHeaders() method
this.someWebClient = WebClient.builder()
.baseUrl(someConfiguration.getApiUrl())
.clientConnector(buildTimeoutConnector())
.defaultHeaders(????) // Consumer<HttpHeaders>
.build();
I can build my header in that way:
private HttpHeaders createHeaders(String token) {
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.ACCEPT, V1_PUBLIC);
headers.add(HttpHeaders.HOST, "abc");
headers.add(HttpHeaders.AUTHORIZATION, "Bearer " + token);
return headers;
}
but how to wrap it into Consumer?
Clearly the method doc says that it needs a Consumer of some Type. So you can create an anonymous class implementing the Consumer interface or use lambda expression like this:
Using anonymous inner class:
this.someWebClient = WebClient.builder()
.baseUrl(someConfiguration.getApiUrl())
.clientConnector(buildTimeoutConnector())
.defaultHeaders(new Consumer<HttpHeaders>() {
#Override
public void accept(HttpHeaders httpHeaders) {
httpHeaders.addAll(createHeaders(token));
}
})
.build();
Using lambda:
this.someWebClient = WebClient.builder()
.baseUrl(someConfiguration.getApiUrl())
.clientConnector(buildTimeoutConnector())
.defaultHeaders(httpHeaders -> {
httpHeaders.addAll(createHeaders(token));
})
.build();
Use this.
#Bean
public WebClient webClientConfiguration() {
return WebClient
.builder()
.clientConnector(new ReactorClientHttpConnector(HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectionTimeout)
.responseTimeout(Duration.ofMillis(responseTimeout))
))
.baseUrl(this.msConfigProperties.getApiUrl())
.defaultHeaders(httpHeaders())
.build();
}
Then Consumer function
private Consumer<HttpHeaders> httpHeaders(){
return headers -> {
headers.set(HEADER_CONTENT_TYPE, String.valueOf(MediaType.APPLICATION_JSON));
headers.setBasicAuth(this.msConfigProperties.getUserName(),this.msConfigProperties.getPassword());
headers.set(HEADER_ACCEPT_ENCODING, String.valueOf(MediaType.APPLICATION_JSON));
};
}
private void addDefaultHeaders(final HttpHeaders headers) {
headers.add(HttpHeaders.CONTENT_TYPE, "application/json");
headers.add(HttpHeaders.ACCEPT, "application/json");
}
and then
this.someWebClient = WebClient.builder()
.baseUrl(someConfiguration.getApiUrl())
.clientConnector(buildTimeoutConnector())
.defaultHeaders(this::addDefaultHeaders)
.build();

Spring WebClient: Passing username and password in request body to get a token

Following the Spring WebClient tutorial here I'm trying to pass username and password in the request body (no basic auth) to the API endpoint as it expects these as parameters.
I've tested in Postman supplying the username and password as separate fields in the body and setting the contentType as application/x-www-form-urlencoded. This results in getting the expected token, however when I print out the response below using my implementation using WebClient, it prints:
{"error":"invalid_request","error_description":"Missing form parameter: grant_type"}
|
TcpClient tcpClient = TcpClient.create().option(ChannelOption.CONNECT_TIMEOUT_MILLIS, CONNECTION_TIMEOUT_MILIS)
.doOnConnected(connection -> {
connection.addHandlerLast(new ReadTimeoutHandler(CONNECTION_TIMEOUT_MILIS, TimeUnit.MILLISECONDS));
connection.addHandlerLast(new WriteTimeoutHandler(CONNECTION_TIMEOUT_MILIS, TimeUnit.MILLISECONDS));
});
WebClient client = WebClient.builder().baseUrl(BASE_URL)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient))).build();
LinkedMultiValueMap<String, String> multiMap = new LinkedMultiValueMap<>();
multiMap.add("username", username);
multiMap.add("password", password);
BodyInserter<MultiValueMap<String, Object>, ClientHttpRequest> inserter = BodyInserters.fromMultipartData(multiMap);
WebClient.RequestHeadersSpec<?> request = client.post().uri(API_CALL_GET_TOKEN).body(inserter);
String response = request.exchange().block().bodyToMono(String.class).block();
System.out.println(response);
Providing .accept(MediaType.APPLICATION_JSON) provided the response:
WebClient client = WebClient.create();
LinkedMultiValueMap<String, String> credentials = new LinkedMultiValueMap<>();
credentials.add("username", username);
credentials.add("password", password);
String response = client.post()
.uri(BASE_URL + API_CALL_GET_TOKEN)
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.accept(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromFormData(credentials))
.retrieve()
.bodyToMono(String.class)
.block();
return response;

Categories