为何跨域
由于浏览器需要不同站点之间通信,简单说如从http://www.baidu.com/ 页面去请求 http://www.google.com 的资源。此时就会存在跨域问题,好比只要锄头挥得好,哪有墙脚挖不倒,只要知识学得好,哪有工作不好找,只要工作找得好,哪有妹纸撩不倒,扯远了,大家懂的。。。
跨域是个啥
跨域是指浏览器从一个域名的网页去请求另一个域名的资源,此时浏览器同源策略会限制请求。而同源策略限制即只要协议,域名,端口有任何一个的不同,就被当作是跨域。即简单理解a.com 域名下的js无法操作b.com或是c.a.com域名下的对象。那为什么有这个限制,当时设计浏览器那帮老头就想到女朋友不能共享,一切的一切出于安全考虑,否则就要刀光剑影乱套。
特别注意两点:第一,如果是协议和端口造成的跨域问题“前端”是无能为力的,第二:在跨域问题上,域仅仅是通过“URL的首部”来识别而不会去尝试判断相同的ip地址对应着两个域或两个域是否在同一个ip上。
如何解决跨域
1、 JSONP
/**
*JSONP(json+padding)(第三方框架如:jQuery)
*原理:采用全局方法或全局变量,会被自动加到window对象上(客户端定义回调函数,服务端响应调函数即fn(data))
*利用script标签中src天生跨域特性,除此
*还有a-href、img-src、iframe-src、*location.href等
*局限:只支持"GET请求",且不能解决不同域的页面间如何进行javascript调用问
*题,但兼容性好,且不受"同源策略的限制"。
*@param url 交互页面url即跨域地址
*@param type 跨域方式 即jsonp(方法)或var(变量) 默认jsonp
*@param success 跨域成功回调 处理传来的数据
*@param error 跨域失败回调
*/
function jsonp(url,success,error,type){
//回调函数名最好唯一即,以防止响应方调用时,调用到同名
var prefix = "jsonp"+new Date().getTime();//前缀
var defaultType = "jsonp",
defaultCallback = prefix + "Callback";
var jsonpType = /jsonp|var/.test(type)? type : defaultType;
//创建默认回调函数
if(defaultType == jsonpType){
/**在响应方调用,实参存在arguments数组中,默认值undefined
* 若指定形参接受时,就只能arguments[i]来获取,不用担心报越界。
*
* data数据类型任意,可以是json,也可以是方法,还可以是基本类型
*/
window[defaultCallback] = function(data){
if(success) success(data); //arguments[0]
};
}
//创建一个script的DOM对象
var script = document.createElement("script");
script.type = "text/javascript";
//设置其同步属性
script.async = true;
//设置其地址
/**js中正则表达式另一种表示形式:/pattern/attributes 直接量,但是不能含 有变量
*注意:(1)不能加""或'',否则就变成字符串 (2)等价于 new RegExp(pattern","attributes")
*pattern:即regexp
*attributes:其取值分别为i(大小写不敏感)、g(全局匹配,而非匹配第一个就停止)、m(多行匹配)
*/
/*script.src = url.replace(/#.*$/,"") +
(/\?/.test(url) ? "&" : "?")+
type + "=" + defaultCallback;
*/
script.src = url;//url绑定参数问题:url长度有限,其次需要解析出需要的参数(一般是?后面)
//设置jsonpType属性
/*
if(!script.setAttribute){
script.setAttribute(jsonpType,defaultCallback);//通过设置属性方式,ie8以前浏览器不支持
}
else*/
script.jsonpType = defaultCallback; //自定义属性
//设置监听,当浏览器把响应方加载完毕就及时“销毁”(安全考虑),但这样响应方就不需要再添加onload
script.onload = script.onreadystatechange = function(){
//if(! this.readState === 'loaded' || this.readState === 'complete'){
if(! this.readState || /loaded|complete/.test(this.readState)){
this.onload = this.onreadystatechange = null;
if(jsonpType == "var"){
if(success) success(window[defaultCallback]); //响应方创建全局变量window[defaultCallback]
}
//移除该script的DOM对象,及其属性都会被移除
if(this.parentNode) this.parentNode.removeChild(this);
//删除全局函数或变量
window[defaultCallback] = null;
}
};
addEvent(script,"error",error);
//插入script到head中
document.head.appendChild(script);
}
/**
*JSONP 漏洞
*由于浏览器解析HTML是按顺序解析即从上到下解析,
* 因此若要js操作HTML标签,则必须采用“浏览器加载完毕,才进行解析”。
* 即onload或onreadystatechange中readState判断。
*
*当使用JSONP时需要格外小心。
*js提供了两种访问成员的方式:
*(1) 对象.成员
*(2) 对象["成员"]
*js提供了两种声明变量的方式:
*(1)var 变量 = 初始值;//默认的初始值undefined
*(2)变量 = 初始值; //无默认值,因此不指定值而直接作为条件时,会报错
*/
(function(w){
var getCallback = function(){//获取回调函数名
var js = (window.document || document).head.getElementsByTagName("script");
var callback = '';
for(var i = 0 ; i < js.length ; i++){
/*
if(js[i].hasAttribute("jsonp")) return js[i].getAttribute("jsonp");
else if(js[i].hasAttribute("var"))return js[i].getAttribute("var");
else ///\?.*(jsonp|var)/.test(src) ? src.split(/\?.*(jsonp|var)/) : "";
//alert ( js[i].jsonpType );
*/
//js[i].jsonpType 不存在时,不会报错(由于此时对象成员默认undefined)且callback = undefined
return (callback = js[i].jsonpType) ? callback : callback;
}
}
//执行回调
var response = function(callback,data){
var callback = getCallback();
if(typeof w[callback] === "function") w[callback](data); //type:jsonp 方法
else w[callback] = data; //type:var 变量
}
//response({name:"jsonp"}) 服务端
})(window);
jsonp("./proxy.js?aa",function(data){
//for(v in data)
alert(data)
},function(){
alert("失败")
});
function test(){
var callback = "jsonpCallback";
window[callback] = function(){}
alert(window[callback])
}
2、 DDI
/**
*DDI(document.domain+iframe) 父子域即主域相同,而二级域名不同。安全性低
*原理:只需要请求方与响应方的document.domain一致
*@param domain 域名
*@param url 交互页面url
*@method fn 回调函数 处理传来的数据
*/
function ddi(domain,url,fn){
document.domain = domain;
var ifr = document.createElement("iframe");//中间层即代理
ifr.src = url;
ifr.style.display = "none";
document.body.appendChild(ifr);
ifr.onload = ifr.onreadystatechange = function(){
var doc = ifr.contentDocument || ifr.contentWindow.document;
alert(doc.getElementsById("div"));
}
}
3、 LHI
/**
*LHI(location.hash+iframe) 父子域即主域相同,而二级域名不同。
*原理:只需要请求方与响应方的document.domain一致.
*注:若iframe下窗口(子窗口)想通过hash传递数据给其父窗口,
*即子窗口可以通过修改parent.location.hash值,
*但是ie、chrome的安全机制无法修改,因此需要在父窗口对应的域下创建一个空页面,
*并在该空页面添加parent.parent.location.hash = self.location.hash.substring(1);
*问题所在:
*(1).信息暴露(url) (2).数据容量/类型有限
*@param domain 域名
*@param url 交互页面url
*@method fn 回调函数 处理传来的数据
*/
function lhi(url,key,fn){
var ifr = document.createElement("iframe");//中间层即代理
ifr.src = url+'#'+key;
ifr.style.display = "none";
document.body.appendChild(ifr);
}
//lhi("./dialog.html","h");
function checkHash(){
try{
var data = location.hash ? location.hash.substring(1) : '';
if(!data) alert(data);
}catch(e){
if(t) clearInterval(t);
}
}
//var t = setInterval(checkHash,2000);
4、 WNI
/**
*WNI(window.name+iframe)
*安全性高、数据容量因浏览器而言(一般2M~32M)、数据类型任意
*原理:通过iframe的src属性将外域转向本域,
*而跨域数据由iframe的window.name从外域传到本域。从而绕过浏览器的访问限制。
*但需要代理页面即和本域同域名下的页面文件
*@param domain 域名
*@param url 交互页面url
*@method fn 回调函数 处理传来的数据
*/
function wni(url){
var state = 0;
//动态创建iframe
var ifr = document.createElement("iframe");//中间层即代理
ifr.style.display = "none";
/**信息交互页面即跨域页面,会发出请求,当不同域时,响应不回来。
*因此需要创建同域代理即ifr.contentWindow.location
*/
ifr.src = url ;
document.body.appendChild(ifr);
//加载外域传来数据
var loadfn = function(){
if(state === 0){
state = 1;
ifr.contentWindow.location = "./proxy.html";//代理页面url即本域页面
}else if(state === 1){
//读取数据
var data = ifr.contentWindow.name;
//操作
alert("跨域数据:"+data)
//销毁iframe,以保安全
ifr.contentWindow.document.write('');
ifr.contentWindow.close();
document.body.removeChild(ifr);
}
};
//事件绑定
addEvent(ifr,"load",loadfn);
}
5、 HTML5
/**
*HTML5 postMessage
*通过事件获取即event.data
*@param url 交互页面url
*@param data 交互数据
*@method 回调函数
*/
function cd(url,data,fn){
//动态创建iframe
var ifr = document.createElement("iframe");//中间层即代理
var targetOrigin = "*"; //用于限制调用postMessage方法的window,若*表示不限制
ifr.style.display = "none";
ifr.src = url ;
document.body.appendChild(ifr);
ifr.contentWindow.postMessage(data,targetOrigin);
}
6、 CROS
/**
*CROS(Cross-Origin Resource Sharing) 跨域资源共享
*原理:使用自定义的HTTP头部让浏览器与服务器进行交互,从而决定请求或响应是应该失败还是成功。
CROS header简单的描述
Access-Control-Allow-Origin
多个域名可以用逗号隔开。如www.ios.com,www.android.com。*表示谁都可以,不限制域名(不建议使用*)。
Access-Control-Expose-Headers
设置浏览器允许访问的服务器的头信息的白名单,带Authorization令牌
Access-Control-Max-Age
在CROS协议中,一个AJAX请求被分成了两步。第一步OPTION为预检测请求,第二步为正式请求。请求的结果的有效期是多久,单位秒。
Access-Control-Allow-Credentials
是否允许请求带有验证信息,若要获取客户端域下的cookie时,需要将其设置为true。
Access-Control-Allow-Methods
资源可以被哪些方式请求GET, POST,TRACE等,多个值时用逗号分开,*为不受限制。
Access-Control-Allow-Headers
允许自定义的头部,逗号隔开。如:Origin , Accept ,Content-Type, x-requested-with, Authorization等。
*若请求两次:跨域请求时浏览器会自动发起一个 OPTIONS 到服务器,是一种预检测请求,用来检测是否安全。
*局限:兼容性不好即对以前浏览器如ie9以下且服务器会对其限制即需要服务器方法允许(参考后面spring)
*/
function xhr(url,type){
var xhr = new XMLHttpRequest();
xhr.onload = xhr.onreadystatechange = function(){
fn(data);
}
xhr.open(type,url,true);
xhr.send();
}
或jQuery中ajax的headers/beforeSend配置或crossDomain:true 若要发送cookie配置则xhrFields:{ withCredentials:true }
7、 其他
/**
*服务代理
*如:nginx代理
*/
继cros
1.采用Spring @CrossOrigin注解
@CrossOrigin(origins = "url", maxAge = 10)
2.过滤器方式
public class CORSFilter implements Filter {
@Override
public void destroy() {
System.out.println("Filter-destroy");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
//String origin = (String) servletRequest.getRemoteHost()+":"+servletRequest.getRemotePort();
HttpServletRequest request = (HttpServletRequest) servletRequest;
System.out.println("Filter-Method:" + request.getMethod()); // GET, POST, OPTIONS
System.out.println("Filter-Authorization:" + request.getHeader("Authorization"));
HttpServletResponse response = (HttpServletResponse) servletResponse;
response.setHeader("Access-Control-Allow-Origin", "http://localhost:8080");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "10");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with,Authorization");
response.setHeader("Access-Control-Allow-Credentials", "true");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void init(FilterConfig arg0) throws ServletException {
System.out.println("Filter-init");
}
}
或继承springMVC中OncePerRequestFilter类
public class CorsFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (request.getHeader("Access-Control-Request-Method") != null && "OPTIONS".equals(request.getMethod())) {
// CORS "pre-flight" request
response.addHeader("Access-Control-Allow-Origin", "*");
response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
response.addHeader("Access-Control-Max-Age", "1800");//30 min
}
//This will filter your requests and responses.
filterChain.doFilter(request, response);
}
}
或springboot
package cn.ucmed.otaku;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ImportResource;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@EnableZuulProxy
@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
@ImportResource("classpath*:META-INF/spring/dubbo.xml")
public class GatewayApplication {
public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args);
}
@Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*"); config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("HEAD");
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST"); config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
config.addExposedHeader("token");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
/**
*flash技术
*/
/**
*事件绑定
*@param tag 标签
*@param type 事件类型,注没有"on"
*@param handle 事件句柄函数即"函数名"
*/
function addEvent(tag,type,handle){
//try...catch 避免判断浏览器
try{
//false,冒泡(默认);true,捕获。指定事件是否在捕获或冒泡阶段执行。
//Chrome、FireFox、Opera、Safari、IE9及其以上
tag.addEventListener(type,handle,false);//可以绑定多个事件
}catch(ex){
try{
//IE9及其以下
tag.attachEvent('on' + type,handle);
}catch(ex){
//
tag['on' + type] = handle;
}
}
}
function proxy(url,obj,fn){//实参一般为匿名方法,也可以为有方法名
//方法作为形参时,不能如fn(参数)。即方法名可以作形参
//而方法作实参如fn(参数),那么在此直接就执行,而不会在调用fn 即方法调用:方法名(实参)
var ifr = document.createElement("iframe");//中间层即代理
ifr.src = url+(obj? obj : '');
ifr.style.display = "none";
try{//防止未给定方法而异常
fn();//由于函数调用如function(){}()或(function 方法名(){})(参数)等形式
alert(fn)
}catch(e){}
document.body.appendChild(ifr);
}
/*
var hash_url = window.location.hash;
var datas = hash_url.split('#')[1].split('&');
function dialog(){
var iframe = document.createElement("iframe");
//iframe.style.display = 'none';
var state = 0;
iframe.src = "./dialog.html";
document.body.appendChild(iframe);
iframe.onload = function(){
if(state === 1){
//alert(iframe.contentWindow.name);
//iframe.contentWindow.close();
//document.body.removeChild(iframe);
}else if(state === 0){
state = 1;
//iframe.contentWindow.location = './proxy.html';
iframe.src = "./proxy.html";
}
}*/