CORS詳解

W3C推薦的實現安全跨域請求的實現機制,CORS核心:讓服務器決定是否容許跨域訪問java

使用場景後端

一、簡單請求跨域

1)只使用GET、HEAD或者POST請求方法,若是是POST,則數據類型(Content-Type)只能是application/x-www-form-urlencodeed、multipart/form-data、text/plain中的一種。
2)沒有使用自定義的請求頭(如x-token)瀏覽器

二、預請求緩存

1)請求以GET、HEAD、POST以外的方法發起。或者,使用POST,但數據類型爲application/x-www-form-urlencoded, multipart/form-data 或者 text/plain 之外的數據類型。
2)只使用服務端自定義的請求頭(如xtoken)。安全

三、帶憑證的請求服務器

通常來講,對於跨站請求,瀏覽器是不會發送憑證(HTTP Cookies和驗證信息)的。若是要發送帶憑證的信息,只須要給XMLHttpRequest設置一個特殊的屬性withCredentials = true,經過這種方式,瀏覽器就容許發送憑證信息。
帶憑證的請求多是簡單請求,也能夠是會有預請求。是否容許跨域,會先判斷簡單請求和預請求的規則,而後還會帶上帶憑證的請求本身的規則。
在帶憑證的請求中,後端的響應必須包含HeaderAccess-Control-Allow-Credentials=true,同時Header Access-Control-Allow-Origin,不能再使用*號這種匹配符。app

服務端響應的響應頭cors

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,多個用逗號隔開dom


瀏覽器發出跨域請求頭詳解

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(",")));
        }
    }


}
相關文章
相關標籤/搜索