I have some Rest endpoints in my project which I call from a client application in another server. I have successfully disabled Cors using the #CrossOrigin annotation, and all the methods work fine except the Delete method which throws the following error on Chrome:
XMLHttpRequest cannot load http://localhost:8856/robotpart/1291542214/compatibilities. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1:8888' is therefore not allowed access. The response had HTTP status code 403.
Here is my controller:
#CrossOrigin(origins = "*")
#ExposesResourceFor(RobotPart.class)
public class RobotPartController {
//All endpoints are working except the Delete Mapping
#GetMapping("/robotpart")
public ResponseEntity<List<RobotPartResource>> listAllParts() {
//..
}
#GetMapping("/robotpart/{id}")
public ResponseEntity<RobotPartResource> getById(#PathVariable Integer id) {
//..
}
#GetMapping("/robotpart/{id}/compatibilities")
public ResponseEntity<Collection<RobotPartResource>> getRobotCompatibilities(#PathVariable Integer id,
//..
}
#PostMapping("/robotpart")
public ResponseEntity<RobotPartResource> getById(#RequestBody #Valid RobotPart newRobot) {
//..
#PutMapping("/robotpart/{id}")
public ResponseEntity<RobotPartResource> modify(#PathVariable Integer id, #Valid #RequestBody RobotPart newRobot) {
//...
}
#DeleteMapping("/robotpart/{id}")
public ResponseEntity<RobotPart> deleteById(#PathVariable Integer id) {
//...
}
}
Any way around it?
I found a solution, after analyzing http requests, I noticed that Access-Control-Allow-Methods header was missing the DELETE method, so I have added it by delete the #CrossOrigin annotation, and adding this bean to the configuration:
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/robotpart/**").allowedOrigins("*").allowedMethods("GET", "POST","PUT", "DELETE");
}
};
}
Adding to the answers above, the reason why disabling CORS won't work for DELETE (but works for GET and POST) is that this is the default behavior for the WebMvcConfigurer as stated here (highlighted in yellow):
Some of the previous answers have been very helpful, however, in my case (spring boot 2.7.4) I had to configure cors like this:
#Configuration
#EnableWebMvc
public class CorsConfiguration implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("GET", "POST","PUT", "DELETE");
}
}
This is my CORS configuration, it may be helpful to somebody
#Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration().applyPermitDefaultValues();
corsConfiguration.addAllowedMethod(HttpMethod.DELETE);
corsConfiguration.addAllowedMethod(HttpMethod.PATCH);
source.registerCorsConfiguration("/**", corsConfiguration);
return source;
}
Related
I have a Spring Boot app and I wanted to allow other origins to make requests because I got the 'cors' error. So I searched and I found this answer: Annotation CrossOrigin not working in Spring boot witch helped for the endpoints that have no body. On the other hand, those who have a body I get Internal Server Error.
Below is the configuration:
#Configuration
#EnableWebMvc
public class CorsConfiguration implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:63343", "http://localhost:3000", "https://smart-booking-ba548.web.app")
.allowedMethods("GET", "POST", "DELETE", "PUT");
}
}
The controllers have the #RestController annotation and the methods #Get/Post|Mapping. They return a ResponseEntity<Object>.
I solved it by putting the following method in main class.
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*");
}
};
}
I had the error "Access to XMLHttpRequest at 'http://localhost:8081/products/getPro' from origin 'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource" while I have added the annotation #CrossOrigin("*") in my spring boot application
#RestController
#RequestMapping("/products")
#CrossOrigin("*")
public class ProductController {
#Autowired
private ProductService productService;
#PostMapping(value = "/getPro", consumes = {"application/json"}, produces =
{"application/json"})
public ResponseEntity<?> getPro(#RequestBody Product product){
return this.productService.getPro(product);
}
In my Frontend I have :
export class ProductService {
public productModel : Product
private baseUrl = 'http://localhost:8081';
constructor(private http:HttpClient) { }
getPro () {
return this.http.post<Product>(this.baseUrl + "/products/getPro",
JSON.stringify(this.productModel));
}
}
Can anyone help me ?
PS :I am using this application like a feign client I mean in reality I am calling another application with FeignClient ! It can be the problem ?
I will suggest you to get rid of #crossOrigin.
You can do following in your config file
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");// Your allowed paths here
}
}
I will recommend you to move that all to angular only by using reverse proxy.
{
"/api": {
"target": "http://localhost:8081",
"secure": false
}
}
See github project here. https://github.com/vohra01/parking-demo-SG/blob/master/IdeaProjects/parking-demo/parking-ui-app/proxy.conf.json
I have a simple Spring MVC application in which I want to handle all the unmapped urls using #ControllerAdvice.
Here is the controller:
#ControllerAdvice
public class ExceptionHandlerController {
#ResponseStatus(HttpStatus.NOT_FOUND)
#ExceptionHandler(NoHandlerFoundException.class)
public String handle404() {
return "exceptions/404page";
}
}
Still, every time get Whitelabel Error Page.
I tried using RuntimeException.class, HttpStatus.BAD_REQUEST and extending the class with NoHandlerFoundException but no use.
Any suggestions?
To make it work, you need to set throwExceptionIfNoHandlerFound property on DispecherServlet. You can do that with:
spring.mvc.throwExceptionIfNoHandlerFound=true
in application.properties file, otherwise the requests will always be forwarded to the default servlet and NoHandlerFoundException would ever be thrown.
The problem is, even with this configuration, it doesn't work. From the documentation:
Note that if
org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler is
used, then requests will always be forwarded to the default servlet
and NoHandlerFoundException would never be thrown in that case.
Because Spring Boot uses by default the org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler you'll have to override this using your own WebMvcConfigurer:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
#EnableWebMvc
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
// Do nothing instead of configurer.enable();
}
}
Of course, the above class might be more complicated in your case.
Another way to do so is ErrorController
#Controller
public class MyErrorController implements ErrorController {
#GetMapping("/error")
public ModelAndView errorHandler(HttpServletRequest req) {
// Get status code to determine which view should be returned
Object statusCode = req.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
// In this case, status code will be shown in a view
ModelAndView mav = new ModelAndView("error_default");
mav.addObject("code", statusCode.toString());
return mav;
}
public String getErrorPath() {
return "/error";
}
}
Add the below line in application.properties
spring.mvc.throwExceptionIfNoHandlerFound=true
and #EnableWebMvc with #ControllerAdvice,
Note: add #Override over handleNoHandlerFoundException method
this worked for me!
#EnableWebMvc
#ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
CustomResponse response = new CustomResponse();
response.setTimestamp(LocalDateTime.now());
response.setMessage(ApplicationErrorCodes.NO_HANDLER_FOUND.getErrorMessage());
response.setStatus(HttpStatus.BAD_REQUEST);
response.setErrorCode(ApplicationErrorCodes.NO_HANDLER_FOUND.getErrorCode());
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
}
Controller needs uses .htm extensions for all handlers, including JSON REST endpoints. How should I test for REST endpoints?
Problem:
I cannot disable suffix interpretation and I am getting 406 "Could not find acceptable representation"
Tried attempts:
I reviewed posts on stackoverflow related to 406, but could not find relevant one to the case where 'htm' suffix is used in tests. When you remove '.htm' suffix from both Controller and Test - the test is passing.
Here is controller with /changePassword.htm endpoint:
#Controller
public class MainController {
public static class ResultBean {
private final String result;
public String getResult() {
return result;
}
public ResultBean(String result) {
this.result = result;
}
}
#RequestMapping(value="/changePassword.htm", method= RequestMethod.POST, produces = { "application/json" })
public #ResponseBody ResultBean changePassword (
#RequestParam("username") String username, #RequestParam("password") String password) {
return new ResultBean("OK");
}
}
And here is the test with configuration:
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes = { HomeControllerTest.Config.class })
public class HomeControllerTest {
#InjectMocks
private MainController controller = new MainController();
private MockMvc mvc;
#Configuration
#EnableWebMvc
public static class Config extends WebMvcConfigurerAdapter {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false)
.favorParameter(true)
.parameterName("mediaType")
.ignoreUnknownPathExtensions(true)
.ignoreAcceptHeader(false)
.useJaf(false)
.defaultContentType(MediaType.APPLICATION_JSON);
}
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(false);
}
}
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
mvc = MockMvcBuilders.standaloneSetup(controller)
.build();
}
#Test
public void shouldPassChangePasswordBean() throws Exception {
mvc.perform(post("/changePassword.htm")
.accept("*/*")
.param("username", "example")
.param("password", "abcdef")
)
.andExpect(status().isOk()); // Test produces 406 instead of 200
}
}
Any idea?
On newer version of Spring (4+ I think), mime type is determined from suffix first.
So If you use a .htm suffix, Spring will default to produce HTML even if you don't want to.
One way to bypass this is to use a filter that rewrite URL. For instance tuckey URL rewriter filter
With this, you can set some rules like:
/my/page/that/return/json.htm is rewriten to /my/page/that/return/json so that Spring can produce data according to the Accept header.
with Spring 5, try changing your URL of your web service to .json! that is the right fix. great details here http://stick2code.blogspot.com/2014/03/solved-orgspringframeworkwebhttpmediaty.html
I'm receiving a 404 error when accessing a particular page in my spring boot web application.
The strange thing is that I don't receive that error when the resource is mapped to a different location.
#RequestMapping(value="report", method = RequestMethod.GET)
public String getReportPage() {
return "templates/report.html";
}
works just fine while
#RequestMapping(value="report/{uuid}", method = RequestMethod.GET)
public String getReportPage() {
return "templates/report.html";
}
does not. I need the uuid parameter for my angular service so I cannot simply remove that from the path. I've tried adding the path variable to the model; that makes no difference.
The directory structure is set up as follows:
webapp
resources
...
templates
report.html
The configuration is pretty much an out of the box spring boot with some added resource handlers and some basic security:
#Configuration
public class MvcConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/resources/", "file:resources/");
}
}
#Configuration
#EnableWebSecurity
class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and()
.csrf().disable();
}
}
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter {
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService());
auth.authenticationProvider(authProvider());
}
.... custom user details service and authentication provider ...
}
Any thoughts about what may be causing this issue?
Edit: After some further investigation, it looks like anything mapped beyond the first level doesn't work for the web controller (but the rest controllers are working just fine). For example, a mapping with the value /web/report doesn't work either.
While looking through debug messages I found that the application was looking for the pages in the wrong place:
DEBUG : Looking up handler method for path /report/templates/report.html
Which is why only top level requests were working.
Changing the mapping as such:
#RequestMapping(value="report/{uuid}", method = RequestMethod.GET)
public String getReportPage() {
return "/templates/report.html";
}
fixed the problem.