前端如何进行跨域

什么是跨域

跨域是指一个域下的文档或脚本试图去请求另一个域下的资源,使用XMLHttpRequest进行跨域请求会被浏览器拦截。

由于浏览器同源策略,凡是发送请求url的协议、域名、端口三者之间任意一与当前页面地址不同即为跨域。

跨域解决方案

跨域资源共享(CORS)

CORS是主流的跨域解决方案,所有浏览器都支持该功能(IE8+:IE8/9需要使用XDomainRequest对象来支持CORS)。

跨域请求默认不携带cookie,这时只需要服务端设置Access-Control-Allow-Origin,如果需要请求携带cookie(由于同源策略的限制,携带的不是当前页的cookie而是跨域请求接口所在域的cookie),则前后端都需要设置。

const xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容

// 前端设置是否带cookie
xhr.withCredentials = true;

xhr.open("post", "http://www.demo2.com:8080/login", true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send("user=admin");

xhr.onreadystatechange = () => {
    if (xhr.readyState == 4 && xhr.status == 200) {
        console.log(xhr.responseText);
    }
};

HTTP请求可以分为简单请求和复杂请求,CORS这两类请求按不同的策略进行跨域资源共享协商。

当HTTP请求出现以下两种情况时,浏览器认为是简单请求:

  1. 请求方法是GET、HEAD或者POST,并且当请求方法是POST时,Content-Type必须是application/x-www-form-urlencoded, multipart/form-data或着text/plain中的一个值。
  2. 请求中没有自定义HTTP头部。

对于简单跨域请求,浏览器要做的就是在HTTP请求中添加Origin Header,将JavaScript脚本所在域填充进去,向其他域的服务器请求资源。服务器端收到一个简单跨域请求后,根据资源权限配置,在响应头中添加Access-Control-Allow-Origin Header。浏览器收到响应后,查看Access-Control-Allow-Origin Header,如果当前域已经得到授权,则将结果返回给JavaScript。否则浏览器忽略此次响应。

对于复杂跨域请求浏览器在发送真实HTTP请求之前会先发送一个OPTIONS的预检请求,检测服务器端是否支持真实请求进行跨域资源访问,真实请求的信息在OPTIONS请求中通过Access-Control-Request-Method Header和Access-Control-Request-Headers Header描述,此外与简单跨域请求一样,浏览器也会添加Origin Header。

服务器端接到预检请求后,根据资源权限配置,在响应头中放入Access-Control-Allow-Origin Header、Access-Control-Allow-Methods和Access-Control-Allow-Headers Header,分别表示允许跨域资源请求的域、请求方法和请求头。

此外,服务器端还可以加入Access-Control-Max-Age响应头,允许浏览器在指定时间内,无需再发送预检请求进行协商,直接用本次协商结果即可。浏览器根据OPTIONS请求返回的结果来决定是否继续发送真实的请求进行跨域资源访问。

@WebFilter("/*")
public class CORSFilter implements Filter {
 
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse resp = (HttpServletResponse) servletResponse;
 
        // 告诉浏览器允许所有的域访问
        // 注意 * 不能满足带有cookie的访问,Origin 必须是全匹配
        // resp.addHeader("Access-Control-Allow-Origin", "*");
        // 解决办法通过获取Origin请求头来动态设置
        String origin = request.getHeader("Origin");
        if (StringUtils.hasText(origin)) {
            resp.addHeader("Access-Control-Allow-Origin", origin);
        }
        // 允许带有cookie访问
        resp.addHeader("Access-Control-Allow-Credentials", "true");
 
        // 告诉浏览器允许跨域访问的方法
        resp.addHeader("Access-Control-Allow-Methods", "*");
 
        // 告诉浏览器允许带有Content-Type,header1,header2头的请求访问
        // resp.addHeader("Access-Control-Allow-Headers", "Content-Type,header1,header2");
        // 设置支持所有的自定义请求头
        String headers = request.getHeader("Access-Control-Request-Headers");
        if (StringUtils.hasText(headers)) {
            resp.addHeader("Access-Control-Allow-Headers", headers);
        }
 
        // 告诉浏览器缓存OPTIONS预检请求1小时,避免非简单请求每次发送预检请求,提升性能
        resp.addHeader("Access-Control-Max-Age", "3600");
 
        chain.doFilter(request, resp);
    }
}

JSONP

拥有src属性和href属性的

const script = document.createElement("script");
script.type = "text/javascript";

// 传参并指定回调执行函数为onBack
script.src = "http://www.demo2.com:8080/login?user=admin&callback=onBack";
document.head.appendChild(script);

// 回调执行函数
function onBack(res) {
    console.log(JSON.stringify(res));
}

// 服务端返回如下(返回时即执行全局函数):
// onBack({"status": true, "user": "admin"})

代理跨域

同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨域问题。

通过代理服务器(域名与demo1相同,端口不同)做跳板机,反向代理访问demo2接口,还可以修改cookie中demo信息,方便当前域cookie写入。

postMessage跨域

使用postMessage跨域很简单,通过postMessageAPI对目标窗口发送数据,通过监听message事件接收数据,从而实现跨窗口的异域通讯。

WebSocket跨域

websocket协议没有同源策略的限制,而且它本身就有意被设计成可以跨域的一个手段。由于历史原因,跨域检测一直是由浏览器端来做,但是WebSocket出现以后,对于WebSocket的跨域检测工作就交给了服务端,浏览器仍然会带上一个Origin跨域请求头,服务端则根据这个请求头判断此次跨域WebSocket请求是否合法。

iframe跨域

document.domain + iframe跨域

如果主域相同只有子域不一样时,通过设置两个页面的document.domain都为主域名就实现了同域。

location.hash + iframe跨域

location.hash属性是一个可读可写的字符串,该字符串是URL的锚部分(从# 号开始的部分)。 通过location.hash跨域就是利用动态改变location.hash不会重载iframe的特性将要传递的数据放在url里,因此传递数据量有限、安全性也比较低。

window.name + iframe跨域

name是window对象的一个属性,当在iframe中加载新页面时,window.name不会发生变化。

给iframe赋值跨域的链接,加载完后触发onload事件,此时iframe已经拿到数据放在window.name里,因为浏览器同源策略没法直接拿iframe的name值,所以将iframe的地址改成同域名下的一个网页,在用contentWindow方法获取iframe的name值获取数据。

a.html:

let flag = false;
const iframe = document.createElement("iframe");

// 加载跨域页面
iframe.src = "http://www.demo2.com/b.html";

// onload事件会触发2次,第1次加载跨域页,并留存数据于window.name
iframe.onload = () =>  {
    if (flag) {
        // 第2次onload(同域proxy页)成功后,读取同域window.name中数据
        console.log(iframe.contentWindow.name);
        // 获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域frame js访问)
        iframe.contentWindow.document.write("");
        iframe.contentWindow.close();
        document.body.removeChild(iframe);
        return;
    }

    // 第1次onload(跨域页)成功后,切换到同域代理页面
    iframe.contentWindow.location = "http://www.demo1.com/proxy.html";
    flag = true;
};

document.body.appendChild(iframe);

b.html:

window.name = "This is demo2 data!";

window.name值最大支持2M,如果给window.name赋值,window.name会调用类似toString的方法将赋给它的值转换成对应的字符串表示。

参考:https://www.cnblogs.com/sdcs/p/8484905.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值