一.为什么浏览器禁止跨域?
广义跨域就是指跨域访问,比如 A 网站的 js 代码试图访问 B 网站,包括提交内容和获取内 容。它是由浏览器的同源策略造成的,所谓同源是指,域名,协议,端口均相同。不符合同源策 略的,都属于跨域。浏览器出于安全策略考虑,是禁止跨域访问的。因为,跨域访问,会给网络 安全带来隐患。比如scrf攻击。 以DVWA为例。在DVWA的low security模式下,选择scrf.
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// Get input
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
// Do the passwords match?
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = mysql_real_escape_string( $pass_new );
$pass_new = md5( $pass_new );
// Update the database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysql_query( $insert ) or die( '<pre>' . mysql_error() . '</pre>' );
// Feedback for the user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match.</pre>";
}
mysql_close();
}
?>
那么,如果该测试网站用户做出如下行为:
1.用户小花访问'localhost/dvwa/login.php'网站A,并且输入了用户名和密码进行登录。
2.小花在同一tab下打开了另一个危险网址B。该网址html如下:
<img src="localhost/dvwa/vulnerabilities/csrf/?password_new=ccc123
&password_conf=ccc123&Change=Change#"border="0" style="display:none;"/>
<h1>404<h1>
<h2>file not found.<h2>
3.用户如果点击该危险网址B,B会以get方式向A发送请求提交数据。
由于用户小花已登录网站A,网站A已验证用户小花的信息,并已给浏览器返回了cookie信息,那么在cookie会话未结束之前,同一个tab下的危险网站B被用户小花点开,并向网站A发送请求时,cookie信息已经自动添加在请求头里了,而网站A没有区分出这个请求到底是用户小花发出的,还是危险网站B发出的,对这个请求做出了处理。
所以,虽然点击了这个危险网址B的用户小花在网页上只看到显示了404错误,可其实小花在网站A里的密码已被成功更改。而更改者就可以盗用用户的身份向该网址发起请求。而小花如果再重新登陆自己账户后就会发现“login filed”。:
二.跨域问题如何解决?
禁止跨域是针对那些不法网站的,但是也不能因噎废食,让安全的合法网站也没办法跨域访问。 尤其是在前后端分离的情况下,可能前端有一个域名,后端有另一个域名,那么如果不能跨域访 问,前后端将不能实现数据交互。解决跨域的方法不止一种,本文选取其中一种方法----封装 一个简单的jsonp请求。
封装jsonp请求
浏览器端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
function jsonp(url, options) {
const { timeout } = options;
return new Promise((resolve, reject) => {
let funcName = `jsonp${Date.now()}`;
let t = null, scriptNode;
// timeout是为了限制请求时间,如下文,如果请求发送三秒还未返回,则设置为reject状态,报超时,网络错误
// 如果请求按时回来了,那么就清除这个定时器
// callback是为了告诉后端,前端定义的接收后端数据的函数名称是什么,如果不告诉,而直接在后端写前端这个名字的话,那么前端改名了后端也要改,这样定义,就只用改前端的
window[funcName] = function(data) {
if (t) clearTimeout(t);
resolve(data);
// 在函数中调用resolve方法时,proMise的状态就变为fullfilled,即操作成功状态,然后执行封装后调用jsonp中then方法里的操作
// then方法里有两个参数,onfulfilled(promise为fulfilled状态时执行),onrejected(promise为rejected状态时执行)
delete window[funcName];
document.body.removeChild(scriptNode);
}
scriptNode = document.createElement('script');
<!--
在网页上添加scr节点,src不受跨域限制,浏览器会将scr里的这段字符串当做js执行,发出请求
-->
scriptNode.src = `${url}?callback=${funcName}`;
document.body.appendChild(scriptNode);
t = setTimeout(() => {
reject('network err, timeout')
// 在函数中调用reject,promise状态变为fullfilled,和then的第二个参数
//onrejected一样,catch会在promise状态为reject时执行
}, timeout)
scriptNode.onerror = function(err) {
reject(err);
}
})
}
// promise:
// pending resolve
// pending
//调用封装好的jsonp库
jsonp('http://localhost:9090/api', {
timeout: 3000
}).then(res => {
console.log('jsonp->', res);
})
.catch(err => {
console.log(err);
})
</script>
</body>
</html>
服务器端
const http = require('http');
// node url模块 解析url
const url = require('url')
http.createServer((req,res)=>{
if(req.url.includes ('/api')){
let posts = ['js','php'];
// res.end(JSON.stringify(posts));
console.log(req);
let myurl = url.parse(req.url);
// console.log(myurl);
let params = new URLSearchParams(myurl.query);
let methodName = params.get('callback');
res.end(`${methodName}(${JSON.stringify(posts)})`)
// 模板字符串里放 前端接收返回数据的函数名 + 后端要给前端返回的数据
则返回给前端后,将会当成一段js执行,调用同名函数显示后端返还的数据。
}
})
.listen(9090,()=>{
console.log(9090);
})