I have a Feign client to access to an createUser endpoint that requires two headers: username and password. I know how to add one header, but how do I add two headers to the request?
#FeignClient(name = "client", url = "https://abc.abc.com/user/", configuration = FeignClientConfig.class)
public interface MyFeignClient {
#Headers("username_header: {username}") // how do I add "password" here.
#PostMapping(value = "v1/users")
void createUser((#Param("username") String username, User userRequest);
}
Update: Now based on the answer below, I changed the interface body to:
#Headers({"username_header: {username}", "password_header: {password}"})
#PostMapping(value = "v1/users")
void createUser(#Param("username") String username,
#Param("password") String password,
User userRequest);
And the code that calls it is:
feignClient.createUser("me", "123", userObj);
Then I am getting an error:
org.springframework.beans.factory.BeanCreationException: Error creating bean,
nested exception is java.lang.IllegalStateException: Method has too many Body parameters:
feignClient.createUser(java.lang.String,java.lang.String, User)
I know it may be late but I am hopping to help others.
What has worked for me:
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
#PostMapping(path = "${service-uri}", consumes = MediaType.APPLICATION_JSON_VALUE)
String callService(#RequestBody MyBodyClass body, #RequestParam Integer param01, #RequestParam String param02, #RequestHeader Map<String, String> headerMap);
The solution proposed with #HeaderMap doesn't work, apparently because this class is from feign package (import feign.HeaderMap).
Headers accepts String[] for value ... So
#Headers({ "username: {username}", "password: {password}" })
Should do the trick
#RequestLine("POST /v1/users")
#Body("{userBody}")
Response createUser(#HeaderMap Map headerMap, #Param(value = "userBody") String userBody);
Updated
#RequestLine("POST /v1/users")
Response createUser(String userBody, #HeaderMap Map headerMap);
Related
I'm trying to build a spring controller which essentially acts as a reverse-proxy for a geoserver instance.
For example if the client wants to access geoserver/wms?PARAM1=foo&PARAM2=bar, the controller will simply forward the request to the actual geoserver instance and serve back the response. In my case, geoserver either returns an XML payload or an image.
When testing this controller with an URL which returns an image, I am able to process the initial client request, forward it to geoserver and then process it but I'm getting the following error when serving the response to the client :
There was an unexpected error (type=Internal Server Error, status=500).
No converter for [class [B] with preset Content-Type 'image/png'
org.springframework.http.converter.HttpMessageNotWritableException: No converter for [class [B] with preset Content-Type 'image/png'
Full stack trace
Here is the controller class:
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.rest.webmvc.BasePathAwareController;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.reactive.function.client.WebClient;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
#Slf4j
#RestController
#BasePathAwareController
public class GeoServerProxyController {
//only used to check which converters are present
#Autowired
List<HttpMessageConverter<?>> converters;
#RequestMapping(value = "/geoserver/**")
#ResponseBody
public ResponseEntity<byte[]> forwardRequest(#RequestParam MultiValueMap<String, String> requestParams, #RequestHeader Map<String, String> headers, HttpServletRequest request) {
WebClient client = WebClient.builder()
.baseUrl("http://127.0.0.1:8090/geoserver/")
.build();
String url = new AntPathMatcher().extractPathWithinPattern("/geoserver/**", request.getRequestURI());
WebClient.ResponseSpec response = client.get()
.uri(uriBuilder -> uriBuilder
.path(url)
.queryParams(requestParams)
.build())
.headers(httpHeaders -> {
headers.forEach(httpHeaders::set);
})
.retrieve();
HttpHeaders responseHeaders = response.toBodilessEntity().map(HttpEntity::getHeaders).block();
byte[] responseBody = response.bodyToMono(byte[].class).block();
return ResponseEntity.ok().headers(responseHeaders).body(responseBody);
}
As advised in another thread, I have tried registering a byte array http message converter, which I was able to confirm is added to the list of http message converters.
#Configuration
public class WebMVCConfig implements WebMvcConfigurer {
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
final ByteArrayHttpMessageConverter arrayHttpMessageConverter = new ByteArrayHttpMessageConverter();
final List<MediaType> list = new ArrayList<>();
list.add(MediaType.IMAGE_JPEG);
list.add(MediaType.IMAGE_PNG);
list.add(MediaType.APPLICATION_OCTET_STREAM);
arrayHttpMessageConverter.setSupportedMediaTypes(list);
converters.add(arrayHttpMessageConverter);
}
}
This resulted in the same error.
I have also tried using InputStreamResource as a return type, as recommended by this article. It resulted in the same kind of error except with InputStreamResource instead of class [B].
I have also tried adding the following annotation to my controller method (which wasn't optimal as I would prefer not specifying a constant content type) :
#RequestMapping(value = "/geoserver/**", produces=MediaType.IMAGE_PNG_VALUE)
This also results in the exact same error.
I was not able to find a solution to this problem in other threads or in spring web documentation. The most common problem that somewhat resembles this deals with a "Content-Type=null" header, which is not my case.
Does anyone know how to solve this error ? Alternatively, is there a better way to serve distant image files through a Spring controller ?
The #ResponseBody annotation is used to serialize a Java object to the response (typically as JSON or XML).
Here you don't want to convert anything, just send raw binary content. Remove the annotation.
You should also change #RestController to #Controller, because #RestController automatically adds #ResponseBody.
It seems like the problem you are facing is due to #BasePathAwareController , remove it and you are good to go.
Description:
Based on the stack trace shared:
it occurs that SpringDataRest is trying to map an appropriate object to the content-type which it cannot find. hence throwing an generic 500 exception.
For those who are interested: i've had same issue and solve it like this.
This code is not the final, only for test in my case.
Angular frontend openlayers
Tile layer : i defined custom tileLoadFunction (example can be found in openlayers docs), url call my springboot backend api witch is proxying geoserver backend, a jwt token is sent in the fetch header (for security purpose, not mandatory)
new TileLayer({
source: new TileWMS({
url: 'http://localhost:8080/api/v1/map/wms',
tileLoadFunction: (tile, src) => {
const retryCodes = [408, 429, 500, 502, 503, 504];
const retries: any = {};
const image: any = (tile as ImageTile).getImage();
fetch(src, {
headers: {
Authorization: 'Bearer ' + this.oauthService.getAccessToken(),
},
})
.then((response) => {
if (retryCodes.includes(response.status)) {
retries[src] = (retries[src] || 0) + 1;
if (retries[src] <= 3) {
setTimeout(() => tile.load(), retries[src] * 1000);
}
return Promise.reject();
}
return response.blob();
})
.then((blob) => {
const imageUrl = URL.createObjectURL(blob);
image.src = imageUrl;
setTimeout(() => URL.revokeObjectURL(imageUrl), 5000);
})
.catch(() => tile.setState(3)); // error
},
params: {
LAYERS: 'TEST:section_33',
TILED: true,
},
serverType: 'geoserver',
transition: 0,
}),
Springboot backend
I have a RestController endpoint witch is close to the first post, and everythind is ok for me : wms geoserver tiles are called by my frontend, the bakend intercept these calls and make geoserver request and finally send back the tiles to the frontend and the wms layer appears to the map
#RestController
#RequestMapping("/api/v1/map")
#Slf4j
public class MapController {
#GetMapping(value = "/wms")
public ResponseEntity<byte[]> getWmsTile(#RequestParam MultiValueMap<String, String> requestParams, #RequestHeader Map<String, String> headers, HttpServletRequest request) throws IOException {
WebClient client = WebClient.builder()
.baseUrl("http://localhost:7070/geoserver/TEST/wms")
.build();
WebClient.ResponseSpec response = client.get()
.uri(uriBuilder -> uriBuilder
.path("")
.queryParams(requestParams)
.build())
.headers(httpHeaders -> {
headers.forEach(httpHeaders::set);
})
.retrieve();
HttpHeaders responseHeaders = response.toBodilessEntity().map(HttpEntity::getHeaders).block();
byte[] responseBody = response.bodyToMono(byte[].class).block();
return ResponseEntity.ok().contentType(MediaType.IMAGE_PNG).body(responseBody);
}
}
Problem
I'm trying to create an app that uses Auth0 SPA + React on the frontend to auth users without ever having to deal with passwords. Then, I'd like to secure any endpoints I create using an Auth server that I'm required to create using the Spring Framework.
Just to clarify, the flow would be
Frontend ->
Auth through Auth0 ->
Redirect to users dashboard on frontend ->
Make HTTP request to endpoint sending JWT returned from Auth0 ->
Endpoint makes request to my Auth Server sending JWT returned from Auth0 ->
Auth server either either returns 401 or user object based on JWT ->
Endpoint grabs data specific to that user from DB ->
Returns data to frontend
I've managed to get my frontend to work just fine using the Quickstart Guide that Auth0 provides but I'm having a lot of trouble figuring out how to get my Auth Service to verify the user.
I believe I've come to the conclusion that I need to create an "API" on Auth0 and grab an access token and use that to validate the JWT, which in this case is just the access token and not the JWT that my frontend contains. I've also got this part working but there doesn't seem to be a way to know who the user is. When testing this "API", after sending a valid request I am returned
{
"iss": "https://${username}.auth0.com/",
"sub": "${alphanumericCharacters}#clients",
"aud": "${ApiIdentifier}",
"iat": ${issuedAt},
"exp": ${expiresAt},
"azp": "${alphanumericCharacters}",
"gty": "client-credentials"
}
While it's good to know I'm on the right track I can't seem to figure out what to do with this response to find the user.
Expected
I expect to be able to identify a specific user after validating an access_token from my Auth Service
Code
I don't have much code to show but I'll provide what I can from my Auth Service
SecurityConfiguration.java
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Value("${auth0.audience}")
private String audience;
#Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
private String issuer;
#Override
public void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.authorizeRequests()
.mvcMatchers("/api/validate")
.authenticated()
.and()
.oauth2ResourceServer()
.jwt();
}
#Bean
JwtDecoder jwtDecoder() {
NimbusJwtDecoderJwkSupport jwtDecoder = (NimbusJwtDecoderJwkSupport)
JwtDecoders.fromOidcIssuerLocation(issuer);
OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator(audience);
OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuer);
OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);
jwtDecoder.setJwtValidator(withAudience);
return jwtDecoder;
}
}
AudienceValidator.java
public class AudienceValidator implements OAuth2TokenValidator<Jwt> {
private final String audience;
public AudienceValidator(String audience) {
this.audience = audience;
}
public OAuth2TokenValidatorResult validate(Jwt jwt) {
OAuth2Error error = new OAuth2Error("invalid_token", "The required audience is missing", null);
if (jwt.getAudience().contains(audience)) {
return OAuth2TokenValidatorResult.success();
}
return OAuth2TokenValidatorResult.failure(error);
}
}
ValidateController.java
#RestController
#RequestMapping("/api/validate")
public class ValidateController {
#GetMapping
public boolean validate() {
return true; // only returns if successfully authed
}
}
After reading through the docs I've found my solution.
It turns out that I don't need to create an "API" on Auth0 but instead need to use my Applications endspoint(s) from Auth0. Auth0 provides many endpoints based on your account that you can take advantage of from any of your applications (CLI, Server, Client, etc.) as long as you can:
Make an HTTP Request
Provide credentials
So the way to get a users information is explained here.
Data flow
Using my projects auth/data flow it's pretty much:
Using #auth0/auth0-spa-js on the frontend, you can grab a users access token after a successful auth by using the getTokenSilently() method.
Send up HTTP request to your Rest Service
Rest Service sends that token to your Auth Service
Auth Service sends GET request to https://myAuth0Username.auth0.com/userinfo with the Authorization: Bearer ${access_token} header. Example
If successfully authed from Auth0
Returns your users information such as "name", "email", etc.
Else
Returns a 403 Forbidden HTTP Status
Auth Service then returns user object to Rest Service
Rest Service then does necessary logic for that endpoint (DB query, another HTTP request, etc.)
Example Auth Service endpoint to validate tokens and return a user
ValidateController.java
package x.SpringTodo_Auth.Controllers;
import x.SpringTodo_Auth.Models.User;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
#RestController
#RequestMapping("/api/validate")
public class ValidateController {
#GetMapping
public Object validate() {
// Create and set the "Authorization" header before sending HTTP request
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + access_token);
HttpEntity<String> entity = new HttpEntity<>("headers", headers);
// Use the "RestTemplate" API provided by Spring to make the HTTP request
RestTemplate restTemplate = new RestTemplate();
Object user = restTemplate.exchange("https://myAuth0Username.auth0.com/userinfo", HttpMethod.POST, entity, User.class);
return user;
}
}
User.java (This is the class passed to the restTemplate.exchange(...) method as the last argument
package x.SpringTodo_Auth.Models;
public class User {
private String sub;
private String given_name;
private String family_name;
private String nickname;
private String name;
private String picture;
private String locale;
private String updated_at;
private String email;
private boolean email_verified;
// Getters/setters (or you can use Lombok)
}
I am trying to do a simple post request from React (client side) to Java server side. Here is my controller below.
package com.va.med.dashboard.controllers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import com.va.med.dashboard.services.VistaServiceImpl;
import gov.va.med.exception.FoundationsException;
#RestController
#RequestMapping("/dashboard")
public class DashboardController {
#Autowired
private VistaServiceImpl vistaService;
#RequestMapping("/main")
String home() {
return "main route";
}
#RequestMapping("/rpc")
String test() throws FoundationsException {
vistaService.myAuth();
return "this is rpc route";
}
#RequestMapping(method = RequestMethod.POST, produces =
"application/json", value = "/vista")
#ResponseStatus(value = HttpStatus.ACCEPTED)
public String getVistaConnection(#RequestBody String ipString, #RequestBody String portString, #RequestBody String accessPin,
#RequestBody String verifyPin) {
System.out.println(ipString);
System.out.println(portString);
System.out.println(accessPin);
System.out.println(verifyPin);
vistaService.connect(ipString, portString, accessPin, verifyPin); // TO-DO populate with serialized vars
if (vistaService.connected) {
return "Connected";
} else {
return "Not Connected";
}
}
}
Below is my react axios post request
axios.post('/dashboard/vista', {
ipString: this.state.ipString,
portString: this.state.portString,
accessPin: this.state.accessPin,
verifyPin: this.state.verifyPin
})
.then(function (response){
console.log(response);
})
.catch(function (error){
console.log(error);
});
This is also the error that I am getting.
Failed to read HTTP message:
org.springframework.http.converter.HttpMessageNotReadableException:
Required request body is missing:
Can anyone please shed some light on this error message? I'm coming from a pure JavaScript background so a lot of things I just don't know for Java because it is automatically implemented inside of JavaScrips language.
Thanks again in advance!
You're doing it wrong.
Instead of
public String getVistaConnection(#RequestBody String ipString, #RequestBody String portString, #RequestBody String accessPin,RequestBody String verifyPin)
You should wrap those parameters in a class:
public class YourRequestClass {
private String ipString;
private String portString;
....
// Getter/setters here
}
and your controller method will look like:
public String getVistaConnection(#RequestBody YourRequestClass request)
From #Rajmani Arya:
Since RestContoller and #RequestBody suppose to read JSON body, so in your axios.post call you should put headers Content-Type: application/json
Try to replace all #RequestBody annotations with #RequestParam
public String getVistaConnection(#RequestParam String ipString, #RequestParam String portString, #RequestParam String accessPin, #RequestParam String verifyPin)
I would like the use in RESTful WS #OPTION annotation to use it as a Help. I found one snippet on http://www.programcreek.com/java-api-examples/index.php?source_dir=AIDR-master/aidr-output/src/main/java/qa/qcri/aidr/output/getdata/GetBufferedAIDRData.java. There is snippet of code:
#OPTIONS
#Produces(MediaType.APPLICATION_JSON)
#Path("/channel/filter/{crisisCode}")
public Response getBufferedAIDRDataPostFilter(#PathParam("crisisCode") String channelCode,
#QueryParam("callback") String callbackName,
#DefaultValue("1000") #QueryParam("count") int count) {
return Response.ok()
.allow("POST", "GET", "PUT", "UPDATE", "OPTIONS", "HEAD")
.header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Credentials", "true")
.header("Access-Control-Allow-Methods", "POST, GET, PUT, UPDATE, OPTIONS, HEAD")
.header("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With")
.build();
}
But I didnt't found, how to call and obtain content of header etc. from definition above on the client side.
Since the snippet that you posted in a JaxRS REST API implementation, my solution provided below is also a JaxRS Client implementation.
This can be implemented in any language using any framework though depending on your requirements.
Following is a code snippet to invoke the OPTIONS request on a REST API endpoint and process its response.
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;
import java.util.List;
import java.util.Map;
public class RestClient {
public static void main(String[] args) {
Client client = ClientBuilder.newBuilder().build();
WebTarget target = client.target("https://<api-base-url>/<resource-path>");
Response response = target.request().options();
Map<String, List<String>> headerMap = response.getStringHeaders();
for (String key : headerMap.keySet()) {
List<String> values = headerMap.get(key);
for (String value : values) {
// The header and its corresponding value can be processed as per the case.
}
}
// This will return a list of all supported HTTP Methods (GET, POST, PUT, ..., etc)
List<String> httpMethods = headerMap.get("Allow");
for (String method : httpMethods) {
if ("GET".equals(method)) {
// Do something
}
if ("POST".equals(method)) {
// Do something
}
...
...
}
response.close();
}
}
There are may more methods exposed by the Response class which can be explored as needed for your implementation.
I'm trying spring framework.
I have RestController and function:
#RequestMapping(value="/changePass", method=RequestMethod.POST)
public Message changePassword(#RequestBody String id, #RequestBody String oldPass,
#RequestBody String newPass){
int index = Integer.parseInt(id);
System.out.println(id+" "+oldPass+" "+newPass);
return userService.changePassword(index, oldPass, newPass);
}
and code angularJS
$scope.changePass = function(){//changePass
$scope.data = {
id: $scope.userId,
oldPass:$scope.currentPassword,
newPass:$scope.newPassword
}
$http.post("http://localhost:8080/user/changePass/", $scope.data).
success(function(data, status, headers, config){
if(date.state){
$scope.msg="Change password seccussful!";
} else {
$scope.msg=date.msg;
}
})
.error(function(data, status, headers, config){
$scope.msg="TOO FAIL";
});
}
and when i run it.
Error Message :
Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public com.csc.mfs.messages.Message com.csc.mfs.controller.UserController.changePassword(java.lang.String,java.lang.String,java.lang.String)
Help me fix it, pls...
Issue is in this code.
#RequestBody String id, #RequestBody String oldPass,
#RequestBody String newPass
You cannot have multiple #RequestBody in same method,as it can bind to a
single object only (the body can be consumed only once).
APPROACH 1:
Remedy to that issue create one object that will capture all the relevant data, and than create the objects you have in the arguments.
One way for you is to have them all embedded in a single JSON as below
{id:"123", oldPass:"abc", newPass:"xyz"}
And have your controller as single parameter as below
public Message changePassword(#RequestBody String jsonStr){
JSONObject jObject = new JSONObject(jsonStr);
.......
}
APPROACH 2:
Create a custom implementation of your own for ArgumentResolver
You can't have request body for the GET method. If you want to pass username and password as part of request body then change RequestMethod type to POST/PUT.
If you want to use GET only then you will have to pass username and password as either path variables or request/query parameters - which is not best practice.
I would recommend changing RequestMethod and pass username & password as request body.