PHP解决跨域问题
<h3>一、什么是跨域</h3><p> 跨域,指的是从一个域名去请求另外一个域名的资源。即跨域名请求!跨域时,浏览器不能执行其他域名网站的脚本,是由浏览器的同源策略造成的,是浏览器施加的安全限制。</p>
<h3>二、什么是同源策略</h3>
<p> 同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。</p>
<p> <img src="https://img2020.cnblogs.com/blog/2026637/202005/2026637-20200528172335131-1729071210.png"></p>
<p> 同源策略限制内容有:</p>
<ol>
<li>Cookie、LocalStorage、IndexedDB 等存储性内容</li>
<li>DOM 节点</li>
<li>AJAX 请求发送后,结果被浏览器拦截了</li>
</ol>
<p> 但是有三个标签是允许跨域加载资源:</p>
<ol>
<li><img src=XXX></li>
<li><link href=XXX></li>
<li><script src=XXX></li>
</ol>
<h3>三、常见跨域场景有哪些?</h3>
<p> 当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作“跨域”。</p>
<p> 常见跨域场景如下图所示:</p>
<p> <img src="https://img2020.cnblogs.com/blog/2026637/202005/2026637-20200528172400494-2114419238.png"></p>
<p> 特别说明两点:<br> 第一:如果是协议和端口造成的跨域问题,那么“前台”是无能为力的。<br> 第二:在跨域问题上,仅仅是通过“URL的首部”来识别,而不会根据域名对应的IP地址是否相同来判断。“URL的首部”可以理解为“协议, 域名和端口必须匹配”。</p>
<p> 这里你或许有个疑问:请求跨域了,那么请求到底发出去没有?<br> 跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。</p>
<p> 你可能会疑问明明通过表单的方式可以发起跨域请求,为什么 Ajax 就不会?因为归根结底,跨域是为了阻止用户读取到另一个域名下的内容,Ajax 可以获取响应,浏览器认为这不安全,所以拦截了响应。</p>
<p> 但是表单并不会获取新的内容,所以可以发起跨域请求。同时也说明了跨域并不能完全阻止 CSRF,因为请求毕竟是发出去了。</p>
<h3>四、PHP解决跨域问题的方法 </h3>
<p> 跨域的严格一点来说就是只要协议,域名,端口有任何一个的不同,就被当作是跨域。比如,在实际项目中由于前后端分离当前端需要通过接口向后台发起请求,此时就会出现跨域问题,那么,这类问题需要如何解决呢?其实php解决跨域问题很简单,只需加上下面的代码就可以了: </p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">header</span>("Access-Control-Allow-Origin:*");</pre>
</div>
<p> 加上这行代码表示允许所有的域名访问,不过为了安全起见,在实际项目中往往会限定只允许固定的几个域名和方法发起的请求。</p>
<p> 1、允许单个域名访问 </p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">header</span>('Access-Control-Allow-Origin:http://www.baidu.com'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">header</span>('Access-Control-Allow-Methods:POST'); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">表示只允许POST请求</span>
<span style="color: rgba(0, 128, 128, 1)">header</span>('Access-Control-Allow-Headers:x-requested-with, content-type'); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">请求头的限制</span></pre>
</div>
<p> 2、不限制域名</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">header</span>('Access-Control-Allow-Origin:*'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">header</span>('Access-Control-Allow-Methods:POST');<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">表示只允许POST请求</span>
<span style="color: rgba(0, 128, 128, 1)">header</span>('Access-Control-Allow-Headers:x-requested-with, content-type');</pre>
</div>
<p> 3、允许多个域名访问</p>
<p> 在实际项目中最好指定能跨域访问的域名,增加安全性。可以写在一个公共类里面,封装一个方法调用。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 设置能访问的域名</span>
<span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(128, 0, 128, 1)">$originarr</span> =<span style="color: rgba(0, 0, 0, 1)"> [
</span>'https://test1.com',
'https://test2.com',<span style="color: rgba(0, 0, 0, 1)">
];
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
*公共方法调用
</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> setheader()
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取当前跨域域名</span>
<span style="color: rgba(128, 0, 128, 1)">$origin</span> = <span style="color: rgba(0, 0, 255, 1)">isset</span>(<span style="color: rgba(128, 0, 128, 1)">$_SERVER</span>['HTTP_ORIGIN']) ? <span style="color: rgba(128, 0, 128, 1)">$_SERVER</span>['HTTP_ORIGIN'] : ''<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 128, 128, 1)">in_array</span>(<span style="color: rgba(128, 0, 128, 1)">$origin</span>, self::<span style="color: rgba(128, 0, 128, 1)">$originarr</span><span style="color: rgba(0, 0, 0, 1)">)) {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 允许 $originarr 数组内的 域名跨域访问</span>
<span style="color: rgba(0, 128, 128, 1)">header</span>('Access-Control-Allow-Origin:' . <span style="color: rgba(128, 0, 128, 1)">$origin</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 响应类型</span>
<span style="color: rgba(0, 128, 128, 1)">header</span>('Access-Control-Allow-Methods:POST,GET'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 带 cookie 的跨域访问</span>
<span style="color: rgba(0, 128, 128, 1)">header</span>('Access-Control-Allow-Credentials: true'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 响应头设置</span>
<span style="color: rgba(0, 128, 128, 1)">header</span>('Access-Control-Allow-Headers:x-requested-with,Content-Type,X-CSRF-Token'<span style="color: rgba(0, 0, 0, 1)">);
}
}</span></pre>
</div>
<p> 在PHP上如何实现</p>
<div class="cnblogs_code">
<pre><?<span style="color: rgba(0, 0, 0, 1)">php
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 制定允许其他域名访问</span>
<span style="color: rgba(0, 128, 128, 1)">header</span>("Access-Control-Allow-Origin:*"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 响应类型</span>
<span style="color: rgba(0, 128, 128, 1)">header</span>('Access-Control-Allow-Methods:POST'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 响应头设置</span>
<span style="color: rgba(0, 128, 128, 1)">header</span>('Access-Control-Allow-Headers:x-requested-with, content-type'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">$callback = isset($_REQUEST['callback']) ? trim($_REQUEST['callback']) : ''; //jsonp回调参数,必需</span>
<span style="color: rgba(0, 0, 255, 1)">function</span> getKey(<span style="color: rgba(128, 0, 128, 1)">$key</span>,<span style="color: rgba(128, 0, 128, 1)">$default</span>=""<span style="color: rgba(0, 0, 0, 1)">){
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 128, 128, 1)">trim</span>(<span style="color: rgba(0, 0, 255, 1)">isset</span>(<span style="color: rgba(128, 0, 128, 1)">$_REQUEST</span>[<span style="color: rgba(128, 0, 128, 1)">$key</span>])?<span style="color: rgba(128, 0, 128, 1)">$_REQUEST</span>[<span style="color: rgba(128, 0, 128, 1)">$key</span>]:<span style="color: rgba(128, 0, 128, 1)">$default</span><span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(128, 0, 128, 1)">$id</span> = getKey("id"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$conn</span> = <span style="color: rgba(0, 128, 128, 1)">mysqli_connect</span>("localhost","root","","test") or <span style="color: rgba(0, 0, 255, 1)">die</span>("连接失败"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$conn</span>->query("set names utf8"<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$sql</span> = "select * from data where ".<span style="color: rgba(128, 0, 128, 1)">$id</span>." is not null"<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 0, 128, 1)">$result</span> = <span style="color: rgba(128, 0, 128, 1)">$conn</span>->query(<span style="color: rgba(128, 0, 128, 1)">$sql</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$arr</span> =<span style="color: rgba(0, 0, 0, 1)"> [];
</span><span style="color: rgba(0, 0, 255, 1)">while</span>(<span style="color: rgba(128, 0, 128, 1)">$row</span>=<span style="color: rgba(128, 0, 128, 1)">$result</span>-><span style="color: rgba(0, 0, 0, 1)">fetch_assoc()){
</span><span style="color: rgba(0, 128, 128, 1)">array_push</span>(<span style="color: rgba(128, 0, 128, 1)">$arr</span>,json_encode(<span style="color: rgba(128, 0, 128, 1)">$row</span><span style="color: rgba(0, 0, 0, 1)">));
}
</span><span style="color: rgba(128, 0, 128, 1)">$json</span> = json_encode(<span style="color: rgba(128, 0, 128, 1)">$arr</span>);<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">json 数据</span>
<span style="color: rgba(0, 128, 128, 1)">print_r</span>(<span style="color: rgba(128, 0, 128, 1)">$json</span>);</pre>
</div>
<p> 4、代理 </p>
<p> 这种经常用,比如www.a.cn/index.html需要调用www.b.cn/server.php,我们可以这样做,写一个接口www.a.cn/server.php,由这个接口在后端去调用www.b.cn/server.php并拿到返回值,然后再返回给index.html,这就是一个代理的模式。相当于绕过了浏览器端,自然就不存在跨域问题。<br> 5、Nginx反向代理</p>
<p> 使用nginx反向代理实现跨域,是最简单的跨域方式。只需要修改nginx的配置即可解决跨域问题,支持所有浏览器,支持session,不需要修改任何代码,并且不会影响服务器性能。</p>
<p> 实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。</p>
<p> 修改配置文件nginx.conf,如下:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> proxy服务器</span>
<span style="color: rgba(0, 0, 0, 1)">server {
listen </span>81<span style="color: rgba(0, 0, 0, 1)">;
server_namewww</span>.domain1.<span style="color: rgba(0, 0, 0, 1)">com;
location </span>/<span style="color: rgba(0, 0, 0, 1)"> {
proxy_pass http</span>:<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">www.domain2.com:8080;#反向代理</span>
proxy_cookie_domain www.domain2.com www.domain1.com; <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)">修改cookie里域名</span>
indexindex.html index.<span style="color: rgba(0, 0, 0, 1)">htm;
</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用</span>
add_header Access-Control-Allow-Origin http:<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">www.domain1.com;#当前端只跨域不带cookie时,可为*</span>
add_header Access-Control-Allow-Credentials <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
}
}</span></pre>
</div>
<p> 配置修改好后,再重启nginx。index.html文件访问代理服务器</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> index.html</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> xhr = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> XMLHttpRequest();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 前端开关:浏览器是否读写cookie</span>
xhr.withCredentials = <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 访问nginx中的代理服务器</span>
xhr.open('get', 'http://www.domain1.com:81/?user=admin', <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">);
xhr</span>.<span style="color: rgba(0, 0, 0, 1)">send();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> server.js</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> http = <span style="color: rgba(0, 0, 255, 1)">require</span>('http'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> server = http.<span style="color: rgba(0, 0, 0, 1)">createServer();
</span><span style="color: rgba(0, 0, 255, 1)">var</span> qs = <span style="color: rgba(0, 0, 255, 1)">require</span>('querystring'<span style="color: rgba(0, 0, 0, 1)">);
server</span>.on('request', <span style="color: rgba(0, 0, 255, 1)">function</span>(req,<span style="color: rgba(0, 0, 0, 1)"> res) {
</span><span style="color: rgba(0, 0, 255, 1)">var</span> params = qs.parse(req.url.substring(2<span style="color: rgba(0, 0, 0, 1)">));
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 向前台写cookie</span>
res.writeHead(200,<span style="color: rgba(0, 0, 0, 1)"> {
</span>'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly' <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> HttpOnly:脚本无法读取</span>
<span style="color: rgba(0, 0, 0, 1)"> });
res</span>.write(JSON.<span style="color: rgba(0, 0, 0, 1)">stringify(params));
res</span>.<span style="color: rgba(0, 128, 128, 1)">end</span><span style="color: rgba(0, 0, 0, 1)">();
});
server</span>.listen('8080'<span style="color: rgba(0, 0, 0, 1)">);
console</span>.<span style="color: rgba(0, 128, 128, 1)">log</span>('Server is running at port 8080...');</pre>
</div>
<p> </p>
<p> </p><br><br>
来源:https://www.cnblogs.com/Jessie-candy/p/12982380.html
頁:
[1]