I'm experiencing some troubles with a simple matter.
I'm trying to send a request to other REST service
//getting restTemplate from RestTemplateBuilder.build()
//endpoint and rest of variables came in properties
Map<String, String> map = new HashMap<>();
map.put("app", app);
map.put("username", username);
map.put("password", password);
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
String token = restTemplate.postForObject(loginEndpoint, headers, String.class, map);
And I recive:
Unexpected error occurred in scheduled task.
org.springframework.web.client.HttpClientErrorException: 400 Bad Request
The weird thing, when I use a simple CURL call and works smooth.
Already checked the variables and endpoint, and it's correct.
In this case, endpoint must have appropiate placeholders on end point url.
I made this method to do it easy:
private String placeHolders(Map<String, String> values){
String response = "?";
boolean first = true;
for(Map.Entry<String, String> entry:values.entrySet()){
if(first){
first = false;
}else{
response+="&";
}
response+=entry.getKey()+"="+entry.getValue();
}
return response;
}
And the call now Is:
String token = restTemplate.postForObject(loginEndpoint+placeHolders, headers, String.class, map);
Related
I am trying to connect RestApi created with SpringBoot to get access token from Keycloak.
This is the code I have:
Application.yml
keycloak:
realm: ${CLIENT_RELM_NAME:registerApiRealm}
auth-server-url: ${KEYCLOAK_URL_WITH_PATH:http://localhost:8080/auth}
ssl-required: external
#keycloak resource is the client ID
resource: ${KEYCLOAK_CLIENT_NAME:registerApiClienty}
#replace secret with your key
credentials:
secret: ${CLIENT_RELM_SECRET:12a658ea-b728-4f53-9948-492ef470363f}
#The line below will prevent redirect to login page
bearer-only: true
KeycloakServiceImpl.java
#Component
public class KeyCloakServiceImpl implements KeyCloakService {
private static final Logger log = LoggerFactory.getLogger(RegistrationController.class);
#Value("${keycloak.credentials.secret}")
private String SECRETKEY;
#Value("${keycloak.resource}")
private String CLIENTID;
#Value("${keycloak.auth-server-url}")
private String AUTHURL;
#Value("${keycloak.realm}")
private String REALM;
#Value("${admin.username}")
private String ADMIN_USERNAME;
#Value("${admin.password}")
private String ADMIN_PASSWORD;
#Autowired
RestTemplate restTemplate;
#Override
public TokenDto getToken(UserCredentials userCredentials) {
TokenDto responseToken = null;
try {
MultiValueMap<String, String> urlParameters = new LinkedMultiValueMap<>();
urlParameters.add("grant_type", "password");
urlParameters.add("client_id", CLIENTID);
urlParameters.add("username", userCredentials.getUsername());
urlParameters.add("password", userCredentials.getPassword());
urlParameters.add("client_secret", SECRETKEY);
responseToken = authenticate(urlParameters);
} catch (Exception e) {
e.printStackTrace();
}
return responseToken;
}
private TokenDto authenticate( MultiValueMap<String, String> urlParameters ) throws Exception {
TokenDto tokenDto = new TokenDto();
String uri = AUTHURL + "/realms/" + REALM + "/protocol/openid-connect/token";
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded");
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(urlParameters, httpHeaders);
ResponseEntity<Object> result = restTemplate.exchange(uri, HttpMethod.POST, request, Object.class);
log.info("{}", result);
log.info("{}", result.getBody());
LinkedHashMap<String, Object> map = (LinkedHashMap<String, Object>) result.getBody();
if (map != null) {
tokenDto.setAccess_token(map.get("access_token").toString());
tokenDto.setToken_type(map.get("token_type").toString());
tokenDto.setRefresh_token(map.get("refresh_token").toString());
tokenDto.setExpires_in(map.get("expires_in").toString());
tokenDto.setScope(map.get("scope").toString());
} else {
return null;
}
return tokenDto;
}
When I test it with Postaman by sending username and `password``
{
"username": "user",
"password": "useruser35"
}
I am getting the following error:
org.springframework.web.client.HttpClientErrorException$BadRequest: 400 Bad Request: [{"error":"invalid_client","error_description":"Invalid client credentials"}]
I am not sure why I double-checked if my user is created and it is, I checked clientId and secret and everything seems fine.
What am I missing here, any advice appreciated.
Your code seems to be valid.
You might want to check out this manual to see if keycloak has been properly configured.
The password grant flow in your example is usually not the preferred approach for doing signons.
You can also retrieve the token via spring security, see this link. There are many examples on the internet of how to do oAuth2 with spring security.
I know how to upload multipart file from postman, but how to do the same through REST API. The consumer API works fine when I hit through postman, but while doing the same through REST, it does not work.
Same thing I am doing through REST like this but its not working:
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
MultiValueMap<String, Object> body
= new LinkedMultiValueMap<>();
body.add("file", file);
HttpEntity<MultiValueMap<String, Object>> requestEntity
= new HttpEntity<>(body, headers);
String serverUrl = "http://localhost:9001/communication/api/messageEngine/event/RECIPT/sendEmailAttachment";
ParameterizedTypeReference<ApiResponse<Map<String,Object>>> parameterizedTypeReference =
new ParameterizedTypeReference<com.loylty.dataacquisition.model.ApiResponse<Map<String,Object>>>() {};
RestTemplate restTemplate = new RestTemplate();
try {
ResponseEntity<com.loylty.dataacquisition.model.ApiResponse<Map<String,Object>>> result =
restTemplate.exchange(serverUrl, HttpMethod.POST, requestEntity, parameterizedTypeReference);
if (result.getStatusCode().is2xxSuccessful() == false) {
throw new DENotReachableException();
}
} catch (Exception e) {
e.printStackTrace();
throw e;
}
Target API or consumer API:
#CrossOrigin
#RequestMapping(method = RequestMethod.POST, value = "/event/{event}/sendEmailAttachment", consumes = {"multipart/form-data"})
public ApiResponse<Object> sendReceiptWithAttachment(#RequestPart("file") MultipartFile file, #PathVariable("event") String event) {
LidsysUtil.messageId.set(String.valueOf(new Date().getTime()));
MessageTracker tracker = new MessageTracker(LidsysUtil.messageId.get(), event);
LidsysUtil.tracker.set(tracker);
LOGGER.info("Executing Message Id : {} ", LidsysUtil.messageId.get());
LOGGER.info("Request received for event : {}", event);
// LOGGER.info("Request Body : {}", LidsysUtil.displayJSON(requestBody));
Map<String, Object> request = messageEngineService.initiateEmailwithAttachmentV2( file, event);
return new ApiResponse<>(APIResponseKey.ALL_GOOD, messageEngineService.execute(request, event), null);
}
Following exception I get when I try with REST api
Exception on source microservice
020-10-21 19:11:18,237 [ERROR]---[DirectJDKLog.java]---[http-nio-8010-exec-2]: Servlet.service() for servlet [dispatcherServlet] in context with path [/dataacquisition] threw exception [Request processing failed; nested exception is org.springframework.web.client.HttpClientErrorException: 400 null] with root cause
org.springframework.web.client.HttpClientErrorException: 400 null
Exception on target microservice
2020-10-21 19:11:17,445 [ERROR]---[HttpLoggingFilter.java]---[http-nio-9001-exec-10]: null
When you are instantiating the ParameterizedTypeReference object why do you refer to the ApiResponse in two different ways? (You use both the simple name and later the full qualified name com.loylty.dataacquisition.model.ApiResponse) Could that mean that these may be two different classes?
Also, on the consumer side, you have declared the sendReceiptWithAttachment() method to return ApiResponse<Object>, rather than ApiResponse<Map<String,Object>>.
Make sure both the consumer and the producer are in sync.
I found the solution, here in map I was adding File instead it required of type Resource
MultiValueMap<String, Object> body
= new LinkedMultiValueMap<>();
body.add("file", file);
Solution
Resource rfile = new FileSystemResource(file)
MultiValueMap<String, Object> body
= new LinkedMultiValueMap<>();
body.add("file", rfile );
I have this method to make request:
#Override
public HttpEntity<MultiValueMap<String, String>> generateRequestEntity(Date date) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("key", "SOME_API_KEY");
map.add("source", "SOME_API_SOURCE");
if (date == null)
map.add("method", "getall");
else {
map.add("method", "getfrom");
map.add("date", new SimpleDateFormat("yyyy-MM-dd").format(date));
}
return new HttpEntity<>(map, headers);
}
I send a request and get a response, as recommended at following link: URL
HttpEntity<MultiValueMap<String, String>> request = generateRequestEntity(date);
ResponseEntity<OnlineSell[]> response = restTemplate.postForEntity(url, request, OnlineSell[].class);
OnlineSell[] onlineSells = response.getBody();
But I have a problem. I am failing when trying to parse JSON-response. OnlineSell - class, that must keep the answer BUT I just can’t create a structure that successfully stores the values of this answer. A very large and complex answer tree comes.
Answer: Can I get JSONObject from it to parse and save manually? Or can I get help with JSON parsing by previously updating this post and adding it (answer form Postman)?
What you can do is to consider the ResponseEntity as a String.
Then afterwards you can use objectMapper with readValue(),
Something like this:
ResponseEntity<String> response = restTemplate().postForEntity(url, request, String.class);
String body = response.getBody();
OnlineSell[] onlineSells = new ObjectMapper().readValue(body, OnlineSell[].class);
I'm currently writing an application that issues a JWT token on demand.
When the token is issued, the user should be redirected to a webpage. This works like a charm - but I need to set an authorization header for that redirect.
The user enters his credentials on Webpage A. Webpage A sends a POST Request to Server B. Server B checks the credentials and offers a token. Now the user should be redirected to Webpage C.
I tried the following:
#RequestMapping(value = "/token", method = RequestMethod.POST, produces = "application/json", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public ResponseEntity<Object> token(
#RequestParam("user") String _username,
#RequestParam("secret") String _secret
) throws Exception
{
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
map.add("user", _username);
map.add("secret", _secret);
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<MultiValueMap<String, String>>(map, headers);
HttpStatus statusCode = HttpStatus.FOUND;
HttpHeaders httpHeaders = new HttpHeaders();
try {
ResponseEntity<String> request = restTemplate.exchange(_url, HttpMethod.POST, entity, String.class);
} catch (Exception ex) {
ex.printStackTrance();
}
String response = request.getBody();
JSONObject _tokenObject = new JSONObject(response);
String _token = _tokenObject.getString("access_token");
httpHeaders.add("Authorization", "Bearer: " + _token);
URI _redirectUri = new URI("http://foo.example.com/webpageC");
httpHeaders.setLocation(_redirectUri);
return new ResponseEntity<>(httpHeaders, HttpStatus.FOUND);
}
The redirect works, but only /token gets the Authorization Header as Response Header, right before the redirect happens.
How can I achieve that the header is sent to Webpage C?
Thanks.
Update
A forward: is not possible, as Webpage C is on another URL and not in the same Controller.
Anyone has an Idea how to solve?
Typically, we let the frontend developers handle the redirections. If you work on the backend, you could offer a restful API to issue JwtTokens. The frontend will worry about how to carry the Authorization header in the following redirected Http requests. Here is a simple login controller using mobile and password in exchange for the JwtToken.
#RequestMapping(value = "/login", method = RequestMethod.POST)
public Result login(#RequestBody Map<String, String> loginMap) {
User user = userService.findByMobile(mobile);
if(user == null || !user.getPassword().equals(password)) {
return new Result(ResultCode.MOBILEORPASSWORDERROR);
}else {
String token = jwtUtils.createJwt(user.getId(), user.getUsername(), map);
return new Result(ResultCode.SUCCESS,token);
}
}
If you, as the backend, wish to handle the redirection anyway, redirect the request to a webpage with the token as a parameter, in this case:
GET http://www.example.com/login/success?token=xxx&redirectUrl=%2Fxxx
The related backend code would be:
protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
Optional<String> redirectUri = CookieUtils.getCookie(request, REDIRECT_URI_PARAM_COOKIE_NAME)
.map(Cookie::getValue);
if(redirectUri.isPresent() && !isAuthorizedRedirectUri(redirectUri.get())) {
throw new BadRequestException();
}
String targetUrl = redirectUri.orElse(getDefaultTargetUrl());
String token = tokenProvider.createToken(authentication);
return UriComponentsBuilder.fromUriString(targetUrl)
.queryParam("token", token)
.build().toUriString();
}
Again, let the frontend put the token into the further request as the authorization header.
Keep in mind, you are returning a response so you can set the response header. You don't get to set the request header of the next request for the frontend.
Reference:
https://www.callicoder.com/spring-boot-security-oauth2-social-login-part-2/
Trying to setup a restful service component that update a database table. Tried using both Spring RestTemplate as well as apache commons restful impl and both seems to no work.
On using
Option 1: Using Spring RestTemplate : Results in following error
com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.util.LinkedHashMap out of START_ARRAY token
Option 2: Using using org.apache.commons.httpclient.methods.PostMethod; results in following errors
Server side error:
org.codehaus.jackson.JsonParseException: Unexpected character ('<' (code 60)): expected a valid value (number, String, array, object, 'true', 'false' or 'null')
Client side error:
The server refused this request because the request entity is in a format not supported by the requested resource for the requested method ().
My Restful service method is annotated as "Post" and consumes "JSON". My client side controller which initiates the RestFul call, code below
#RequestMapping(value="/update", consumes="application/json")
public void updateMaintReport(
#RequestBody Map<String, String> formModelData,
HttpServletRequest request,
HttpServletResponse response)
throws IOException,JsonMappingException {
logger.log(LogLevel.DEBUG, "REACHED method updateMaintReport..");
System.out.println("Reached method updateMaintReport.....");
boolean errorEncountered = false;
ReportingSession repSession = null;
HttpSession session = request.getSession(false);
if(session==null) {
// TODO: code for handling invalid/expired session
} else {
repSession = (ReportingSession)session.getAttribute(ReportingWebConstants.REPORTING_SESSION);
if(repSession==null) {
errorEncountered = true;
}
}
if(!errorEncountered) {
ServiceClient serviceClient = new ServiceClient();
String servicesUrl = this.environment.getProperty("service_url_reports_data");
String servicesName = this.environment.getProperty("service_name_reports_update_fnol");
String serviceUrl = VIPUrlFactory.getServiceUrl(servicesUrl+servicesName);
logger.log(LogLevel.DEBUG, "For update : serviceUrl: "+serviceUrl);
//Option 1: Using Spring RestTemplate :
LinkedMultiValueMap<String,String> headers = new LinkedMultiValueMap<String,String>();
headers.add("Accept","application/json");
headers.add("Content-type","application/json");
List list = new ArrayList<Map<String, String>>(); list.add(formModelData);
RestTemplate restTemplate = new RestTemplate();
HttpEntity<List> requestEntity = new HttpEntity<List>(list, headers);
ResponseEntity<List> fList = restTemplate.exchange(serviceUrl,
HttpMethod.POST,
requestEntity,
List.class);
//Option 2: using org.apache.commons.httpclient.methods.PostMethod; -- Will be commented when option 1 block is uncommented
serviceClient.setParams(formModelData);
serviceClient.setServiceUrl(serviceUrl);
serviceClient.callRestServicePost();
logger.log(LogLevel.DEBUG, "Posting data to service - to execute the update");
}
}
In the above code, option 1 and option 2 block won't be executed simultaneously.
Below is the code block which accepts the Restful call, my server side code.
#RequestMapping(value = "/update", method = RequestMethod.POST)
public void updateMainRptData(#RequestBody Map<String, String> formModelData) throws ReportingIntegrationException,
IOException, JsonMappingException {
String updateStmt = "UPDATE CL_SCRIPTS SET DELETE_IND = #{delete_ind}, SCRIPT_DESC = #{script_desc}, SCRIPT_TXT = #{script_txt}WHERE COMPANY_CD = #{company_cd} AND SCRIPT_NAME = #{script_name}AND PROMPT_ID = #{prompt_id}";
ParameterObjectDTO paramObjDTO = new ParameterObjectDTO();
logger.log(LogLevel.DEBUG,"In Services Web: updateMainRptData()");
if(!formModelData.isEmpty()) {
Set<String> keySet = formModelData.keySet();
StringBuilder sb = new StringBuilder();
for (String key : keySet) {
sb.append(key).append(" -- ").append(formModelData.get(key)).append("\n");
}
logger.log(LogLevel.DEBUG, sb.toString());
}
paramObjDTO.setModalForQuery(formModelData);
paramObjDTO.setUpdateSqlStmt(updateStmt);
maintReportingSvc.updateMaintReport(paramObjDTO);
}
Error Messages I see in browsers is not helpful but my JSON data is valid I believe. Any help is appreciated. Thanks.
Later I changed the signature of the method updateMainRptData and added a returntype and #ResponseBody to resolve this issue.