Im new to both Java (spring) and React and the most issues I seem to get is by the annoying CORS. I did have same CORS issues before and by looking online I found serveral solutions, but mainly the answers were directing to the back-end, that’s where you need to allow certain domains.
So I added first @CrossOrigin (I know the second one is redundant, still wanted to try everything)
@CrossOrigin(origins = "*", allowedHeaders = "*") @RestController @RequestMapping(value = "/api/v1") public class OrderController { @Autowired private OrderRepository orderRepository; @CrossOrigin @GetMapping(value = "/admin/orders") public ResponseEntity<Object> getOrders() { List<Order> orders = orderRepository.findAll(); return new ResponseEntity<>(orders, HttpStatus.OK); } ......rest code
that did the trick on other controllers, but not this one. For the “Admin” part of “OrderController” as stated above I did a check with Postman and JWT token to see if this endpoint gets blocked. It doesn’t get blocked, I receive the order I requested.
On the front-end I tried serveral options:
.... rest code useEffect(() => { async function getOrders() { setError(false); toggleLoading(true); let url = `http://localhost:8080/api/v1`; if (isAdmin) { url = `${url}/admin/orders/`; } else { //fix!!! let user_id = 1; url = `${url}/orders/customer/${user_id}/`; } console.log(url); try { const result = await axios.get(url, { headers : { "Authorization" : `Bearer ${token}`, 'Content-Type': 'application/json', "Access-Control-Allow-Origin": "*", } }); console.log(result); if (result.data.length > 0) { setOrderItems(result.data); console.log(orderItems); toggleLoading(false); } } catch (e) { console.error(e); setError("Error retrieving order"); toggleLoading(false); } } ..... rest code
Basically there are 3 user roles, customer and admin are used here, both witj JWT authentication. So, I want to sent the headers with the get request, as it needs authentication. On every try (also with response => .then/.catch) I get the ‘Error retrieving order’ part and in my log I find:
CORS Preflight did not succeed
Via method OPTIONS. On the Headers I find:
HTTP/1.1 403 X-Content-Type-Options: nosniff X-XSS-Protection: 1; mode=block Cache-Control: no-cache, no-store, max-age=0, must-revalidate Pragma: no-cache Expires: 0 X-Frame-Options: DENY Vary: Origin Vary: Access-Control-Request-Method Vary: Access-Control-Request-Headers Access-Control-Allow-Origin: * Access-Control-Allow-Methods: GET,HEAD,POST Access-Control-Allow-Headers: access-control-allow-origin, authorization Access-Control-Max-Age: 1800 Allow: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH Content-Length: 0 Date: Sat, 10 Apr 2021 16:33:16 GMT Keep-Alive: timeout=60 Connection: keep-alive
Then below the first error is a second error with:
NS_ERROR_DOM_BAD_URI
on the GET method, which has the headers:
GET /api/v1/admin/orders/ undefined Host: localhost:8080 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:87.0) Gecko/20100101 Firefox/87.0 Accept: application/json, text/plain, */* Accept-Language: nl,en-US;q=0.7,en;q=0.3 Accept-Encoding: gzip, deflate Authorization: Bearer *key removed* Access-Control-Allow-Origin: * Origin: http://localhost:3000 Connection: keep-alive Referer: http://localhost:3000/
I tried another shot at creating a proxy in the package.JSON to ‘dodge’ the browser CORS
"proxy": "http://localhost:8080"
Does nothing.
My SpringSecurity config:
@Configuration @EnableWebSecurity public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired public CustomUserDetailsService customUserDetailsService; @Autowired private JwtRequestFilter jwtRequestFilter; @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(customUserDetailsService); } @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { //JWT token authentication http .csrf().disable() .authorizeRequests() .antMatchers("/api/v1/customers/**").hasRole("CUSTOMER") .antMatchers("/api/v1/admin/**").hasRole("ADMIN") .antMatchers("/api/v1/users/**").hasRole("USER") .antMatchers("/api/v1/manufacturer/**").hasRole("MANUFACTURER") .antMatchers("/api/v1/authenticated").authenticated() .antMatchers("/api/v1/authenticate").permitAll() .anyRequest().permitAll() .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); } }
So, I have no clue wether it’s bad headers sent from the front-end or something blocking (CORS or Auth) from the back-end. Any advice?
@EDIT:
I forgot to mention, I even added
@SpringBootApplication @RestController public class BeershopApplication { public static void main(String[] args) { SpringApplication.run(BeershopApplication.class, args); } @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**"); } }; } }
In the application.java file.
Advertisement
Answer
Browsers will not allow you to call some domain from another domain, for example from you are calling localhost:8080 from localhost:3000. It is not ok to disable this validation on production but it’s ok if you disable it in the development environment because the production environment will hold both things behind one domain. You can solve this problem in different ways.
- If you are using webpack, you can add a proxy in webpack configurations on react side like
"proxy": "http://localhost:8080"
and you can use relative paths in you axios requests likeawait axios.get('/api/v1/admin', {...
, this proxy will forward your requests tolocalhost:8080
- You can disable cors on the backend side or add localhost as an allowed origin for some routes for:
@Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/v1/admin").allowedOrigins("http://localhost:3000"); } }; }
- You can create a filter where you can add appropriate headers in case of options requests
public class CorsFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "authorization, content-type, xsrf-token"); response.addHeader("Access-Control-Expose-Headers", "xsrf-token"); if ("OPTIONS".equals(request.getMethod())) { response.setStatus(HttpServletResponse.SC_OK); } else { filterChain.doFilter(request, response); } } }
Add filter:
.addFilterBefore(new CorsFilter(), ChannelProcessingFilter.class)
- You can add this configuration:
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .cors() .and() ... } @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList("*")); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); configuration.setAllowedHeaders(Arrays.asList("authorization", "content-type", "x-auth-token")); configuration.setExposedHeaders(Arrays.asList("x-auth-token")); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; } }
- You can enable cross-origin on some methods with
@CrossOrigin(origins = "http://localhost:3000")