Spring POST controller request body as controller variable - spring

Usually, we get request body as a parameter of controller methods. Spring binds the body to the type of the variable.
I want the request body as a property of the controller so that other private methods can access it.
public class UserController {
private String body; // Request body should automatically bind to this String.
private HttpServletRequest request;
#Autowired
public UserController(HttpServletRequest request) {
this.request = request;
}
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<String> create(#RequestBody String body) {
// I know I can do like: this.body = body.
// But don't want to do that.
}
private someMethod() {
// Give me access of request body please....
}

Controllers are by default singleton scoped beans (created once per container initialisation). Assigning request body (which changes in every request) to something which is created once and can be used everywhere can lead you to serious trouble. If you are just looking to apply some logic in controller by using private method, you can pass the body as argument to that method like this.
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<String> create(#RequestBody String body) {
someMethod(body);
}
private void someMethod(String body) {
// voila! you have the request body here
}

Related

Accessing the http request in spring security custom expression

I'm trying to write a custom spring security expression with PreAuthorize.
I want to achieve this:
#PreAuthorize(customAuthCheck(param1, param2))
#RestController
public String getRestrictedInfo(HttpServletRequest request) {
//
}
where param1, param2 depends on the business logic.
In my AuthSecurityExpressionRoot class:
public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {
public boolean customAuthCheck(String param1, String param2) {
// check authorization
}
}
The problem is, I want to access the http request (specifically, the "Authorization" header, and the httpMethod) inside the customAuthCheck method. How can I implement that?
You can pass the entire request to your MethodSecurityExpressionOperations object, then extract whatever headers you need. In your case, use this:
public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {
public boolean customAuthCheck(HttpServletRequest request) {
String jwtString = request.getHeader("Authorization");
// decode/process the JWT
// check authorization
}
}
Then in your controller:
#PreAuthorize(customAuthCheck(#request))
#RestController
public String getRestrictedInfo(HttpServletRequest request) {
//
}
You can pass other params along with the header too, if you want, as long as they are part of the method signature, e.g.:
#PreAuthorize(customAuthCheck(#param1, #request))
public String getRestrictedInfo(HttpServletRequest request, #RequestParam String param1) {
//
}

How to get Request URL in Spring Boot

I need to submit request URL as a String parameter to a method
#RequestMapping(value = "/test", method = RequestMethod.POST)
public void testItt(#RequestParam String requestParameter, #RequestURL String requestUrl) {
// Do something with requestUrl
}
How to submit Request URL correctly?
I tried request.getRequestURL().toString()
But I feel there must be a better way.
Never just grab the URL from the request. This is too easy! programming is supposed to be hard and when it's not hard, you MAKE it hard! :)
But you can retrieve the URL the way you show up above
So lets start off with an annotation that represents the value you want to retrieve
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface RequestURL {
}
This will work as a way to inject the value you already have access to.
Next we need to create a class that can build the URL string
public class RequestUrlArgumentResolver
implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.getParameterAnnotation(RequestURL.class) != null;
}
#Override
public Object resolveArgument(
MethodParameter methodParameter,
ModelAndViewContainer modelAndViewContainer,
NativeWebRequest nativeWebRequest,
WebDataBinderFactory webDataBinderFactory) throws Exception {
HttpServletRequest request
= (HttpServletRequest) nativeWebRequest.getNativeRequest();
//Nice and cozy at home surrounded by safety not obfuscation
return request.getRequestURL().toString();
}
}
Next thing we need to do is get the framework to recognize the handler for this annotation.
add the method below to your configuration (If your config does not implement WebMvcConfigurer you may need to implement this class or create a new config which does and include the new config)
...
#Override
public void addArgumentResolvers(
List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new RequestUrlArgumentResolver());
}
...
Then finally we are back to your original request mapping and it should work as originally written
#RequestMapping(value = "/test", method = RequestMethod.POST)
public void testItt(#RequestParam String requestParameter,
#RequestURL String requestUrl) {
// Do something with requestUrl
}
Credits - https://www.baeldung.com/spring-mvc-custom-data-binder

Jackson failed to convert request element

I'm using Spring to craft a REST API which exposes a POST endpoint. I'm able to reach the endpoint, but I'm having trouble reading the request body.
The following code works: payload contains the object sent.
#RestController
public class RestController {
#RequestMapping(value = "/endpoint")
public ResponseEntity endpoint(#RequestParam("payload") String str) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
Payload payload = objectMapper.readValue(str, Payload.class);
return ResponseEntity.status(HttpStatus.OK).build();
}
}
However, the following code DOES NOT works: it throws an exception.
#RestController
public class RestController {
#RequestMapping(value = "/endpoint")
public ResponseEntity endpoint(#RequestParam("payload") Payload payload) throws IOException {
return ResponseEntity.status(HttpStatus.OK).build();
}
}
The exception:
Failed to convert request element: org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException: Failed to convert value of type 'java.lang.String' to required type 'beans.Payload'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'beans.Payload': no matching editors or conversion strategy found
Why the latter doesn't work? I thought Spring decode request parameters in the same way...
UPDATE: my Payload class:
#JsonIgnoreProperties(ignoreUnknown = true)
public class Payload {
#JsonProperty("type")
private String type;
#JsonProperty("callback_id")
private String callbackId;
#JsonProperty("message_ts")
private String message_ts;
#JsonProperty("response_url")
private String responseUrl;
protected Payload() {}
public String getType() {
return type;
}
public String getCallbackId() {
return callbackId;
}
public String getMessage_ts() {
return message_ts;
}
public String getResponseUrl() {
return responseUrl;
}
}
UPDATE: I'm testing the endpoint with Postman. This is what I'm sending:
KEY VALUE
payload { "type": "test" }
and this is the error I got:
{
"timestamp": "2018-08-28T10:38:33.133+0000",
"status": 415,
"error": "Unsupported Media Type",
"message": "Content type 'multipart/form-data;boundary=--------------------------586093407866564427326096;charset=UTF-8' not supported",
"path": "/endpoint"
}
Payload constructor should not be protected. It should be public.
Change it to
Public PayLoad(){}
Otherwise, controllers cant create a payload object when mapping is done.
Use #RequestBody instead of #RequestParam. Your Payload is in post body and #RequestBody annotation will deserialize it to payload.
Just get rid of protected Payload() {} . As you don't have any parameterized constructer you are fine, Java compiler will take care of adding the default constructer to the compiled byte code.
And you need to change this
Controller method
#RestController
public class RestController {
#RequestMapping(value = "/endpoint")
public ResponseEntity endpoint(#RequestParam("payload") Payload payload) throws IOException {
return ResponseEntity.status(HttpStatus.OK).build();
}
}
Changes
a. Change HTTP request method to POST it, instead of GET . (method = RequestMethod.POST) .
b. Change Payload to a message body insteda of request param (#RequestParam("payload") Payload payload --> #RequestBody Payload payload ).
Change it as
#RequestMapping(value = "/endpoint", method = RequestMethod.POST)
public ResponseEntity endpoint(#RequestBody Payload payload) throws IOException {
return ResponseEntity.status(HttpStatus.OK).build();
}
Your URL patterns are mal-configured. Try,
#RestController
#RequestMapping(value = "/")
public class PayLoadController {
#RequestMapping(value = "endpoint/",method = RequestMethod.POST)
public ResponseEntity endpoint(#RequestBody Payload payload) throws IOException {
return ResponseEntity.status(HttpStatus.OK).build();
}}
Just copy paste this code and rename controller file name.
Then post your data to,
localhost:8080/endpoint/

Can not attach body to my POST request using Spring MockMvc

I'm trying to test my rest controller. No issues with GETs, but when I try to test a POST method I'm unable to attach the body.
private static final MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(),
Charset.forName("utf8"));
private ObjectMapper jsonMapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
#Test
public void test1() throws Exception {
//...Create DTO
//...Create same pojo but as entity
when(serviceMock.addEntity(e)).thenReturn(e);
mvc.perform(post("/uri")
.contentType(contentType)
.content(jsonMapper.writeValueAsString(dto))
)
.andDo(print())
.andExpect(status().isCreated())
.andExpect(content().contentType(contentType)); //fails because there is no content returned
}
This is the request output:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /uri
Parameters = {}
Headers = {Content-Type=[application/json;charset=UTF-8]}
There is no body. Why? I have printed jsonMapper.writeValueAsString(dto) and is not null.
edit:
Adding controller code:
#RestController
#RequestMapping("/companies")
public class CompanyController {
#Autowired
private CompanyService service;
#Autowired
private CompanyMapper mapper;
#RequestMapping(method=RequestMethod.GET)
public List<CompanyDTO> getCompanies() {
List<Company> result = service.getCompanies();
return mapper.toDtoL(result);
}
#RequestMapping(method=RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public CompanyDTO createCompany(#RequestBody #Valid CompanyDTO input) {
Company inputE = mapper.toEntity(input);
Company result = service.addCompany(inputE);
return mapper.toDto(result);
}
Solved.
The mock call should use any instead of a concrete object: when(serviceMock.addCompany(any(Company.class))).thenReturn(e);
I needed to override the equals method of the entity class to pass this statement: verify(serviceMock, times(1)).addCompany(e);

Mapping HTTP request value to object in Sprint Boot application when field and value has different field name?

This instance of class AuthorizationRequest is created during HTTP request, params are sent in query string.
#RequestMapping(value = "/authorize", method = {RequestMethod.GET, RequestMethod.POST})
public String authorize(
#Valid AuthorizationRequest authorizationRequest,
BindingResult result
) {
I would like to use this code, this is an example parameter from AuthorizationRequest class:
#NotEmpty
#JsonProperty("client_id")
private String clientId;
but new instance has a filed clientId empty, because in query string there is a value for this parameter under client_id parameter.
Is there some way how to tell Spring which parameter from HTTP request should use for one particular field of created instance? I need to solve problem with different naming clientId andclient_id`.
What you need is a setter to handle each kind of clientId. Keep in mind that if both clientId and client_id is specified that it is unknown which will take precedence.
//These methods will allow clientId or client_id
to be used as arguments setting the same field this.clientId
public void setClient_id(String client_id) {
this.clientId = client_id;
}
public void setClientId(String client_id) {
this.clientId = client_id;
}
I tested this with a post and a get
get - http://localhost:8080/authorize?clientId=2&username=someusername
get - http://localhost:8080/authorize?client_id=2&username=someusername
post - http://localhost:8080/authorize
Content-Type: application/x-www-form-urlencoded
Body: clientId=2&username=someusername
or Body: client_id=2&username=someusername
I was only able to have #JsonProperty("client_id") to be recognized when I annotated AuthorizationRequest with #RequestBody and then used application/json instead of application/x-www-form-urlencoded
I found the solution with own implementation of org.springframework.web.method.supportHandlerMethodArgumentResolver.
Resolver implementation:
public class AuthorizationRequestResolver implements HandlerMethodArgumentResolver {
private static Logger LOG = Logger.getLogger(AuthorizationRequestResolver.class);
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().equals(AuthorizationRequest.class);
}
#Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
AuthorizationRequest authRequest = mapFromServletRequest(request);
return authRequest;
}
private AuthorizationRequest mapFromServletRequest(HttpServletRequest request) {
AuthorizationRequest authorizationRequest = new AuthorizationRequest();
authorizationRequest.setClientId(request.getParameter("client_id"));
authorizationRequest.setRedirectUri(request.getParameter("request_uri"));
authorizationRequest.setResponseType(request.getParameter("response_type"));
authorizationRequest.setScope(request.getParameter("scope"));
authorizationRequest.setState(request.getParameter("state"));
return authorizationRequest;
}
}
and cofiguration class:
#Configuration
public class WebappConfig extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new AuthorizationRequestResolver());
}
}

Resources