W3C推荐的实现安全跨域请求的实现机制,CORS核心:让服务器决定是否允许跨域访问

使用场景

1、简单请求

1)只使用GET、HEAD或者POST请求方法,如果是POST,则数据类型(Content-Type)只能是application/x-www-form-urlencodeed、multipart/form-data、text/plain中的一种。
2)没有使用自定义的请求头(如x-token)

2、预请求

1)请求以GET、HEAD、POST之外的方法发起。或者,使用POST,但数据类型为application/x-www-form-urlencoded, multipart/form-data 或者 text/plain 以外的数据类型。
2)只使用服务端自定义的请求头(如xtoken)。

3、带凭证的请求

一般来说,对于跨站请求,浏览器是不会发送凭证(HTTP Cookies和验证信息)的。如果要发送带凭证的信息,只需要给XMLHttpRequest设置一个特殊的属性withCredentials = true,通过这种方式,浏览器就允许发送凭证信息。
带凭证的请求可能是简单请求,也可以是会有预请求。是否允许跨域,会先判断简单请求和预请求的规则,然后还会带上带凭证的请求自己的规则。
在带凭证的请求中,后端的响应必须包含HeaderAccess-Control-Allow-Credentials=true,同时Header Access-Control-Allow-Origin,不能再使用*号这种匹配符。

服务端响应的响应头

Access-Control-Allow-Origin: | * 允许的域名
Access-Control-Expose-Headers: 允许的白名单Header,多个用逗号隔开
Access-Control-Max-Age: 预请求缓存时间,单位秒
Access-Control-Allow-Credentials: true | false 是否允许带凭证的请求
Access-Control-Allow-Methods: 允许的请求类型,多个用逗号隔开
Access-Control-Allow-Headers: 在实际请求中,允许的自定义header,多个用逗号隔开


浏览器发出跨域请求头详解

Origin: 告诉服务器,请求来自哪里,仅仅是服务器名,不包含路径。
Access-Control-Request-Method: 预请求时,告诉服务器实际的请求方式
Access-Control-Request-Headers: 预请求时,告诉服务器,实际请求所携带的自定义Header

注意cors配置的Access-Control-Allow-Origin允许域名只是禁止配置之外域名获取服务端返回的数据,但是不阻止跨域请求的发送。

 

服务端Java实现示例:

/** 
 * @ClassName: CORSFilter
 * @Description:  
 * @author:zyq
 * @date 2018年03月08日
 *
 */
public class CORSFilter implements Filter {

    private final Set<String> ALLOWED_DOMAINS = new HashSet<String>() {
        {
            add(".ncf.wdtest.cc");
            add(".weidai.com.cn");
            add(".wd5.com.cn");
            add(".wdai.com");
            add(".wdgood.cn");
            add(".weidai.work");
            add(".wdtest.cc");
            add(".wddev.cc");
        }
    };

    @Override
    public void destroy() {
        // TODO Auto-generated method stub

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        if (allowCors(request)) {
            HttpServletRequest servletRequest = (HttpServletRequest) request;
            HttpServletResponse servletResponse = (HttpServletResponse) response;
            servletResponse.setHeader("Access-Control-Allow-Origin", servletRequest.getHeader("Origin"));//必填
            servletResponse.setHeader("Access-Control-Allow-Methods", servletRequest.getHeader("Access-Control-Request-Method"));//可选
            servletResponse.setHeader("Access-Control-Allow-Headers", servletRequest.getHeader("Access-Control-Request-Headers"));//可选
            servletResponse.setHeader("Access-Control-Allow-Credentials", "true");//可选
            servletResponse.setHeader("Access-Control-Max-Age", getCorsMaxAge(request));//可选,指定本次预检请求的有效期,单位为秒,我先写个1天
        }
        chain.doFilter(request, response);
    }

    //可继承改写,根据自己情况设置哪些源地址、目标url允许跨域
    protected boolean allowCors(ServletRequest request) {
        String originDomain = getHostName(((HttpServletRequest) request).getHeader("Origin"));
        if (originDomain != null) {
            for (String domain : ALLOWED_DOMAINS) {
                if (originDomain.endsWith(domain)) {
                    return true;
                }
            }
        }
        return false;
    }

    private String getHostName(String url) {
        if (url == null || url.length() == 0) {
            return null;
        }
        try {
            return new URL(url).getHost();
        } catch (MalformedURLException e) {
            return null;
        }
    }

    //可继承改写,自己设置有效期
    protected String getCorsMaxAge(ServletRequest request) {
        return "86400";
    }


    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        String allowedDomains = filterConfig.getInitParameter("allowedDomains");
        if (allowedDomains != null) {
            ALLOWED_DOMAINS.addAll(Arrays.asList(allowedDomains.split(",")));
        }
    }


}