跨域请求
1. CORS
CORS
: 跨域资源共享CSRF
: 跨站请求伪造用户在访问A网站, 此时用户在A网站的信息记录在Cookie中, 假设A网站没有对Cookie做同源策略限制, 此时如果有B网站的链接嵌入到A网站中, 用户点开了该链接后, 跳转到B网站, 假设B网站是不怀好意的网站, 那么B网站的所有者就能获取到A网站的Cookie, 然后使用Cookie以该用户的身份去访问A网站.
产生跨站伪造的原因就是网站没有遵循同源策略
1.2. 同源策略
多个页面的协议, 域名, 端口完全相同, 则认为他们遵循了同源策略.
协议: 要么是http, 要么是https等.
域名: 多个页面的域名要完全相同.
www.php.cn
和www.php.net
是不同的!端口: 要么是80, 要么是443, 或者都是别的, 必须相同.
同源策略是一种安全机制, 浏览器禁止通过脚本发起跨域请求, 如:
XMLHttpRequest
,Fetch API
等. 但是允许HTML标签属性跨域. 即, 可以用<a>
标签或<img>
发送跨域请求或跨域访问.- 当使用脚本发起跨域请求时, 浏览器会提示禁止跨域.
此时, 若目标网站允许跨域, 则需要在跨域请求的目标请求头指定允许跨域访问自己的站点:
header('Access-Control-Allow-Origin: http://php11.edu')
. 记得一定要指定协议- 若不指定允许跨域访问的站点, 那么这个站点将是非常不安全的. 即, 不要这样设置:
header('Access-Control-Allow-Origin: *')
- 若不指定允许跨域访问的站点, 那么这个站点将是非常不安全的. 即, 不要这样设置:
1.3. JSONP跨域
- 虽然同源策略不允许脚本发起跨域请求, 但是并不阻止HTML标签的属性发起. JSONP就是使用HTML标签的属性发起跨域请求的方式.
<script src="把需要跨域的脚本URL写到这里发起一个跨域请求">
</script>
实战案例
1. 通过HTML标签属性跨域
浏览器允许通过标签属性跨域
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>通过HTML标签属性跨域</title>
</head>
<body>
<img src="https://img.php.cn/upload/image/234/213/408/1590257532837910.png" alt="球员列表" style="width: 400px;"><br>
<a href="https://www.baidu.com">访问百度</a><br>
<button type="button">发送跨域请求</button>
</body>
</html>
2. CORS跨域请求
php.edu
站点的文件php.edu/0522/test1.php
中设置允许站点php11.edu
跨域
<?php
// 指定允许http://php11.edu站点跨域访问当前文件
header('Access-Control-Allow-origin: http://php11.edu');
echo '跨域请求返回的数据';
// 刷新缓冲区
flush();
php11.edu
站点的php11.edu/js/0522/demo1.html
使用ajax发起跨域访问请求
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CORS跨域</title>
</head>
<body>
<button type="button">发送跨域请求</button>
<script>
var btn = document.querySelector('button');
btn.addEventListener('click', function() {
// 通过ajax发送一个跨域请求
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if(xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText);
}
}
xhr.open('GET', 'http://php.edu/0522/test1.php', true);
xhr.send(null);
});
</script>
</body>
</html>
成功获取到数据
3. JSONP跨域
php.edu
站点中负责获取请求,并执行查询,调用回调函数.
<?php
// 这里返回json数据, 而json只支持utf8编码
header('content-type: text/html;charset=utf-8');
// 模拟根据id值获取数据, 并调用回调函数处理数据
// 获取请求参数(jsonp指定一个回调函数名)
$callback = $_GET['jsonp'];
$id = $_GET['id'];
// 返回的处理结果数组
$res = [
'status' => '1',
'message' => '查询成功',
];
// 判断id值是否有效
if(!filter_var($id, FILTER_VALIDATE_INT, ['option' => ['min_range' => 1]])) {
$res['status'] = '0';
$res['message'] = 'ID值无效';
}
// 连接数据库
$pdo = new PDO('mysql:host=localhost;dbname=phpedu;charset=utf8;port=3306', 'root', 'root');
// 查询
$stmt = $pdo->query("SELECT * FROM `player` WHERE `id` = {$id}");
// 判断是否查到值
if(!$stmt || $stmt->rowCount < 1) {
// 没查到的处理
$res['status'] = '0';
$res['message'] = 'ID值无效';
} else {
// 查到数据
$res['data'] = $stmt->fetch(PDO::FETCH_ASSOC);
}
// 打印回调
echo $callback . '(' . json_encode(json_encode($res)) . ')';
php11.edu
中通过JSONP发送跨域请求
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>通过JSONP跨域</title>
</head>
<body>
<button>发送跨域请求</button>
<script>
/* 1. 被当做参数值传给跨域的目标站点, 目标站点echo "调用该函数的字符串",
相当于在当前站点执行该函数调用 */
function handle(jsonData) {
// console.log(jsonData);
// json=>js对象
var res = JSON.parse(jsonData);
// 约定状态值为0表示查询结果异常
if(res.status === '0') {
alert(res.message ?? '查询失败');
return;
}
// 渲染数据
var ul = document.createElement('ul');
ul.innerHTML += "<ul>姓名: " + res.data.name + "</ul>";
ul.innerHTML += "<ul>球队: " + res.data.team + "</ul>";
ul.innerHTML += "<ul>身高: " + res.data.height + "</ul>";
ul.innerHTML += "<ul>体重: " + res.data.weight + "</ul>";
ul.innerHTML += "<ul>位置: " + res.data.position + "</ul>";
document.body.appendChild(ul);
}
</script>
<script>
/* 2. 给按钮增加点击事件, 生成一个跨域请求的script标签 */
var btn = document.querySelector('button');
btn.addEventListener('click', function () {
// 生成一对<script>标签
var script = document.createElement("script");
// 为script标签的src属性赋予一个跨域请求的url, 并带上handle函数作为回调
script.src = "http://php.edu/0522/test2.php?jsonp=handle&id=1";
// 把生成的标签加到head标签中
document.head.appendChild(script);
// 相当于在head标签中生成了下面的标签, 然后就能自动发起跨域请求了
// <-script src="http://php.edu/0522/test2.php?jsonp=handle&id=1"><-/script>
}, false);
</script>
</body>
</html>
执行结果:
- 查询到值时
- 查询不到值
学习心得
跨域访问涉及到网站安全, 必须要注意. 成功跨域访问, 一种是访问的目标站点指定了当前站点允许跨域访问(CORS); 另一种是曲线救国, 使用HTML标签的属性发起.