什么是跨域?
跨域,指的是从一个域名去请求另外一个域名的资源。即跨域名请求!跨域时,浏览器不能执行其他域名网站的脚本,是由浏览器的同源策略造成的,是浏览器施加的安全限制。
什么是同源策略?
同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指"协议 域名 端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
这里你或许有个疑问:请求跨域了,那么请求到底发出去没有?
跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。
你可能会疑问明明通过表单的方式可以发起跨域请求,为什么 Ajax 就不会?
因为归根结底,跨域是为了阻止用户读取到另一个域名下的内容,Ajax 可以获取响应,浏览器认为这不安全,所以拦截了响应。但是表单并不会获取新的内容,所以可以发起跨域请求。同时也说明了跨域并不能完全阻止 CSRF,因为请求毕竟是发出去了。
PHP解决跨域问题的方法
方法一
代码语言:javascript复制<?php
setHeader();
echo json_encode(['code' => 200, 'msg' => 'success']);
exit;
function originList()
{
return [
'http://a.test.com',
];
}
function setHeader()
{
$origin = isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : '';
if (in_array($origin, originList())) {// 获取当前跨域域名
/*
header("Access-Control-Allow-Origin:*");//不限制域名
header('Access-Control-Allow-Origin:http://a.test.cn');//允许单个域名访问
*/
header('Access-Control-Allow-Origin:' . $origin); // 允许 $originarr 数组内的 域名跨域访问
header('Access-Control-Allow-Methods:POST,GET'); // 响应类型
header('Access-Control-Allow-Credentials: true');// 带 cookie 的跨域访问
header('Access-Control-Allow-Headers:x-requested-with,Content-Type,X-CSRF-Token'); // 响应头设置
}
}
方法二(代理)
比如a.test.com/a.html需要调用b.test.com/index.php,我们可以这样做,写一个接口a.test.com/index.php,由这个接口在后端去调用b.test.com/index.php并拿到返回值,然后再返回给a.html,这就是一个代理的模式。相当于绕过了浏览器端,自然就不存在跨域问题。
代码语言:javascript复制<?php
if ($_SERVER['REQUEST_METHOD'] == 'GET') {
include 'a.html';
exit;
}
$data = (new AipHttpClient())->post('http://b.test.com',$_POST);
echo $data['content'];
exit;
/**
* Http Client
*/
class AipHttpClient
{
protected $headers;
protected $connectTimeout;
protected $socketTimeout;
protected $conf;
/**
* HttpClient
* @param array $headers HTTP header
*/
public function __construct($headers = array())
{
$this->headers = $this->buildHeaders($headers);
$this->connectTimeout = 60000;
$this->socketTimeout = 60000;
$this->conf = array();
}
/**
* 连接超时
* @param int $ms 毫秒
*/
public function setConnectionTimeoutInMillis($ms)
{
$this->connectTimeout = $ms;
}
/**
* 响应超时
* @param int $ms 毫秒
*/
public function setSocketTimeoutInMillis($ms)
{
$this->socketTimeout = $ms;
}
/**
* 配置
* @param array $conf
*/
public function setConf($conf)
{
$this->conf = $conf;
}
/**
* 请求预处理
* @param resource $ch
*/
public function prepare($ch)
{
foreach ($this->conf as $key => $value) {
curl_setopt($ch, $key, $value);
}
}
/**
* @param string $url
* @param array $data HTTP POST BODY
* @param array $param HTTP URL
* @param array $headers HTTP header
* @return array
*/
public function post($url, $data = array(), $params = array(), $headers = array())
{
$url = $this->buildUrl($url, $params);
$headers = array_merge($this->headers, $this->buildHeaders($headers));
$ch = curl_init();
$this->prepare($ch);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_POSTFIELDS, is_array($data) ? http_build_query($data) : $data);
curl_setopt($ch, CURLOPT_TIMEOUT_MS, $this->socketTimeout);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $this->connectTimeout);
$content = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($code === 0) {
throw new Exception(curl_error($ch));
}
curl_close($ch);
return array(
'code' => $code,
'content' => $content,
);
}
/**
* @param string $url
* @param array $datas HTTP POST BODY
* @param array $param HTTP URL
* @param array $headers HTTP header
* @return array
*/
public function multi_post($url, $datas = array(), $params = array(), $headers = array())
{
$url = $this->buildUrl($url, $params);
$headers = array_merge($this->headers, $this->buildHeaders($headers));
$chs = array();
$result = array();
$mh = curl_multi_init();
foreach ($datas as $data) {
$ch = curl_init();
$chs[] = $ch;
$this->prepare($ch);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_POSTFIELDS, is_array($data) ? http_build_query($data) : $data);
curl_setopt($ch, CURLOPT_TIMEOUT_MS, $this->socketTimeout);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $this->connectTimeout);
curl_multi_add_handle($mh, $ch);
}
$running = null;
do {
curl_multi_exec($mh, $running);
usleep(100);
} while ($running);
foreach ($chs as $ch) {
$content = curl_multi_getcontent($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$result[] = array(
'code' => $code,
'content' => $content,
);
curl_multi_remove_handle($mh, $ch);
}
curl_multi_close($mh);
return $result;
}
/**
* @param string $url
* @param array $param HTTP URL
* @param array $headers HTTP header
* @return array
*/
public function get($url, $params = array(), $headers = array())
{
$url = $this->buildUrl($url, $params);
$headers = array_merge($this->headers, $this->buildHeaders($headers));
$ch = curl_init();
$this->prepare($ch);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_TIMEOUT_MS, $this->socketTimeout);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $this->connectTimeout);
$content = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($code === 0) {
throw new Exception(curl_error($ch));
}
curl_close($ch);
return array(
'code' => $code,
'content' => $content,
);
}
/**
* 构造 header
* @param array $headers
* @return array
*/
private function buildHeaders($headers)
{
$result = array();
foreach ($headers as $k => $v) {
$result[] = sprintf('%s:%s', $k, $v);
}
return $result;
}
/**
*
* @param string $url
* @param array $params 参数
* @return string
*/
private function buildUrl($url, $params)
{
if (!empty($params)) {
$str = http_build_query($params);
return $url . (strpos($url, '?') === false ? '?' : '&') . $str;
} else {
return $url;
}
}
}
方法三(Nginx反向代理)
代码语言:javascript复制使用nginx反向代理实现跨域,是最简单的跨域方式。只需要修改nginx的配置即可解决跨域问题,支持所有浏览器,支持session,不需要修改任何代码,并且不会影响服务器性能。
实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。
修改配置文件nginx.conf,如下:
// proxy服务器
server {
listen 80;
server_name a.test.com;
location / {
#可以自行加正则约束那些需要代理
proxy_pass http://b.test.com:80; #反向代理
proxy_cookie_domain b.test.com a.test.com; #修改cookie里域名
index index.html index.htm;
# 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
add_header Access-Control-Allow-Origin http://a.test.com; #当前端只跨域不带cookie时,可为*
add_header Access-Control-Allow-Credentials true;
}
}