跨域等问题

跨域相关的问题

环境配置

最近又把 xampp 换成了 wampserverwampserver 真好用!

先用 wampserver 配置两个 virtualhost

virtualhost

先在 httpd.conf 开启 virtualhost

然后配置 httpd-vhosts.conf

最后配置计算机中的 hosts 文件

httpd-vhosts.conf中的配置

1
2
3
4
5
6
7
8
9
<VirtualHost *:80>
ServerName abc.test.com
DocumentRoot "e:/code/**/www2"
<Directory "e:/code/**/www2/">
Options +Indexes +Includes +FollowSymLinks +MultiViews
AllowOverride All
Require local
</Directory>
</VirtualHost>

DocumentRoot “ **” #主站点的网页存储位置。

ServerName “ **” #主站点名称(网站的主机名)。

<directory> 是对目录进行访问控制的相关功能。

Options:配置在特定目录使用哪些特性,常用的值和基本含义如下:

  • ExecCGI: 在该目录下允许执行CGI脚本。
  • FollowSymLinks: 在该目录下允许文件系统使用符号连接。
  • Indexes: 当用户访问该目录时,如果用户找不到DirectoryIndex指定的主页文件(例如index.html),则返回该目录下的文件列表给用户。
  • includes:允许使用mod_include模块提供的服务器端包含功能。
  • MultiViews:如果客户端请求的路径可能对应多种类型的文件,那么服务器将根据客户端请求的具体情况自动选择一个最匹配客户端要求的文件。例如,在服务器站点的file文件夹下中存在名为hello.jpghello.html的两个文件,此时用户输入Http://localhost/file/hello,如果在file文件夹下并没有hello子目录,那么服务器将会尝试在file文件夹下查找形如hello.*的文件,然后根据用户请求的具体情况返回最匹配要求的hello.jpg或者hello.html

AllowOverride

  • None 不读取.htaccess
  • all 允许.htaccess所有指令,缺省是all

Require

  • local 仅允许本机访问
  • Require ip 192.168.1.104 192.168.1.205
  • Require host example.org

跨域问题

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)

无论是简单请求,还是非简单请求都会带来跨域问题,只是其他的一些不同。

简单请求

满足下面条件的是简单请求:

  • 请求方法是以下三种:HEAD , GET , POST
  • HTTP的头信息只含有以下这些字段:Accept , Accept-Language , Content-Language , Last-Event-ID , Content-Type 。其中 Content-Type 只允许三个值,分别为:application/x-www-form-urlencodedmultipart/form-datatext/plain

非简单请求

不满足简单请求的,即为非简单请求。

简单请求

看下面的代码,实现了一个简单的请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html>
<head>
<title>test2</title>
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
</head>
<body>
<button id='button'>Ajax</button>
</body>
<script>
$('#button').click(
function(){
$.ajax(
{
method: 'GET',
url:"http://127.0.0.1:5000",
headers: { 'Content-Type':'text/plain'},
success:function(result){alert(result);},
error: function(result) {console.log('error!');}
}
);
}
)
</script>
</html>

服务器代码

1
2
3
4
5
6
7
from flask import Flask, jsonify, request
app = Flask(__name__)

@app.route('/')
def hello_world():
print('123')
return 'Hello World!'

现在查看下结果。虽然这是个简单请求,但是仍然会引发跨域。

图一

现在我们查看下服务器的情况,虽然跨域了,但是服务器返回了结果,只是前端没有得到。

再看一下请求包,服务器发送的是GET请求的信息。

简单请求,就自动在头信息之中,添加一个Origin字段

看一下相应包,服务器其实返回了!

总结

​ 也就是说,在发送简单请求时,服务器收到这个请求,是当作正常请求处理的,但是浏览器阻止了这一过程。所以这种情况下,就会存在安全的隐患。因为服务器是执行了代码的。

非简单请求

前端代码,在header中添加了 ‘others’ 请求头。后端代码未变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html>
<head>
<title>test2</title>
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
</head>
<body>
<button id='button'>Ajax</button>
</body>
<script>
$('#button').click(
function(){
$.ajax(
{
method: 'GET',
url:"http://127.0.0.1:5000",
headers: { 'Content-Type':'text/plain','others':'1234'},
success:function(result){alert(result);},
error: function(result) {console.log('error');}
}
);
}
)
</script>
</html>

先看以下请求包,惊奇的发现,它变成了 opption

看一下响应包,发现变成了空!

再看一下后面的服务器,发现它只收到了OPPTION的请求,并且没有执行代码。

解决跨域问题

那面对这两种情况,该如何处理呢。

简单请求

看一下图一中的报错,已拦截跨源请求:同源策略禁止读取位于 http://127.0.0.1:5000/ 的远程资源。(原因:CORS 头缺少 ‘Access-Control-Allow-Origin’) 。所以我们需要在服务器中,为它增加 Access-Control-Allow-Origin

服务器端代码如下:

1
2
3
4
5
@app.route('/')
def hello_world():
resp = make_response(jsonify({'data': 'hello'})) # 返回json格式
resp.headers['Access-Control-Allow-Origin'] = "http://abc.test.com"
return resp

对于简单请求,除了 Access-Control-Allow-Origin 字段外,还有另外两个需要关注的字段。

Access-Control-Allow-Credentials 如果请求需要带cookie,该header必须为true,同时Access-Control-Allow-Origin不能为*,否则同样拿不到结果。一些问题看下文。

Access-Control-Allow-Methods:用于预检请求响应,表示允许使用的HTTP方法。

处理简单请求,常见的服务器代码。

1
2
3
resp.headers['Access-Control-Allow-Origin'] = "http://abc.test.com"
resp.headers['Access-Control-Allow-Credentials'] = 'true'
resp.headers['Access-Control-Allow-Methods'] = "POST, GET, OPTIONS, PUT, DELETE, PATCH"

非简单请求

客户端代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
$('#button').click(
function(){
$.ajax(
{
method: 'GET',
url:"http://127.0.0.1:5000",
headers: { 'Content-Type':'text/plain','others':'1234'},
success:function(result){alert(result);},
error: function(result) {console.log('error');}
}
);
}
)

需要服务器端跟前端协商好,服务器端对不符合简单请求的字段进行添加。这里主要 flask 有一个坑,它必须在路由方法中加上 OPTIONS 这个方法才能收到。

1
2
3
4
5
6
7
8
@app.route('/', methods=['POST', 'GET', 'OPTIONS'])  # 坑
def hello_world():
print(request.cookies)
resp = make_response(jsonify({'data': 'hello'}))
resp.headers['Access-Control-Allow-Origin'] = "*"
resp.headers['Access-Control-Allow-Methods'] = "POST, GET, OPTIONS, PUT"
resp.headers['Access-Control-Allow-Headers'] = "others, content-type"
return resp

跨域请求过程中的cookie

我在尝试 Access-Control-Allow-Credentials 字段时,遇到了一些问题。两种不同程度的跨域,问题如下图。

首先,对于 ajax 跨域,在jquery的ajax中,是禁止直接在header头,添加cookie的,需要通过如下方式。它的前端代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
function(){
$.ajax(
{
method: 'GET',
url:"http://127.0.0.1:5000",
xhrFields: {
withCredentials: true
},
success:function(result){alert(result);},
error: function(result) {console.log('error');}
}
);
}

而对于图中的问题,经过查阅资料以及测试发现,只有二者属于同一个域(域名)下,才能跨域携带cookie。

对于第二种情况,如果想让 abc.test.com 携带cookie,必须先由服务器进行 set-cookie 。然后客户端才能携带cookie。但是服务器 setcookie ,客户端是无法获取,无法更改的。