CORS 几种解决方案

来源:互联网 发布:淘宝怎么看自己评价的 编辑:程序博客网 时间:2024/05/17 07:44

CORS背后的基本思想是使用自定义的HTTP头部允许浏览器和服务器相互了解对方,从而决定请求或响应成功与否.

Access-Control-Allow-Origin:指定授权访问的域
Access-Control-Allow-Methods:授权请求的方法(GET, POST, PUT, DELETE,OPTIONS等)

一:简单的自定义CORSFilter / Interceptor

适合设置单一的(或全部)授权访问域,所有配置都是固定的,特简单。也没根据请求的类型做不同的处理

在web.xml 中添加filter

<filter>    <filter-name>cros</filter-name>    <filter-class>cn.ifengkou.test.filter.CORSFilter</filter-class></filter><filter-mapping>    <filter-name>cros</filter-name>    <url-pattern>/*</url-pattern></filter-mapping>

新增CORSFilter 类

@Componentpublic class CORSFilter extends OncePerRequestFilter {    @Override    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {        response.addHeader("Access-Control-Allow-Origin", "*");        response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");        response.addHeader("Access-Control-Allow-Headers", "Content-Type");        response.addHeader("Access-Control-Max-Age", "1800");//30 min        filterChain.doFilter(request, response);    }}

Access-Control-Allow-Origin只能配置 或者一个域名*
比如配置了192.168.56.130,那么只有192.168.56.130 能拿到数据,否则全部报403异常

response.addHeader("Access-Control-Allow-Origin", "http://192.168.56.130");

二:Nginx 配置支持Ajax跨域

这里是一个nginx启用COSR的参考配置:来源

## Wide-open CORS config for nginx#location / {     if ($request_method = 'OPTIONS') {        add_header 'Access-Control-Allow-Origin' '*';        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';        #        # Custom headers and headers various browsers *should* be OK with but aren't        #        add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';        #        # Tell client that this pre-flight info is valid for 20 days        #        add_header 'Access-Control-Max-Age' 1728000;        add_header 'Content-Type' 'text/plain charset=UTF-8';        add_header 'Content-Length' 0;        return 204;     }     if ($request_method = 'POST') {        add_header 'Access-Control-Allow-Origin' '*';        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';        add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';     }     if ($request_method = 'GET') {        add_header 'Access-Control-Allow-Origin' '*';        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';        add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';     }}

三:支持多域名配置的CORS Filter

因为知道已经有可以用的库可以解决,所以就没重复造轮子了。其实因为懒,看看别人的源码算了。。。

在mvnrepository搜索cors-filter,目前也就两个可以用

  • org.ebaysf.web 的 cors-filter,项目地址:https://github.com/ebay/cors-filter
  • com.thetransactioncompany的 cors-filter,项目地址:http://software.dzhuvinov.com/cors-filter.html

这两个也都大同小异,因为ebay开源在github上,也有详细的README,那么就以ebay的cors-filter为例

配置

添加依赖包到项目:

<dependency>    <groupId>org.ebaysf.web</groupId>    <artifactId>cors-filter</artifactId>    <version>1.0.1</version></dependency>

添加配置(具体配置项,还是见项目的README.md吧)

  <filter>    <filter-name>CORS Filter</filter-name>    <filter-class>org.ebaysf.web.cors.CORSFilter</filter-class>    <init-param>      <param-name>cors.allowed.origins</param-name>      <param-value>http://192.168.56.129,http://192.168.56.130</param-value>    </init-param>    <init-param>      <param-name>cors.allowed.methods</param-name>      <param-value>GET,POST,HEAD,OPTIONS,PUT</param-value>    </init-param>    <init-param>      <param-name>cors.allowed.headers</param-name>      <param-value>Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>    </init-param>  </filter>  <filter-mapping>    <filter-name>CORS Filter</filter-name>    <url-pattern>/*</url-pattern>  </filter-mapping>

源码分析

源码地址:github。但通过IDEA Decompiled 出来的更清晰.....,以下是反编译的

ebaysf的cors-filter 只有一个类CORSFilter。也就是一个拦截器,implements Filter

public final class CORSFilter implements Filter {

通过是实现Filter 的init 方法从配置文件中读取参数:

public void init(FilterConfig filterConfig) throws ServletException {    this.parseAndStore("*", "GET,POST,HEAD,OPTIONS", "Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers", "", "true", "1800", "false", "true");    this.filterConfig = filterConfig;    this.loggingEnabled = false;    if(filterConfig != null) {        String configAllowedOrigins = filterConfig.getInitParameter("cors.allowed.origins");        String configAllowedHttpMethods = filterConfig.getInitParameter("cors.allowed.methods");        String configAllowedHttpHeaders = filterConfig.getInitParameter("cors.allowed.headers");        String configExposedHeaders = filterConfig.getInitParameter("cors.exposed.headers");        String configSupportsCredentials = filterConfig.getInitParameter("cors.support.credentials");        String configPreflightMaxAge = filterConfig.getInitParameter("cors.preflight.maxage");        String configLoggingEnabled = filterConfig.getInitParameter("cors.logging.enabled");        String configDecorateRequest = filterConfig.getInitParameter("cors.request.decorate");        this.parseAndStore(configAllowedOrigins, configAllowedHttpMethods, configAllowedHttpHeaders, configExposedHeaders, configSupportsCredentials, configPreflightMaxAge, configLoggingEnabled, configDecorateRequest);    }}

parseAndStore 方法,解析参数。以 解析cors.allowed.orgins为例;其他参数同理

    Set e;    if(allowedOrigins != null) {        if(allowedOrigins.trim().equals("*")) {            this.anyOriginAllowed = true;        } else {            this.anyOriginAllowed = false;            e = this.parseStringToSet(allowedOrigins);            this.allowedOrigins.clear();            this.allowedOrigins.addAll(e);        }    }//parseStringToSet//对多域名用点分割,加到HashSet中,再赋给allowedOrigins(Collection<String> allowedOrigins = new HashSet();)private Set<String> parseStringToSet(String data) {    String[] splits;    if(data != null && data.length() > 0) {        splits = data.split(",");    } else {        splits = new String[0];    }    HashSet set = new HashSet();    if(splits.length > 0) {        String[] arr$ = splits;        int len$ = splits.length;        for(int i$ = 0; i$ < len$; ++i$) {            String split = arr$[i$];            set.add(split.trim());        }    }    return set;}

如何实现 doFilter

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {    if(servletRequest instanceof HttpServletRequest && servletResponse instanceof HttpServletResponse) {        HttpServletRequest request1 = (HttpServletRequest)servletRequest;        HttpServletResponse response = (HttpServletResponse)servletResponse;        //识别request 属于哪种类别        CORSFilter.CORSRequestType requestType = this.checkRequestType(request1);        if(this.decorateRequest) {            decorateCORSProperties(request1, requestType);        }        switch(CORSFilter.SyntheticClass_1.$SwitchMap$org$ebaysf$web$cors$CORSFilter$CORSRequestType[requestType.ordinal()]) {        case 1:            this.handleSimpleCORS(request1, response, filterChain);            break;        case 2:            this.handleSimpleCORS(request1, response, filterChain);            break;        case 3:            this.handlePreflightCORS(request1, response, filterChain);            break;        case 4:            this.handleNonCORS(request1, response, filterChain);            break;        default:            this.handleInvalidCORS(request1, response, filterChain);        }    } else {        String request = "CORS doesn\'t support non-HTTP request or response.";        throw new ServletException(request);    }}

判断request类别,根据类别进行差异化处理。handleSimpleCORS 处理过程,判断是否设置允许所有origin参数,判断是否符合httpMethods要求,判断此次request的origin(origin = request.getHeader("Origin"))是否在allowedOrigins(origin白名单)内。如果在,就设置response.addHeader("Access-Control-Allow-Origin", origin);这样也就实现了多域名支持。流程图就不画了...

public void handleSimpleCORS(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {        CORSFilter.CORSRequestType requestType = this.checkRequestType(request);        String origin;        if(requestType != CORSFilter.CORSRequestType.SIMPLE && requestType != CORSFilter.CORSRequestType.ACTUAL) {            origin = "Expects a HttpServletRequest object of type " + CORSFilter.CORSRequestType.SIMPLE + " or " + CORSFilter.CORSRequestType.ACTUAL;            throw new IllegalArgumentException(origin);        } else {            origin = request.getHeader("Origin");            String method = request.getMethod();            if(!this.isOriginAllowed(origin)) {                this.handleInvalidCORS(request, response, filterChain);            } else if(!this.allowedHttpMethods.contains(method)) {                this.handleInvalidCORS(request, response, filterChain);            } else {                if(this.anyOriginAllowed && !this.supportsCredentials) {                    response.addHeader("Access-Control-Allow-Origin", "*");                } else {                    response.addHeader("Access-Control-Allow-Origin", origin);                }                if(this.supportsCredentials) {                    response.addHeader("Access-Control-Allow-Credentials", "true");                }                if(this.exposedHeaders != null && this.exposedHeaders.size() > 0) {                    String exposedHeadersString = join(this.exposedHeaders, ",");                    response.addHeader("Access-Control-Expose-Headers", exposedHeadersString);                }                filterChain.doFilter(request, response);            }        }    }

为了避免对参数一知半解,就把作者的参数描述表贴上来,通过参数表可以了解下header里面各个参数的作用

param-namedescriptioncors.allowed.originsA list of origins that are allowed to access the resource. A '' can be specified to enable access to resource from any origin. Otherwise, a whitelist of comma separated origins can be provided. Ex: http://www.w3.org, https://www.apache.org. Defaults: (Any origin is allowed to access the resource).cors.allowed.methodsA comma separated list of HTTP methods that can be used to access the resource, using cross-origin requests. These are the methods which will also be included as part of 'Access-Control-Allow-Methods' header in a pre-flight response. Ex: GET,POST. Defaults: GET,POST,HEAD,OPTIONScors.allowed.headersA comma separated list of request headers that can be used when making an actual request. These header will also be returned as part of 'Access-Control-Allow-Headers' header in a pre-flight response. Ex: Origin,Accept. Defaults: Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headerscors.exposed.headersA comma separated list of headers other than the simple response headers that browsers are allowed to access. These are the headers which will also be included as part of 'Access-Control-Expose-Headers' header in the pre-flight response. Ex: X-CUSTOM-HEADER-PING,X-CUSTOM-HEADER-PONG. Default: Nonecors.preflight.maxageThe amount of seconds, browser is allowed to cache the result of the pre-flight request. This will be included as part of 'Access-Control-Max-Age' header in the pre-flight response. A negative value will prevent CORS Filter from adding this response header from pre-flight response. Defaults: 1800cors.support.credentialsA flag that indicates whether the resource supports user credentials. This flag is exposed as part of 'Access-Control-Allow-Credentials' header in a pre-flight response. It helps browser determine whether or not an actual request can be made using credentials. Defaults: truecors.logging.enabledA flag to control logging to container logs. Defaults: falsecors.request.decorateA flag to control if the request should be decorated or not. Defaults: true

测试:

1.服务端准备接口(我的地址是:http://192.168.10.61:8080/api)

@RequestMapping(method = RequestMethod.GET,value = "test")@ResponseBodypublic HashMap<String,Object> getArticles(){    HashMap<String,Object> map = new HashMap<>();    map.put("result","success");    return map;}

2.过滤器配置(web.xml),配置允许访问的域为:http://192.168.56.129,http://www.website2.com

<filter>    <filter-name>CORS Filter</filter-name>    <filter-class>org.ebaysf.web.cors.CORSFilter</filter-class>    <init-param>      <param-name>cors.allowed.origins</param-name>      <param-value>http://192.168.56.129,http://www.website2.com</param-value>    </init-param>  </filter>  <filter-mapping>    <filter-name>CORS Filter</filter-name>    <url-pattern>/*</url-pattern>  </filter-mapping>

3.准备测试网页index.html:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>cors test page</title>    <script src="jquery.min.js"></script>    <script>        function loadData(){            $.ajax({                url: "http://192.168.10.61:8080/api",                type:"GET",                dataType:"json",                timeout:10000,                success:function(data){                    $("#result").append(data.result+"<br />");                    console.log(data);                },                error:function(e){                    $("#result").append(e.statusText+"<br />");                }            });        }        $(function(){            $("#host").append("origin:"+window.location.origin);        });    </script></head><body><button onclick="loadData()">onclick</button><div id="host"></div><div id="result" style="height:200px;width:100%"></div></body></html>

4.将index.html发布到nginx(nginx后面也有方案)

index.html 不能直接用浏览器打开运行,虽然可以调用Ajax请求,但是域是file:///path/index.html

虚拟机增加一个网卡地址(原机器IP是192.168.56.129)

ifconfig eth0:0 192.168.56.130

建立两个测试网站

cd homemkdir /website1 #站点目录mkdir /website2 

将index.html 传输到这两个目录

配置nginx,增加两个server节点

# ----server1 ----server {    listen       192.168.56.129:80;    server_name  www.website1.com;    location / {        root   /website1;        index  index.html index.htm;    }}# ----server2 ----server {    listen       192.168.56.130:80;    server_name  www.website2.com;    location / {        root   /website2;        index  index.html index.htm;    }}

重启nginx服务

./nginx -s reload

5.修改本地hosts文件

//hosts文件路径:windows系统一般在C:\Windows\System32\drivers\etc192.168.56.129  www.website1.com192.168.56.130  www.website2.com

通过增加虚拟网卡 、nginx代理 和 修改hosts文件,我在本地就有4个网站(域)可以进行测试了,分别是:

  • http://192.168.56.129
  • http://192.168.56.130
  • http://www.website1.com
  • http://www.website2.com

6.测试

准备:

(chrome)打开4个tab,分别进入到上述四个网站,页面打印了当前origin,通过onclick调用Ajax请求,页面布局如下

预期:

  • http://192.168.56.129 SUCCESS
  • http://192.168.56.130 ERROR
  • http://www.website1.com ERROR
  • http://www.website2.com SUCCESS

结果:




符合预期!

建议使用,除了对域的过滤,还做了其他很多操作,比简单的自定义过滤器考虑得周全,例如

this.handlePreflightCORS(request1, response, filterChain);this.handleNonCORS(request1, response, filterChain);this.handleInvalidCORS(request1, response, filterChain);

总结

cors在开发WebService、RESTful API 时经常会遇到,在以前可能直接通过jsonp解决,jsonp怎样怎样就不多说了。 总之,CORS技术规范出来这么久了,如果不考虑IE6 IE7的问题,那么还是积极拥抱CORS吧

上文三种解决方案,通过搜索引擎均能找到,但估计大部分都是用的第一种最简单的无脑的Cors Filter处理,第二种方案是通过nginx配置的,并不适合所有Web应用。第三种,考虑得很周全,而且使用方便,如果不考虑造重复轮子,推荐使用。

本文所用的测试工程代码太简单了,就不放github了,直接下载吧,项目下载地址

断断续续写了好几天,转载请附带原文路径:http://www.cnblogs.com/sloong/p/cors.html