I have a use case where I need to verify if the incoming request body to my controller contains any special characters in a Hybris storefront. Though it can be achieved from the front-end by blocking any special characters, we require back-end validation.
I tried using HandlerIntercepterAdapter to intercept the request and validate for any special characters. But whenever I use request.getReader() or request.getInputStream() and read the data, request body is cleared.
I tried using IOUtils.copy() but this too reads from the original request and makes the body empty.
Even after wrapping the request with HttpServletRequestWrapper or ContentCachingRequestWrapper, the request body gets cleared. I guess internally somewhere it uses the same reference.
I tried following this thread but was unable to solve this issue.
I am looking for a solution where I can extract the request body and validate it without letting it get cleared so it can be used in different controllers [or] any alternative approach which can help in preventing any special characters to hit the controller.
any alternative approach which can help in preventing any special characters to hit the controller.
What if you try to do the following ...
Get the request body
Process it
Set the request body again in your filter by setting the body to the processed version ?
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest originalRequest = (HttpServletRequest) request;
HttpServletResponse originalResponse = (HttpServletResponse) response;
/**
* 2.Read the original request body and change it
*/
String originalRequestBody = ServletUtil.readRequestBody(originalRequest); // Read the original request body
// Body is processed here !
String modifyRequestBody = processBody(originalRequestBody); // Modify request body (clear text)
HttpServletRequest orginalRequest = (HttpServletRequest) request;
ModifyRequestBodyWrapper requestWrapper = new ModifyRequestBodyWrapper(orginalRequest, modifyRequestBody);
/**
* 3. Build a new response object
*/
ModifyResponseBodyWrapper responseWrapper = new ModifyResponseBodyWrapper(originalResponse);
chain.doFilter(requestWrapper, responseWrapper);
String originalResponseBody = responseWrapper.getResponseBody(); // Original response body (clear text)
String modifyResponseBody = this.encryptBody(originalResponseBody); // Modified response volume (ciphertext)
/**
* 4.Output the modified response body with the output stream of the original response object
* To ensure that the response type is consistent with the original request, and reset the response body size
*/
originalResponse.setContentType(requestWrapper.getOrginalRequest().getContentType()); // Be consistent with the request
byte[] responseData = modifyResponseBody.getBytes(responseWrapper.getCharacterEncoding()); // The coding is consistent with the actual response
originalResponse.setContentLength(responseData.length);
#Cleanup ServletOutputStream out = originalResponse.getOutputStream();
out.write(responseData);
}
Here is a code example, which implements this.
The input should be set inside a form.
In your controller, you can use a validator :
#RequestMapping(value = "/process", method = RequestMethod.POST)
public String doValidateAndPost(final MyForm form, final BindingResult bindingResult,
final HttpServletRequest request, final Model model){
getMyValidator().validate(form, bindingResult);
if (bindingResult.hasErrors())
{
return MY_PAGE;
}
The validator will look like this :
#Override
public void validate(final Object object, final Errors errors)
{
final MyForm form = (MyForm ) object;
final String data = form.getMyData();
Pattern p = Pattern.compile("[^a-z0-9 ]", Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(data );
boolean b = m.find();
if (b)
{
errors.rejectValue("myData", "myData.invalid");
}
}
You can also use the #Valid annotation :
public String doValidateAndPost(#Valid final MyForm form ...
And set in your form :
#Pattern(regexp = "[a-z0-9 ]")
private String myData;
Related
I am using the Spring ClientHttpRequestInterceptor to capture all outgoing HTTP calls from my applications in order to log the data. In addition to the data that I am already collecting in the interceptor, I want to somehow fetch the name of the function from which the HTTP call originated. So, as an example, if a method called getStuffFromUrl is making the HTTP call using the Spring RestTemplate as follows,
public String getStuffFromUrl() {
...
return restTemplate.exchange(url, HttpMethod.GET,entity, String.class).getBody();
}
when I capture this outbound HTTP call in my interceptor, I want to retrieve the name of the method getStuffFromUrl as well. How could I go about doing this?
If you are allowed to modify your HTTP request, one way would be to add a ad-hoc HTTP header for the method name :
public String getStuffFromUrl() {
HttpHeaders headers = new HttpHeaders();
headers.add("JavaMethod", "getStuffFromUrl");
entity = new Entity(headers)
...
return restTemplate.exchange(url, HttpMethod.GET,entity, String.class).getBody();
}
You could then get back the method name and remove the header from within the ClientHttpRequestInterceptor prior the HTTP request is actualy sent out.
ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution)
throws IOException {
String javaMethodName="Unknown";
List<String> javaMethodHeader = request.getHeaders().remove("JavaMethod");
if(javaMethodHeader!=null && javaMethodHeader.size()>0) {
javaMethodName = javaMethodHeader.get(0);
}
log.info("Calling method = "+ javaMethodName);
execution.execute(request, body);
}
(provided code not tested)
I have a rest end point designed in spring boot. Tomcat is being used as embedded server. It takes a query parameter.
When I pass query parameter as param1%uFF07 tomcat internally reads parameter as null
When I pass query parameter as param1%FF07 tomcat reads as some character.
tomcat only reads '%' character when followed by two hexadecimal numbers, if u is placed after '%' character tomcat parse parameter as null with message
Character decoding failed. Parameter [name] with value [param1%uFF07]
has been ignored. Note that the name and value quoted here may be
corrupted due to the failed decoding. Use debug level logging to see
the original, non-corrupted values. Note: further occurrences of
Parameter errors will be logged at DEBUG level.
Here is spring boot controller code
#RestController
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
#RequestMapping("/greeting")
public Greeting greeting(#RequestParam(value = "name", required = false) String name) {
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
}
You are passing % sign in your url, but % is symbol in url, to pass % as it is... you will have to pass %25 then it will work as you expected.
So, if you pass %25uFF07 then it will show you %uFF07 as value.
No need to change anything in application.properties or any kind of settings. I have tested this in my project.
Please feel free to ask for any clarification. Hope It Helps.
I found out a way using filters. Basics about filters could be found over here. We can intercept request query string there and use Tomcat UDecoder class to parse the query string and if any exception is thrown we can show response of 400
public class SimpleFilter implements Filter {
private final UDecoder urlDecoder = new UDecoder();
private final Logger logger = LoggerFactory.getLogger(SimpleFilter.class);
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
String queryString = httpServletRequest.getQueryString();
if (queryString != null) {
ByteChunk byteChunk = new ByteChunk();
byteChunk.setBytes(queryString.getBytes(), 0, queryString.length());
try {
urlDecoder.convert(byteChunk, true);
} catch (IOException ioException) {
logger.error("Hazarduos character found in request parameter.");
httpServletResponse.setStatus(HttpStatus.BAD_REQUEST.value());
return;
}
}
chain.doFilter(request, response);
}
#Override
public void destroy() {
}
}
I'm building a REST API using Java and Spring and I need to handle a POST request in my controller, but I need to extract the body from that request which is a JSON and also the "origin" of that request,
#RequestMapping(value = "/create", method = RequestMethod.POST)
public XXX createMyObject(#RequestBody String data, YYY){
MyObject mo = new MyObject();
mo.setData = data;
mo.setOrigin = yyy;
myRepository.save(mo);
return XXX;
}
I have a few questions: First is how can I obtain the origin of that request( which I guess is an url that travels in the header?), is there a similar annotation as the #RequestBody for that?.
My second question is what is usually the proper object to return in these kind of post methods as a response.
To answer your questions:
If you include HttpServletRequest in your method parameters you will be able to get the origin information from there. eg.
public XXX createMyObject(#Requestbody String data, HttpServletRequest request) {
String origin = request.getHeader(HttpHeaders.ORIGIN);
//rest of code...
}
For rest responses you will need to return a representation of the object (json) or the HttpStatus to notify the clients whether the call wass successful or not. eg
Return ResponseEntity<>(HttpStatus.ok);
You should be able to get headers and uris from HttpServletRequest object
public XXX createMyObject(#RequestBody String data, HttpServletRequest request)
As for response I'd say return String which would be a view name to which you can pass some attributes saying that operation was successful or not, or ModelAndView.
#Autowired
private HttpServletRequest servletRequest;
You can declare request object and then access in method to get Uri
Try this:
#RequestMapping(value = "/create", method = RequestMethod.POST)
public XXX createMyObject(HttpServletRequest request, #RequestBody String body) {
String origin = URI.create(request.getRequestURL().toString()).getHost();
System.out.println("Body: " + body + " Origin:" + origin);
return XXX;
}
There are three code snippets below. A cookie does not show up in the browser with the first two approaches. A cookie does show up in the browser with the last approach but I am looking for an approach that lets me send an arbitrary response object (like TestResponse), not just a file.
Maybe another difference to note is that I'm sending an async request using the whatwg-fetch.js library for the first two snippets but hitting the route via browser refresh in the last snippet. I need the cookie to be set by the time an async request completes.
(Snippet 1)The below code snippet does not work as the cookie does not show up in the browser.
#PostMapping(value="/test")
public ResponseEntity<TestResponse> test(#RequestBody TestReq
testReq, HttpServletRequest req, HttpServletResponse res){
String testMessage = testReq.getTestMessage();
TestResponse testResponse = new TestResponse();
testResponse.setTestMessage(testMessage);
HttpHeaders headers = new HttpHeaders();
headers.add("Set-Cookie", "key="+"value");
ResponseEntity<TestResponse> testResponseEntity
= new ResponseEntity<TestResponse>(testResponse, headers, HttpStatus.OK);
return testResponseEntity;
}
(Snippet 2) This next code snippet also does not work as the cookie does not show up in the browser.
#PostMapping(value="/test")
public ResponseEntity<TestResponse> test(#RequestBody TestReq
testReq, HttpServletRequest req, HttpServletResponse res){
String testMessage = testReq.getTestMessage();
TestResponse testResponse = new TestResponse();
testResponse.setTestMessage(testMessage);
ResponseEntity<TestResponse> testResponseEntity
= new ResponseEntity<TestResponse>(testResponse, HttpStatus.OK);
Cookie c = new Cookie("key", "value");
res.addCookie(c);
return testResponseEntity;
}
(Snippet 3) The following code works. The cookie shows up in my browser when I load localhost.
#GetMapping(value="/")
public String home(HttpServletRequest req, HttpServletResponse res){
Cookie c = new Cookie("key","value");
res.addCookie(c);
return "views/app.html;
}
I am having two mapping methods in my controller. one is redirecting to other.
Before redirecting I'm setting a header in response. But I getting the request header as null.
These are my methods in controller. both are in same controller.
#RequestMapping(value="/testStart", method=RequestMethod.POST)
public String testStart(HttpServletRequest request, HttpServletResponse response){
String token = "126712810-1289291":
response.addHeader("authToken", token);
return "redirect:/test";
}
#RequestMapping(value="/test", method={ RequestMethod.POST, RequestMethod.GET })
public String getTestPage(Model model, HttpServletRequest request, HttpServletResponse response){
String token = request.getHeader("authToken");
System.out.println(token); //prints null
model.addAttribute("Testtoken", token);
System.out.println("Test page about to load ..");
return "test";
}
I'm using postman client to test this api. It is hitting the url and redirecting to other url. but the header is null.
I don't know what's wrong. can any one help me to figure this out? Thanks
With Redirect method web app tells browser to load the page which you want to redirect. So this makes new http request from browser, the original requests are not reachable at this moment.
So your problem can be solved with Forward method. Web app forwards all request data to another handler method internally
return "forward:/test";
Additionally , please change your
String token = request.getHeader("authToken");
with
String token = response.getHeader("authToken");
because you are adding your authToken to the response object.(from comments HttpServletResponse -> getHeader(String name) works since Servlet 3.0)
EDIT :
this code will be your complete solution "/test" method supports forwarded reuqest and also supports request from browser.
(You want to get token info from request becuase you want to call /test method without forwarding, so it works in this way, but when forwarding you cant add header so you tried to add in response and get it from request but that doesnt work in that way so you need to resolve token according to dispatcher's type so check the code )
#RequestMapping(value="/testStart", method=RequestMethod.POST)
public String testStart(HttpServletRequest request, HttpServletResponse response){
String token = "126712810-1289291";
request.setAttribute("authToken", token);
return "forward:/test";
}
#RequestMapping(value="/test", method={ RequestMethod.POST, RequestMethod.GET })
public String getTestPage(Model model, HttpServletRequest request, HttpServletResponse response)
{
//-----------------
//resolving token
String token = null;
DispatcherType type = request.getDispatcherType();
if(type == DispatcherType.FORWARD)
{
token = (String) request.getAttribute("authToken");
}
else if(type == DispatcherType.REQUEST)
{
token = (String) request.getHeader("authToken");
}
//-----------------
System.out.println(token); //prints the value
model.addAttribute("Testtoken", token);
System.out.println("Test page about to load ..");
return "test";
}
Use RedirectAttributes to pass parameters with redirect URL:
#RequestMapping(value="/test1", method=GET)
public String test(RedirectAttributes redirectAttributes){
redirectAttributes.addAttribute("authToken", "val");
return "redirect:/test";
}
or if you can use forward:/test you can go with request.setAttribute and request.getAttribute
Still if you want to add in header then use RestTemplate and HTTPHeaders and get the response String