为什么会有跨域问题
同源策略/SOP(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。
SOP要求两个通讯地址的:
- 协议 (http、https)
- 域名 (a.com、b.com)
- 端口号 (a.com:8080、a.com:8000)
必须相同,否则两个地址的通讯将被浏览器视为不安全的,并被block下来
前端跨域问题解决方式:
1.与服务端部署到同域上(正常部署)
正常将web项目放置到服务端项目的静态文件所在目录即可
缺陷:
- 若服务端与web端并非相同公司或者项目组开发:需要将项目整合,然后交到服务端开发人员或者web端开发人员进行部署,部署人员可能对于另外一端的业务部署导致不必要的矛盾。
- 若web项目的服务器分属于不同的域:可能需要将web项目拆分成不同的部分分别部署到不同的服务器上,加大了部署的困难程度。
2.CORS
同域安全策略CORS(Cross-Origin Resource Sharing)
它要求请求的服务器在响应的报头(Response Header)添加 Access-Control-Allow-Origin标签,从而允许此标签所对应域访问此服务器的资源,调用此服务器的接口。
缺陷:
- 默认情况下,跨源请求不提供凭据(cookie、HTTP认证及客户端SSL证明等),通过将withCredentials属性设置为true,可以指定某个请求应该发送凭据。如果服务器接收带凭据的请求,会用下面的HTTP头部来响应:
Access-Control-Allow-Credentials: true
如果发送的是带凭据的请求,但服务器的相应中没有包含这个头部,那么浏览器就不会把相应交给JavaScript,请求就无法得到结果的数据(浏览器得到了,但是我们请求的方法得不到,因为被浏览器拦截了),因此在需要传Cookie等时,服务端的Access-Control-Allow-Origin必须配置具体的具体的域名。并且还需要设置其他的请求头:
Access-Control-Allow-Origin; //request.getHeader("Origin")||www.xxx.com
Access-Control-Allow-Methods; //POST, GET, OPTIONS, DELETE
Access-Control-Allow-Credentials;//是否支持cookie跨域
Access-Control-Allow-Headers;//x-requested-with,content-type
Java可以参照:
public void CORSMethod(ServletRequest request, ServletResponse response) throws IOException,ServletException {
HttpServletResponse res = (HttpServletResponse) response;
res.setContentType("text/html;charset=UTF-8");
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
res.setHeader("Access-Control-Max-Age", "0");
res.setHeader("Access-Control-Allow-Headers", "Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With,userId,token");
res.setHeader("Access-Control-Allow-Credentials", "true");
res.setHeader("XDomainRequestAllowed","1");
}
javaScript可以参考:
$.ajax({
url: 'http://b.fdipzone.com/server.php', // 跨域
xhrFields:{withCredentials: true}, // 发送凭据
dataType: 'json',
type: 'post',
data: {'name':'fdipzone'},
success:function(ret){
if(ret['success']==true){
alert('cookie:' + ret['cookie'])
}
}
});
然后JavaScript请求的时候加上此标志:withCredentials: true。
angular可以百度:“withCredentials angularJS”
3. jsonp方式
angular可以参考:
<script>
var app = angular.module("app", []);
app.controller("ctrl", function ($scope, $http) {
$scope.jsonpAction = function () {
$http.jsonp('http://localhost/Cross_Access/JsonP.php?callback=JSON_CALLBACK')
.success(function (data) {
console.log(data + '---');
});
};
});
</script>
php可以参考:
<?php
/**
* Created by PhpStorm.
* User: Shadow
* Date: 2017/3/2
* Time: 09:21
*/
//服务端返回JSON数据
$arr = array('a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5);
$result = json_encode($arr);
//动态执行回调函数
$callback = $_GET['callback'];
echo $callback . "($result)";
工作方式介绍:
- 调用 $http.jsonp方法时,会根据所传参数,在当前网页创建一个”Script”标签
<script type="text/javascript" src="http://localhost/Cross_Access/JsonP.php?callback= angular.callbacks._1" async=""></script>
其中 JSON_CALLBACK
被Angular自动替换为了angular.callbacks._x
_x
为Angular定义的请求序列,angular会自动生成一个_x的同名方法存放到angular.callbacks
中去
当标签生效
http://localhost/Cross_Access/JsonP.php
被下载回来时,会被当成javascript直接执行,因为我们的php端设置了返回的内容是 callback与实际数据拼接起来的一个方法,故下载的内容实际是angular.callbacks._1(xxxxxx)
其会被直接执行,故若在_1(data)
方法中直接将参数通过jsonp.success(function(data){})
回调,那么便能直接取到相应的值移除当前网页中插入的script标签,并移除angular.callbacks中的_1(data)方法,一个完整的jsonp请求便已成功
4.代理服务器方式
前面几种方式或多或少都需要服务端人员配合,代理服务器方式可以完全不需要服务端人员配合
- 自己写一个php,或者Java的服务端 将web项目与此项目发布到服务器,这样web项目就能直接调用此服务器的内容,然后再用此服务器调用目标服务器的内容,因为避开了浏览器的限制,因此这种方式也是能成的,但是需要注意,这种情况下因为Cookie有path和 domain的限制,因此需要截取目标服务器的Cookie,将其改为代理服务器的path和domain,否则在需要使用到Cookie的时候,由于path或domain不对,不能将Cookie转发到目标服务器。 此方式的大概流程为
- nginx反向代理方式
第一步: 下载安装 nginx
安装方式请百度,并确认浏览器能正常访问到 本地nginx首页:http://localhost 或 http://localhost:8080(不同的系统可能nginx的默认端口号不同)
第二步: 配置nginx环境
打开nginx对应的目录,找到nginx.conf文件,以mac为例:
cd /usr/local/etc/nginx
cat nginx.conf
对于我们只需关注其中server的配置项,现将server中的注释项目全部删掉(不用担心,因为此文件还有一个nginx.conf.default的文件为默认配置的备份)
vim nginx.conf
server {
listen 8080;#监听端口号对应localhost:8080
#listen 80;#默认端口号对应localhost:80即localhost,80端口号可以省略不写
server_name localhost;#服务名称
location / { #匹配 ‘/’时对应的配置
root html; # 根目录对应为 相对目录 mac上为 /usr/local/opt/nginx/html
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
其中 location /中配置的root为我们的web页面需要放置的目录,在mac上默认为 /usr/local/opt/nginx/html
cd /usr/local/opt/nginx/html
进入此目录可以看到里面有个index.html的文件,就是本地nginx首页:http://localhost 或 http://localhost:8080所对应的原文件,我们改了其内容,然后在浏览器刷新后能看到变化。
可以将 root 后面的 html默认的相对位置改为我们自己的目录,比如:
location / {
root /Users/Shadow/Sites;
}
然后进入对应目录:
cd /Users/Shadow/Sites
touch index.html
echo Hello nginx > index.html
保存nginx.conf并重新加载nginx配置文件:
nginx -s reload
在此之前还可以使用
nginx -t
命令检查nginx.conf文件的有效性 刷新http://localhost页面,可以看到
到此为止,nginx的环境已经配置好了
步骤三 部署项目
以testNGINX 项目为例 在/Users/Shadow/Sites/目录下新建项目,新建文件 testNginx.html文件输入以下内容:
https://m.douban.com/rexxar/api/v2/community_center?for_mobile=true
<!DOCTYPE html>
<html lang="en" ng-app="app">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="angular.js"></script>
</head>
<body ng-controller="myCtrl">
<input type="button" value="开始请求" ng-click="action()">
</body>
<script>
var url = "https://m.douban.com/rexxar/api/v2/community_center?for_mobile=true";
var app = angular.module("app", []).controller("myCtrl", function ($scope, $http) {
$scope.action = function () {
$http.get(url).success(function (data) {
console.log(data);
}).error(function (e) {
console.log(e);
});
}
})
</script>
</html>
在浏览器打开
http://localhost/testNGINX/testNGINX.html
并点击开始请求按钮,然后查看日志,发生跨域问题 No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://localhost’ is therefore not allowed access. 解决这个问题,需要修改nginx的配置文件:
cd /usr/local/etc/nginx
vim nginx.conf
在 server中添加如下内容:
location /douban/{
proxy_pass https://m.douban.com/;
proxy_cookie_path / /douban;
}
修改 testNGINX.html的请求地址为:
var url = “https://localhost/douban/rexxar/api/v2/community_center?for_mobile=true“;
重新加载nginx配置文件
nginx -s reload
浏览器刷新
http://localhost/testNGINX/testNGINX.html
点击开始请求,查看日志可以看见请求成功了。
上面 location的配置的意思如下:
nginx截获路径为https://localhost/douban/的请求,并将请求转发到 https://m.douban.com/替换为proxy_pass配置的域名。
完整的Server配置:
server {
listen 80;
server_name localhost;
location / {
root /Users/Shadow/Sites;
}
error_page 500 502 503 504 /50x.html;
#relative path
location = /50x.html {
root html;
}
#absolute path
location /douban/{
proxy_pass https://m.douban.com/;
proxy_cookie_path / /douban;
}
}
代理配置后完整的js请求方法
<!DOCTYPE html>
<html lang="en" ng-app="app">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="angular.js"></script>
</head>
<body ng-controller="myCtrl">
<input type="button" value="开始请求" ng-click="action()">
</body>
<script>
var url = "http://localhost/douban/rexxar/api/v2/community_center?for_mobile=true";
var app = angular.module("app", []).controller("myCtrl", function ($scope, $http) {
$scope.action = function () {
$http.get(url).success(function (data) {
console.log(data);
}).error(function (e) {
console.log(e);
});
}
})
</script>
</html>
配置前后请求地址对比:
前
https://m.douban.com/rexxar/api/v2/community_center?for_mobile=true
后
http://localhost/douban/rexxar/api/v2/community_center?for_mobile=true
若配置不成功,可以优先检查如下几点:
- nginx.conf文件是否保存并被重新加载了
- 配置的路径是否完整,尤其需要检查后面的斜杠,斜杠有没有的差别很大,具体可以百度nginx的配置方法
- web项目的请求地址是否改为了nginx服务器配置的地址
- nginx首页没法打开:
先检查nginx服务是否开启,再查看配置的根目录(root)中是否有index.html文件 - index.html文件修改了,但是浏览器看不到变化:
清空浏览器缓存再次查看 - 需要发布的目录放到了根目录下,但是请求时404:
- 查看根目录配置是否正确;
- 请求地址是否正确;
- 请求路径是否正确;
- 配置文件的路径是否正确,尤其需要查看斜杠,这个特别容易忘记
- nginx.conf文件更新了,并且重新加载了,但是请求时看不到任何变化:
windows下,任务管理器是否打开了多个nginx的进程,可以先将nginx进程全部关闭后,重新打开一个nginx