使用Nginx解决前端跨域问题.md 8.7 KB

前端同事http://localhost:8080/index.html 页面中 ajax 请求 http://10.18.226.160:8091/system/sysPageType/find ,提示跨域错误

跨域错误

1. 使用CORS策略

方法1:添加一个拦截器,给所有的请求响应添加跨域请求头

@Component
public class SimpleCORSFilter implements Filter {

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
        chain.doFilter(req, res);
    }

    public void init(FilterConfig filterConfig) {}

    public void destroy() {}
}

方法2:在SpringBoot项目中使用@CrossOrigin注解

@RestController
@RequestMapping("/account")
public class AccountController {

    @CrossOrigin
    @GetMapping("/{id}")
    public Account retrieve(@PathVariable Long id) {
        // ...
    }

    @DeleteMapping("/{id}")
    public void remove(@PathVariable Long id) {
        // ...
    }
}

或者在 Controller 类上添加

@CrossOrigin(origins = "http://domain2.com", maxAge = 3600)

其中@CrossOrigin中的2个参数:

origins : 允许可访问的域列表

maxAge:准备响应前的缓存持续的最大时间(以秒为单位)。

2. 服务器代理

location /sys-web {
	add_header 'Access-Control-Allow-Origin' $http_origin;
	add_header 'Access-Control-Allow-Credentials' 'true';
	add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
	add_header 'Access-Control-Allow-Headers' 'DNT,web-token,app-token,Authorization,Accept,Origin,Keep-Alive,User-Agent,X-Mx-ReqToken,X-Data-Type,X-Auth-Token,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
	add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
	if ($request_method = 'OPTIONS') {
		add_header 'Access-Control-Max-Age' 1728000;
		add_header 'Content-Type' 'text/plain; charset=utf-8';
		add_header 'Content-Length' 0;
		return 204;
	}
	root   html;
	index  index.html index.htm;
	proxy_pass http://127.0.0.1:8080;
	proxy_set_header Host $host;
	proxy_set_header X-Real-IP $remote_addr;
	proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
	proxy_set_header X-Forwarded-Proto $scheme;
	proxy_connect_timeout 5;
}

关键的就是下面这几行

	add_header 'Access-Control-Allow-Origin' $http_origin;
	add_header 'Access-Control-Allow-Credentials' 'true';
	add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
	add_header 'Access-Control-Allow-Headers' 'DNT,web-token,app-token,Authorization,Accept,Origin,Keep-Alive,User-Agent,X-Mx-ReqToken,X-Data-Type,X-Auth-Token,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
	add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
	if ($request_method = 'OPTIONS') {
		add_header 'Access-Control-Max-Age' 1728000;
		add_header 'Content-Type' 'text/plain; charset=utf-8';
		add_header 'Content-Length' 0;
		return 204;
	}

说明:

1、Access-Control-Allow-Origin,这里使用变量 $http_origin取得当前来源域,大家说用“*”代表允许所有,我实际使用并不成功,原因未知;

2、Access-Control-Allow-Credentials,为 true 的时候指请求时可带上Cookie,自己按情况配置吧;

3、Access-Control-Allow-Methods,OPTIONS一定要有的,另外一般也就GET和POST,如果你有其它的也可加进去;

4、Access-Control-Allow-Headers,这个要注意,里面一定要包含自定义的http头字段(就是说前端请求接口时,如果在http头里加了自定义的字段,这里配置一定要写上相应的字段),从上面可看到我写的比较长,我在网上搜索一些常用的写进去了,里面有“web-token”和“app-token”,这个是我项目里前端请求时设置的,所以我在这里要写上;

5、Access-Control-Expose-Headers,可不设置,看网上大致意思是默认只能获返回头的6个基本字段,要获取其它额外的,先在这设置才能获取它;

6、语句“ if ($request_method = 'OPTIONS') { ”,因为浏览器判断是否允许跨域时会先往后端发一个 options 请求,然后根据返回的结果判断是否允许跨域请求,所以这里单独判断这个请求,然后直接返回;

nginx add_header 指令说明:

add_header 指令用来设置response的 header的。

官方文档 中对 add_header 的解释

Syntax: add_header name value [always]; Default: — | Context: http, server, location, if in location

Adds the specified field to a response header provided that the response code equals 200, 201 (1.3.10), 204, 206, 301, 302, 303, 304, 307 (1.1.16, 1.0.13), or 308 (1.13.0). Parameter value can contain variables.

There could be several add_header directives. These directives are inherited from the previous level if and only if there are no add_header directives defined on the current level.

如果当前层级没有 add_header 指令,那么会继承上层的配置。

If the always parameter is specified (1.7.5), the header field will be added regardless of the response code.

如果add_header指定了 always,无论响应码是多少header都会被添加。

2种情况:

  1. rewrite 不管请求 /path1 还是 /path2,最终 header 都只有 B
location /path1 {
    add_header A a;
    rewrite / /path2;
}

location /path2 {
    add_header B b;
    return 200 "OK";
}
  1. 下面的配置,如果 OPTIONS 类型的请求,那么 responses 的header 只会有 Access-Control-Max-AgeContent-TypeContent-Length,即 if 语句内并不会继承外部的 add_header 设置。
add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type,Authorization,Accept,Origin,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
if ($request_method = 'OPTIONS') {
    add_header 'Access-Control-Max-Age' 1728000;
    add_header 'Content-Type' 'text/plain; charset=utf-8';
    add_header 'Content-Length' 0;
    return 204;
}

这种解决跨域的配置对 请求头 ContentType: application/json 的 post 请求无效。原因是

在使用Ajax跨域请求时,如果设置Header的ContentType为application/json,会分两次发送请求。第一次先发送Method为OPTIONS的请求到服务器,这个请求会询问服务器支持哪些请求方法(GET,POST等),支持哪些请求头等等服务器的支持情况。等到这个请求返回后,如果原来我们准备发送的请求符合服务器的规则,那么才会继续发送第二个请求,否则会在Console中报错。更多信息参考:https://www.cnblogs.com/shamo89/p/7837451.html

综上2个因素,导致请求头 ContentType: application/json 的 post 请求跨域失败的原因是,OPTIONS 请求没有跨域配置。

简单粗暴的办法:直接将外部的跨域配置复制一份到 OPTIONS 请求中。

add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Content-Type,Authorization,Accept,Origin,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
if ($request_method = 'OPTIONS') {
    add_header 'Access-Control-Allow-Origin' $http_origin;
    add_header 'Access-Control-Allow-Credentials' 'true';
    add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
    add_header 'Access-Control-Allow-Headers' 'Content-Type,Authorization,Accept,Origin,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control';
    add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
    add_header 'Access-Control-Max-Age' 1728000;
    add_header 'Content-Type' 'text/plain; charset=utf-8';
    add_header 'Content-Length' 0;
    return 204;
}

Proxy_pass 说明:

location /sys-web { ​ ... ​ proxy_pass http://127.0.0.1:8080; # location 和 proxy_pass有无斜杠 "/" 有很大关系,一定注意!!! ​ ... }