再次研究跨域问题

216 阅读4分钟

1 诡异的OPTION请求

1.1 现象描述

在每次跨域失败的时候,在请求前总会伴随一个对应method是OPTION的请求 但是设置跨域成功后,这个请求就消失不见了

OPTION请求是浏览器针对复杂类型请求在请求服务器之前,先发送的预检请求 询问服务器,当前请求是否接收

当满足下列条件时,浏览器会判断为简单请求

  1. 只能是Get、Head、Post方法
  2. 除了浏览器自己在Http头上加的信息(如Connection、User-Agent),开发者只能加这几个:Accept、Accept-Language、Content-Type、。。。。
  3. Content-Type只能取这几个值:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

不满足上述任一条都会判定为复杂请求

1.2 OPTION请求在跨域中究竟做了什么

OPTION请求头中有两个需要关注的字段:

  1. Access-Control-Request-Method:告知服务器实际请求所使用的HTTP方法
  2. Access-Control-Request-Headers:告知服务器实际请求所携带的请求头有哪些

响应头:

  1. Access-Control-Allow-Credentials: 实际请求得到的响应能否显示在该页面,如果允许跨域即为true
  2. Access-Control-Allow-Headers:服务器允许跨域的请求头
  3. Access-Control-Allow-Method:服务器允许跨域的请求所使用的HTTP方法
  4. Access-Control-Allow-Origin:服务器允许跨域的域名有哪些,通常为*,所有域名都允许
  5. Access-Control-Max-Age:允许浏览器在指定时间内,无需再发送预检请求,直接用本次结果即可

浏览器根据OPTION请求的响应头判断当前请求是否允许跨域

1.3 OPTION请求为什么诡异

答案再这

  • Access-Control-Max-Age:允许浏览器在指定时间内,无需再发送预检请求,直接用本次结果即可

如果浏览器打开后,第一复杂请求会发送预检请求,请求通过后,后面相同url,相同请求头的请求,都不会在发送预检请求

2 一个允许跨域后的报错

Access to XMLHttpRequest at 'http://aaa/test' from origin 'http://localhost:8033' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values '*, *', but only one is allowed.

2.1 预检请求都正常通过,为什么还会不允许跨域

这个报错明显告诉我们,Access-Control-Allow-Origin有多个值*, *

浏览器是很怪的

其实每次跨域请求,它都去请求了服务器,并且请求成功了,但是呢,因为同源策略,如果服务器不允许跨域访问,那么相应的返回数据就不给展示,也就是说响应头已经有了

HTTP/1.1 200 OK
Server: openresty/1.9.7.3
Date: Thu, 05 Aug 2021 14:49:01 GMT
Content-Type: application/json
Transfer-Encoding: chunked
Connection: close
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH
Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type,Token,Accept, Connection, User-Agent, Cookie,app-equip,x-auth-token
Access-Control-Max-Age: 3628800
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With
Access-Control-Allow-Methods: POST,GET
Access-Control-Allow-Origin: *
Access-Control-Max-Age: 0

查看响应头,确实每个跨域设置都被设置了两次

然后浏览器在解析响应头,思考服务器是不是允许跨域,返回的数据我要不要展示的时候,它不会了 怎么跨域设置设置了两次,我该怎么办,以哪个为准,它想不出来,就报错了

2.2 如何解决

2.2.1 nginx配置了跨域,服务器过滤器设置了跨域

检查nginx配置

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,Authorization';

    if ($request_method = 'OPTIONS') {
        return 204;
    }
}

检查服务器过滤器,网关中是否设置了跨域

2.2.2 服务器设置了两次跨域

检查web.xml,有没有跨域相关过滤器

<filter>
    <filter-name>crossFilter</filter-name>
    <filter-class>com.aaa.CrossFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>crossFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
public class CrossFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH");
        response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type,Token,Accept, Connection, User-Agent, Cookie,x-auth-token,authorization");
        response.setHeader("Access-Control-Max-Age", "3628800");

        if ("OPTIONS".equals(request.getMethod())) {
            response.setStatus(HttpStatus.OK.value());
        }else
        {
            filterChain.doFilter(servletRequest, response);
        }
    }

    @Override
    public void destroy() {

    }
}

检查java中有没有过滤器相关的bean

@Component
@Provider
public class CrossOriginFilter implements ContainerResponseFilter {
    private static final String allowOrigin = "*";
    private static final String allowMethods = "POST,GET";
    private static final String maxAge = "0";
    private static final String allowHeaders = "Content-Type, Authorization";

    @Override
    public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
        responseContext.getHeaders().add("Access-Control-Allow-Origin", allowOrigin);
        responseContext.getHeaders().add("Access-Control-Allow-Methods", allowMethods);
        responseContext.getHeaders().add("Access-Control-Max-Age", maxAge);
        responseContext.getHeaders().add("Access-Control-Allow-Credentials","true");
        responseContext.getHeaders().add("Access-Control-Allow-Headers", allowHeaders);
    }
}

检查网关配置有没有配置跨域,以zuul为例

zuul:
#需要忽略的头部信息,不在传播到其他服务
sensitive-headers: Access-Control-Allow-Origin
ignored-headers: Access-Control-Allow-Origin,H-APP-Id,Token,APPToken
————————————————
版权声明:本文为CSDN博主「沙滩de流沙」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_41231928/article/details/103313449