大家好,欢迎来到IT知识分享网。
1. 什么是跨域
跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制。
同源策略限制了一下行为:
- Cookie、LocalStorage 和 IndexDB 无法读取
- DOM 和 JS 对象无法获取
- Ajax请求发送不出去
(因为浏览器的同源策略导致了跨域,就是浏览器在搞事情。)
浏览器为什么要搞事件?是不想我们程序员好日子过?对于这样的质问,浏览器甩锅道:“同源策略是一个重要的安全机制,它限制了不同域名交互带来的潜在危险。”
2. 同源策略
3. 常见的跨域场景
4. 没有同源策略限制的两大危险场景
据我了解,浏览器是从两个方面去做这个同源策略的,一是针对接口的请求,二是针对DOM的查询。试想下没有这样的限制这两种动作有什么危险。
4.1. 没有同源策略限制的接口请求
有个小小的东西叫Cookie大家应该知道,一般用来处理登录等场景,目的是让服务端知道谁发出的请求。如果你请求了接口进行登录,服务端验证通过后会在响应头加入Set-Cookie字段,然后下次再请求的时候,浏览器会自动将Cookie附加在HTTP请求头字段Cookie中,服务器端就能知道这个用户已登录过了。
如果这不是一个淘宝网账号,而是你的银行账号,那……。
这就是传说中的CSRF攻击。
5. 案例
使用浏览器打开百度,同时按F12开启开发者工具,输入:fetch(“https://www.baidu.com”),发现没有任何问题,因为当前域就是https://www.baidu.com/,属于同源。
再输入:fetch(“https://www.taobao.com”),发现出现跨域报错:
有些html标签是可以直接跨域调用的,如:
<link>、<script>、<img>、<frame>等这些标签具有跨域特性,可以直接访问。
6. 跨域请求解决方案
- 修改浏览器的安全设置(不推荐)
- JSONP
- 跨域资源共享CORS(Cross-Origin Resource Sharing)
- Iframe(不推荐)
- 反向代理
6.1. JSONP
示例1:JavaScript原生的实现方式
let script = document.createElement('script'); script.src = 'http://localhost:8082/user?callback=callback'; document.body.appendChild(script); function callback(res) {
console.log(res); }
示例2:jQuery的实现方式
注:当前是http://localhost:8081,向8082发送请求
<script src="jquery-2.1.1.min.js" ></script> <script> $(function(){
$('#abc').on('click', function(){
$.ajax({
url : 'http://localhost:8082/user', type : 'get', dataType : 'jsonp', jsonp : 'callback', //请求发送callback success : function(result){
console.log(result); } }); }); }); </script>
Controller类中写法:
@RequestMapping("/user") public String findUser(String callback){
//接收callback值 //模拟数据 User user = new User(); user.setUsername("admin"); user.setPassword(""); //使用jackson将对象转换成JSON格式数据 ObjectMapper om = new ObjectMapper(); String s = null; try {
s = om.writeValueAsString(user); } catch (JsonProcessingException e) {
e.printStackTrace(); } //注:这里需要拼接callback中的数据 return callback +"("+ s +")"; }
6.2. CORS
CORS(Cross-origin resource sharing,跨域源资源共享)是一个 W3C 标准,它是一份浏览器技术的规范,提供了Web服务从不同网域传来沙盒脚本的方法,以避开浏览器的同源策略,这是 JSONP 模式的现代版。
CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。
实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
在Spring框架中,对于CORS也提供了相应的解决方案。
示例:SpringBoot中实现 CORS
<script src="jquery-2.1.1.min.js" ></script> <script> $(function(){
$('#abc').on('click', function(){
$.ajax({
url : 'http://localhost:8082/user', type : 'post', //可使用其他请求方式 success : function(result){
console.log(result); } }); }); }); </script>
Controller类中代码:
@RestController public class CrossAController {
@CrossOrigin @RequestMapping("/user") public User findUser(){
//模拟数据 User user = new User(); user.setUsername("admin"); user.setPassword(""); return user; } }
请求头参数:
Access-Control-Allow-Origin:* 表示不再限制跨域。
@CrossOrigin的使用:
- 在;方法上,表示该方法可以实现跨域请求。
- 在;类上,表示该类中所有方法都可实现跨域请求。
@CrossOrigin的两个参数:
- origins:允许可访问的域列表
- maxAge:准备响应前的缓存持续的最大时间(以秒为单位)。
示例:
@CrossOrigin(origins = "http://localhost:8082", maxAge = 3600)
CORS的全局配置:
@CrossOrigin可以注解在类或方法上,也可以设置成全局。创建配置类,实现WebMvcConfigurer接口,重写addCorsMappings方法。
示例:CORS全局配置类
@Configuration public class WebMvcConfig implements WebMvcConfigurer {
@Override public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/") //允许所有请求映射 .allowedOrigins("http://localhost:8082") //允许8082跨域请求 .allowedHeaders("*") //设置请求头 .allowedMethods("*") //允许所有请求方式 .maxAge(3600); //设置有效时间 } }
注:在Controller注解上方添加@CrossOrigin注解后,仍然出现跨域问题,解决方案之一就是:在@RequestMapping注解中没有指定Get、Post方式,具体指定后,问题解决。
如:@RequestMapping(value = “/user”, method = RequestMethod.POST)
JSONP与CORS的区别:
- CORS与JSONP的使用目的相同,但是比JSONP更强大。
- JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。
6.3. Nginx代理跨域
6.3.1. Nginx配置解决iconfont跨域
浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在Nginx的静态资源服务器中加入以下配置。
location / {
add_header Access-Control-Allow-Origin *; }
6.3.2. Nginx反向代理接口跨域
跨域原理:同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题。
实现思路:通过Nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。
示例:Nginx具体配置
#proxy服务器 server {
listen 81; server_name www.domain1.com; location / {
proxy_pass http://www.domain2.com:8080; #反向代理 proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名 index index.html index.htm; # 当用webpack-dev-server等中间件代理接口访问Nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用: add_header Access-Control-Allow-Origin http://www.domain1.com; #当前端只跨域不带cookie时,可为* add_header Access-Control-Allow-Credentials true; } }
前端代码示例:
var xhr = new XMLHttpRequest(); // 前端开关:浏览器是否读写cookie xhr.withCredentials = true; // 访问nginx中的代理服务器 xhr.open('get', 'http://www.domain1.com:81/?user=admin', true); xhr.send();
Nodejs后台示例:
var http = require('http'); var server = http.createServer(); var qs = require('querystring'); server.on('request', function(req, res) {
var params = qs.parse(req.url.substring(2)); // 向前台写cookie res.writeHead(200, {
'Set-Cookie': 'l=a;Path=/;Domain=www.domain2.com;HttpOnly' // HttpOnly:脚本无法读取 }); res.write(JSON.stringify(params)); res.end(); }); server.listen('8080'); console.log('Server is running at port 8080...');
6.4.document.domain + iframe 跨域
这种跨域的方式最主要的是要求主域名相同。
什么是主域名相同呢?www.yiyuanxinghe.com、map.yiyuanxinghe.com、ba.yiyuanxinghe.com这三个主域名都是yiyuanxinghe.com,而主域名不同的就不能用此方法。
假设目前a.yiyuanxinghe.com和 b.yiyuanxinghe.com分别对应指向不同IP的服务器。
a.manubao.com下有一个test.html文件:
<script type="text/javascript" src = "jquery-1.12.1.js"></script> <div>A页面</div> <iframe style = "display:none" name = "iframe1" id = "iframe" src="http://b.manubao.com/1.html" frameborder="0"></iframe> <script type="text/javascript"> $(function(){
try{
document.domain = "manubao.com"; }catch(e){
} $("#iframe").load(function(){
var jq = document.getElementById('iframe').contentWindow.$ jq.get("http://manubao.com/test.json", function(data){
console.log(data); }); }) }) </script>
利用 iframe 加载其他域下的文件(manubao.com/1.html),同时 document.domain 设置成 manubao.com ,当 iframe 加载完毕后就可以获取manubao.com 域下的全局对象,此时尝试着去请求 manubao.com域名下的 test.json (此时可以请求接口),就会发现数据请求失败了~~惊不惊喜,意不意外!
数据请求失败,目的没有达到,自然是还少一步: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>html</title> <script type="text/javascript" src = "jquery-1.12.1.js"></script> <script type="text/javascript"> $(function(){
try{
document.domain = "manubao.com" }catch(e){
} }) </script> </head> <body> <div id = "div1">B页面</div> </body> </html>
6.5. window.name + iframe 跨域
<body> <script type="text/javascript"> iframe = document.createElement('iframe'), iframe.src = 'http://localhost:8080/data.json'; document.body.appendChild(iframe); iframe.onload = function() {
console.log(iframe.contentWindow.name) }; </script> </body>
<body> <script type="text/javascript"> iframe = document.createElement('iframe'), iframe.src = 'http://localhost:8080/data.json'; document.body.appendChild(iframe); iframe.onload = function() {
iframe.src = 'http://localhost:81/cross-domain/proxy.html'; console.log(iframe.contentWindow.name) }; </script> </body>
理想似乎很美好,在iframe载入过程中,迅速重置iframe.src的指向,使之与index.html同源,那么index页面就能去获取它的name值了!但是现实是残酷的,iframe在现实中的表现是一直不停地刷新, 也很好理解,每次触发onload时间后,重置src,相当于重新载入页面,又触发onload事件,于是就不停地刷新了(但是需要的数据还是能输出的)。修改后代码如下:
<body> <script type="text/javascript"> iframe = document.createElement('iframe'); iframe.style.display = 'none'; var state = 0; iframe.onload = function() {
if(state === 1) {
var data = JSON.parse(iframe.contentWindow.name); console.log(data); iframe.contentWindow.document.write(''); iframe.contentWindow.close(); document.body.removeChild(iframe); } else if(state === 0) {
state = 1; iframe.contentWindow.location = 'http://localhost:81/cross-domain/proxy.html'; } }; iframe.src = 'http://localhost:8080/data.json'; document.body.appendChild(iframe); </script> </body>
所以如上,我们就拿到了服务器返回的数据,但是有几个条件是必不可少的:
- iframe标签的跨域能力
- window.names属性值在文档刷新后依然存在的能力
6.6. location.hash + iframe 跨域
此跨域方法和上面介绍的比较类似,一样是动态插入一个iframe然后设置其src为服务端地址,而服务端同样输出一端js代码,也同时通过与子窗口之间的通信来完成数据的传输。
关于锚点相信大家都已经知道了,其实就是设置锚点,让文档指定的相应的位置。锚点的设置用a标签,然后href指向要跳转到的id,当然,前提是你得有个滚动条,不然也不好滚动嘛是吧。
而location.hash其实就是url的锚点。比如https://www.geekjc.com#geekjcg的网址打开后,在控制台输入location.hash就会返回#geekjc的字段。
基础知识补充完毕,下面我们来说下如何实现跨域
如果index页面要获取远端服务器的数据,动态的插入一个iframe,将iframe的src执行服务器的地址,这时候的top window 和包裹这个iframe的子窗口是不能通信的,因为同源策略,所以改变子窗口的路径就可以了,将数据当做改变后的路径的hash值加载路径上,然后就可以通信了。将数据加在index页面地址的hash上, index页面监听hash的变化,h5的hashchange方法
<body> <script type="text/javascript"> function getData(url, fn) {
var iframe = document.createElement('iframe'); iframe.style.display = 'none'; iframe.src = url; iframe.onload = function() {
fn(iframe.contentWindow.location.hash.substring(1)); window.location.hash = ''; document.body.removeChild(iframe); }; document.body.appendChild(iframe); } // get data from server var url = 'http://localhost:8080/data.php'; getData(url, function(data) {
var jsondata = JSON.parse(data); console.log(jsondata.name + ' ' + jsondata.age); }); </script> </body>
补充说明:其实location.hash和window.name都是差不多的,都是利用全局对象属性的方法,然后这两种方法和jsonp也是一样的,就是只能够实现get请求
6.7.postMessage跨域
这是由H5提出来的一个炫酷的API,IE8+,chrome,ff都已经支持实现了这个功能。这个功能也是非常的简单,其中包括接受信息的Message时间,和发送信息的postMessage方法。
发送信息的postMessage方法是向外界窗口发送信息
var onmessage = function(event) {
var data = event.data; var origin = event.origin; } if(typeof window.addEventListener != 'undefined'){
window.addEventListener('message',onmessage,false); }else if(typeof window.attachEvent != 'undefined'){
window.attachEvent('onmessage', onmessage); }
举个例子:
a.html(http://www.nealyang.cn/a.html)
<iframe id="iframe" src="http://www.neal.cn/b.html" style="display:none;"></iframe> <script> var iframe = document.getElementById('iframe'); iframe.onload = function() {
var data = {
name: 'aym' }; // 向neal传送跨域数据 iframe.contentWindow.postMessage(JSON.stringify(data),'http://www.neal.cn'); }; // 接受domain2返回数据 window.addEventListener('message', function(e) {
alert('data from neal ---> ' + e.data); }, false); </script>
b.html(http://www.neal.cn/b.html)
<script> // 接收domain1的数据 window.addEventListener('message', function(e) {
alert('data from nealyang ---> ' + e.data); var data = JSON.parse(e.data); if (data) {
data.number = 16; // 处理后再发回nealyang window.parent.postMessage(JSON.stringify(data), 'http://www.nealyang.cn'); } }, false); </script>
6.8. WebSocket协议跨域
<div>user input:<input type="text"></div> <script src="./socket.io.js"></script> <script> var socket = io('http://www.domain2.com:8080'); // 连接成功处理 socket.on('connect', function() {
// 监听服务端消息 socket.on('message', function(msg) {
console.log('data from server: ---> ' + msg); }); // 监听服务端关闭 socket.on('disconnect', function() {
console.log('Server socket has closed.'); }); }); document.getElementsByTagName('input')[0].onblur = function() {
socket.send(this.value); }; </script> node Server var http = require('http'); var socket = require('socket.io'); // 启http服务 var server = http.createServer(function(req, res) {
res.writeHead(200, {
'Content-type': 'text/html' }); res.end(); }); server.listen('8080'); console.log('Server is running at port 8080...'); // 监听socket连接 socket.listen(server).on('connection', function(client) {
// 接收信息 client.on('message', function(msg) {
client.send('hello:' + msg); console.log('data from client: ---> ' + msg); }); // 断开处理 client.on('disconnect', function() {
console.log('Client socket has closed.'); }); });
6.9. node代理跨域
var xhr = new XMLHttpRequest(); // 前端开关:浏览器是否读写cookie xhr.withCredentials = true; // 访问http-proxy-middleware代理服务器 xhr.open('get', 'http://www.domain1.com:3000/login?user=admin', true); xhr.send(); 后端代码 var express = require('express'); var proxy = require('http-proxy-middleware'); var app = express(); app.use('/', proxy({
// 代理跨域目标接口 target: 'http://www.domain2.com:8080', changeOrigin: true, // 修改响应头信息,实现跨域并允许带cookie onProxyRes: function(proxyRes, req, res) {
res.header('Access-Control-Allow-Origin', 'http://www.domain1.com'); res.header('Access-Control-Allow-Credentials', 'true'); }, // 修改响应信息中的cookie域名 cookieDomainRewrite: 'www.domain1.com' // 可以为false,表示不修改 })); app.listen(3000); console.log('Proxy server is listen at port 3000...');
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/111260.html



