跨域问题总结

跨域问题产生的根本原因是浏览器的同源策略。同一个,即同一协议/主机/端口元组的资源加载不会被限制。

根据同源定义,不同端口或不同子域名的资源加载均会被同源策略拦截。其中,子域名页面脚本中可通过设置当前域名来规避:

1
document.domain = "company.com";

如果确实是跨源了,则需要通过跨源资源共享(CORS):

预检请求

预检请求是指在发起真正数据请求前,发起一个OPTIONS请求确认是否允许跨域,以避免跨域请求对服务器的用户数据产生未预期的影响。

简单请求,复杂请求

可以简单的认为,没有预检请求的称为简单请求,反之则是复杂请求。

简单请求跨域访问

使用 Origin 和 Access-Control-Allow-Origin就可以。服务端响应Header:

1
2
Access-Control-Allow-Origin: *
Origin:

复杂请求跨域访问

复杂请求的问题,主要是针对预检请求的处理:

  1. 返回204状态码
  2. 需要跨源Header : Access-Control-Allow-Origin
    以下是一次POST请求示例:

    以上示例中,重点为预检请求的服务端响应:
    1
    2
    3
    4
    5
    Access-Control-Allow-Origin: https://foo.example
    Access-Control-Allow-Methods: POST, GET, OPTIONS
    Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
    # 单位为秒,表示时间段内无需对同一请求再次发起预检
    Access-Control-Max-Age: 86400

1. web新增Filter处理跨域

springboot项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
final CorsConfiguration corsConfiguration = new CorsConfiguration();
/* 是否允许请求带有验证信息 */
corsConfiguration.setAllowCredentials(true);
/* 允许访问的客户端域名 */
corsConfiguration.addAllowedOrigin("*");
/* 允许服务端访问的客户端请求头 */
corsConfiguration.addAllowedHeader("*");
/* 允许访问的方法名,GET POST等 */
corsConfiguration.addAllowedMethod("*");
urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
return new CorsFilter(urlBasedCorsConfigurationSource);
}

spring项目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
CORSFilter implements Filter
.....
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;

String origin = request.getHeader("Origin");
response.setHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Access-Control-Allow-Methods", "GET,OPTIONS,POST,HEAD,PUT,DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Accept,Origin,X-Requested-With,Content-Type,X-Auth-Token,content-type,Authorization");
response.setHeader("Access-Control-Allow-Credentials", "true");

// 防止点击劫持, 只允许自己域名的frame嵌套
response.addHeader("x-frame-options","SAMEORIGIN");

if (request.getMethod().equalsIgnoreCase("OPTIONS")) {
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
return;
}

chain.doFilter(servletRequest, servletResponse);

}

2. nginx配置处理跨域

1
2
3
4
5
6
7
8
9
10
11
location /
{

add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,X-Auth-Token,Authorization';
add_header Access-Control-Max-Age 86400;
if ($request_method = 'OPTIONS') {
return 204;
}
}

预检请求携带证书问题

CORS 预检请求不能包含凭据。预检请求的响应必须指定 Access-Control-Allow-Credentials: true 来表明可以携带凭据进行实际的请求。

附带身份凭证cookie的跨域请求

  1. 首先允许Access-Control-Allow-Headers中必须要有该header名称,特别是自定义的。
  2. 服务端的Access-Control-Allow-Origin不能设置为*,而应将其设置为特定的域。比如在Filter中:
    1
    2
    String origin = request.getHeader("Origin");
    response.setHeader("Access-Control-Allow-Origin", origin);
  3. 服务端Access-Control-Allow-Headers不能为*
  4. 服务端Access-Control-Allow-Methods不能为*

参考 MSDN CORS