Spring REST service returns HTTP 400 status - java

I have a REST service written with the Spring Framework v 3.2.0
Here is the Controller:
#Controller
#RequestMapping("/volunteer")
public class VolunteerController {
//several methods not included (all HTTP GET & working properly)
#RequestMapping(value="/{volunteerId}/assignments/{sessionId}",
method=RequestMethod.PUT, params={"worked"})
#ResponseStatus(HttpStatus.NO_CONTENT)
public void setWorkedFlag(#PathVariable int volunteerId,
#PathVariable int sessionId,
#RequestParam int worked) {
assignmentMapper.setWorked(volunteerId, sessionId, worked);
}
}
When I submit a PUT request with this URL:
http://localhost:8080/volunteer/298/assignments/1?worked=true
I get a 400 restonse with the message : The request sent by the client was syntactically incorrect.
I can also confirm that the underlying resource has not need modified.
I have compared the annotations against the request URL and can't see anything obviously wrong. Also, the other methods (omitted here) all work correctly, so the Application Context has been set up OK.
Any ideas?

Related

POST doesn't work Spring java

I have an web application and I'm trying to creat a simple POSt method that will have a value inside the body request:
#RequestMapping(value = "/cachettl", method = RequestMethod.POST)
#CrossOrigin(origins = "http://localhost:3000")
public #ResponseBody String updateTtl(#RequestBody long ttl) {
/////Code
}
My request which I call from some rest client is:
POST
http://localhost:8080/cachettl
Body:
{
"ttl": 5
}
In the response I get 403 error "THE TYPE OF THE RESPONSE BODY IS UNKNOWN
The server did not provide the mandatory "Content-type" header."
Why is that happening? I mention that other GET requests are working perfectly.
Thanks!
Edit:
When I tried it with postman the error message I got is "Invalid CORS request".
Spring application just doesn't know how to parse your message's body.
You should provide "header" for your POST request to tell Spring how to parse it.
"Content-type: application/json" in your case.
You can read more about http methods here: https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Sending_and_retrieving_form_data
Updated:
Just in case of debug, remove useless annotations to test only POST mechanism. Also, change types of arg and return type. And try to use case-sensitive header.
#RequestMapping(value = "/cachettl", method = RequestMethod.POST)
public void updateTtl(#RequestBody String ttl) {
System.out.println("i'm working");
}
Since the error is about the response type, you should consider adding a produces attribute, i.e :
#RequestMapping(value = "/cachettl", method = RequestMethod.POST, produces=MediaType.APPLICATION_JSON_VALUE)
Since you are also consuming JSON, adding a consumes attribute won't hurt either :
#RequestMapping(value = "/cachettl", method = RequestMethod.POST, consumes=MediaType.APPLICATION_JSON_VALUE, produces=MediaType.APPLICATION_JSON_VALUE)
The error message is slightly misleading. Your server code is not being hit due an authentication error.
Since you say spring-security is not in play then I suspect you're being bounced by a CORS violation maybe due to a request method restriction. The response body generated by this failure (if any at all) is automatic and will not be of the application/json type hence the client failure. I suspect if you hit the endpoint with something that doesn't care for CORS such as curl then it will work.
Does your browser REST client allow you to introspect the CORS preflight requests to see what it's asking for?

Reject http requests handled by endpoints that don't specify security restrictions

I'm trying to protect against accidentally publishing an endpoint with no security in place. #Secured, #PreAuthorize, #PostAuthorize, etc.
I would want requests made to endpoints that don't specify any security requirements to be automatically rejected (403 Forbidden)
If an endpoint should indeed be open to the public it would need to be tagged by an annotation or similar.
#RestController
public class Controller {
#GetMapping("/endpoint1")
public void accidentallyOpen() {
}
#GetMapping("/endpoint2")
#UnrestrictedEndpoint
public void intentionallyOpen() {
}
}
In the above example I'm expecting
Requests made to /endpoint1 should return 403 Forbidden.
Requests made to /endpoint2 should return 204 No Content
I'm not sure where to start. Also if there are better ways of doing this I'm also happy to learn.

Spring Cloud Zuul: RedirectView redirecting to service and not gateway

I'm a bit new to microservices and Spring. I have Spring Cloud microservices (ports: 8xxx-8xxx) with a Zuul gateway running on port 9000. There's a method inside a controller on a UI service which should do a login and then return to a index.html page:
#RequestMapping(value="/do-login", method = RequestMethod.POST)
public RedirectView doLogin (#ModelAttribute("authEntity") final AuthEntity authEntity, final Model model) {
model.addAttribute(VERSION, applicationVersion);
model.addAttribute("authEntity", new AuthEntity());
authenticatedStatus = true;
model.addAttribute(AUTHENTICATED, authenticatedStatus);
return new RedirectView("index");
}
The problem is that when above method completes it returns an url of the microservice itself localhost:8888/index but not localhost:9000/services/ui/.
If I use a simpler method:
#RequestMapping(value="/do-login", method = RequestMethod.POST)
public String doLogin (#ModelAttribute("authEntity") final AuthEntity authEntity, final Model model) {
model.addAttribute(VERSION, applicationVersion);
model.addAttribute("authEntity", new AuthEntity());
authenticatedStatus = true;
model.addAttribute(AUTHENTICATED, authenticatedStatus);
return "index";
}
This returns correctly an url of gateway localhost:9000/services/ui/do-login but with a /do-login which I do not need.
Maybe I can get rid of /do-login/ part of url? Or maybe there is a solution for the incorrect redirect?
Thanks in advance!
If you use relative path like in return "index"; the result of the POST request sent to http://localhost:9000/services/ui/do-login will include URLs to http://localhost:9000/... unless coded otherwise in the jsp / freemarker / thymeleaf file.
If you want to get rid of the do-login, you would need to implement what's called a Redirect After Post (or redirect after form submit) approach so that a page refresh doesn't resubmit the form. If you take this approach, which seem what you were doing when using: return new RedirectView("index");, I can think of a couple ways of fixing the URL and set it to the proxy host.
1) http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/view/RedirectView.html, there are a couple of constructors that takes a host parameter, you would need to inject the proxy host in the controller class and most-likely in every controller class that implements Redirect After Post.
2) http://tuckey.org/urlrewrite/, include UrlRewriteFilter and configure rules to rewrite from webapp host to proxy host when webapp http status code response is 302. With this approach it would only be once rule and no need to inject proxy host to controller classes or change the return new RedirectView("index");`
3) Maybe this rewriting is implemented in Zuul and you don't need include and configure UrlRewriteFilter as suggested in 2).
As a side note, I have configured Nginx's proxy_pass to a Java webapps (where I implemented Redirect After Post) in the past and I don't recall having this issue. Will have to take a look at both UrlRewriteFilter and Nginx config files to expand on this.
I found that this (thanks to answer in here: Spring redirect url issue when behind Zuul proxy) seems to work as required (but is considered a 'workaround'):
#RequestMapping(value="/do-login", method = RequestMethod.POST)
public void doLogin (#ModelAttribute("authEntity") final AuthEntity authEntity,
final Model model,
HttpServletResponse servletResponse) throws IOException {
...
String rUrl = ServletUriComponentsBuilder.fromCurrentContextPath().path("/").build().toUriString();
servletResponse.sendRedirect(rUrl);
}

Spring Security - Ajax calls ignoring #ExceptionHandler

I have a controller in my project that handles all exceptions defined like this:
#ControllerAdvice
public class GlobalExceptionHandlingController {
#ResponseBody
#ExceptionHandler(value = AccessDeniedException.class)
public ResponseEntity accessDeniedException() {
Logger.getLogger("#").log(Level.SEVERE, "Exception caught!");
return new ResponseEntity("Access is denied", HttpStatus.FORBIDDEN);
}
}
I'm focusing on one specific exception here and that is AccessDeniedException that is thrown by Spring Security on unauthorized requests. This is working properly for "normal" aka non-ajax requests. I can click on a link or enter URL directly in the location bar and I will see this message if request is unauthorized.
However on AJAX request (using Angular for it) I'm getting standard 403 error page as a response but what's interesting is that I can see that AccessDeniedException is caught by this controller!
I did some research and it seems that I need to have custom AccessDeniedHandler so I made this:
Added this lines in my Spring Security configuration:
.and()
.exceptionHandling().accessDeniedPage("/error/403/");
and I made special controller just to handle this:
#Controller
public class AjaxErrorController {
#ResponseBody
#RequestMapping(value = "/error/403/", method = RequestMethod.GET)
public ResponseEntity accessDeniedException() {
return new ResponseEntity("Access is denied (AJAX)", HttpStatus.FORBIDDEN);
}
}
Now this is working fine but the exception is still caught in the first controller but return value of that method is getting ignored. Why?
Is this how it's supposed to be done? I have a feeling that I am missing something here.
I'm using Spring 4.2.5 with Spring Security 4.0.4.
Although I don't know all the details, my theory is that it can be a content type issue.
Often when doing AJAX requests, the response is expected to be in JSON, so the browser will add an Accept: application/json header to the request.
Your response entity on the other hand:
new ResponseEntity("Access is denied", HttpStatus.FORBIDDEN)
is a text response, the default Content-Type of this with a typical Spring setup is text/plain.
When Spring detects that it can't deliver a response with type the client wants, it fallbacks to the default error page.

Spring MVC http REST explanation, POST + GET

I have a webapp running on Spring SVM running in this direction:
http://localhost:8085/mongodb
The main controller that handles GET and POST request look looks like this:
#Controller
public class HomeController {
private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
private static final String DATA_FIELD = "Data";
#RequestMapping(value = "/", method = RequestMethod.GET)
public String home(Model model) {
return "home";
}
#RequestMapping(value = "/", method = RequestMethod.POST)
public ModelAndView createDomain(#RequestBody Domain Domain, HttpServletResponse httpresponse, WebRequest request) throws Exception{
logger.info("Welcome home! The client locale is {}.", "FUCK YOU2");
Domain createdDomain = Domain;
logger.info("Post Received" + Domain.toString());
//Create HTTP Response for the POST request
httpresponse.setStatus(HttpStatus.CREATED.value());
MongoOperations mongoOps = new MongoTemplate(new Mongo(),"rest_database");
mongoOps.insert(Domain);
System.out.println("PRINTED");
return new ModelAndView("post", DATA_FIELD, createdDomain);
}
}
Apparently the code is working. When I use the Chrome Extension Advanced Rest Client (here) to send POST request the client sends a POST then a GET every time, so I always get the page in the GET method.
The weird thing is that google chrome network developer tool shows a POST request pending and then a GET request that returns OK, the network debugger shows this:
https://dl.dropboxusercontent.com/u/7437425/network_debugger.png
When I send this request:
https://dl.dropboxusercontent.com/u/7437425/post_response.png
I would like to know if this is the standard way of sending a POST, by sending a GET shortly afterwards.
A funny thing is that my console never shows the result of this commands:
logger.info("Welcome home! The client locale is {}.", "FUCK YOU2");
System.out.println("PRINTED");
This leads me to believe that actually the method that handle POST requests is not running at all.
Thanks!
The method createDomain is not configured for handling post requests, but only put request.
What you are seeing in the debugger is that upon the post request something answers with a redirect to another URL where a get is performed. Yes, this is a pretty standard way of doing things. But this behavior does not come from the code you are showing, but is probably encoded somewhere else. Possibly in some default controllers that handle almost everything.

Categories

Resources