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