FRNT001 Cross Domain, Cross Origin Request
Tags: axios, springboot
Published: 2021-01-22 01:45:51

# 本篇主要讲跨域问题

目前我正在使用spring boot跑后端，node.js跑react前端。后端运行的地址是`http://localhost:8080`，而前端运行的地址是`http://localhost:3000`。

现在前端想给后端发送一个axios请求，带有参数（比如用户名和密码），后端会返回一些数据。

# Existing code

## Backends

To make things simple, I just wrote a controller, using `@ResponseBody` and `@RequestParam` to recieve parameters in requests.

```java
@Controller
public class IndexController {

	@RequestMapping("/")
	@ResponseBody
	public String hello() {
		return "Hello Spring Boot";
	}
	
	@RequestMapping(value="/testusername", method=RequestMethod.POST)
	@ResponseBody
    public String testdata(@RequestParam("username") String username, @RequestParam("password") String password){
        return TestDataHelper.getTestJsonUserInfo(username);
    }

}
```

The method `TestDataHelper.getTestJsonUserInfo(username);` would return a simple json String as follow:

```javascript
{
    name: "Alice",
    email: "alice@hue.au",
    pasl: 2323
}
```

## Frontends

In the frontend, axios is used to send requests. In this example, **qs** is required to make sure backends side can recieve the params. 

**qs** is installed along with **axios**, simply import it by `import qs from 'qs'`.

```javascript
function doLogin() {
  var username = document.getElementById('lgusername').value;
  var password = document.getElementById('lgpassword').value;
  const axios = require('axios');
  axios({
    method: 'post',
    url: 'http://localhost:8080/testusername',
    data: qs.stringify({username, password})  // use qs to stringify data
  }).then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });
}
```

# Problem

Starting both projects, and clicking the button which will run `doLogin()` function, the backend cannot recieve the request:

> Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

This is because the browser is blocking it and it usually only allows a request in the same origin for security reasons. Even only port numbers are different, they still be treated as different origins.

# Solution

Here, a simple solution on the backend side (spring boot) will be discussed, which implements a filter to allow cross domain requests.

Another solution applying Cross-Origin Resource Sharing (CORS) which I believe is better but more difficult can be found here: https://web.dev/cross-origin-resource-sharing/

For the filter solution, we can simply create a filter package and add a filter class with a `@Component` annotation:

```java
import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Component;

@Component
public class AccessFilter implements Filter {

	@Override
	public void init(FilterConfig arg0) throws ServletException {

	}

	@Override
	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {

		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;

		// Response to preflight request doesn't pass access control check: 
		// No 'Access-Control-Allow-Origin' header is present on the requested resource.
		
		response.setHeader("Access-Control-Allow-Origin", "http://localhost:3000"); // the url that cross domain requests are from
        response.setHeader("Access-Control-Allow-Credentials","true");
        response.setHeader("Access-Control-Allow-Headers", "Content-Type,Content-Length, Authorization, Accept,X-Requested-With,X-App-Id, X-Token");
		response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
		response.setHeader("Access-Control-Max-Age", "3600");

		chain.doFilter(req, res);

	}
	
	@Override
	public void destroy() {

	}

}
```