0%

跨域

跨域

跨域的概念

  • 跨域指的是浏览器不能执行其他网站的脚本,它是由浏览器的同源策略造成的,是浏览器对 javascript 施加的安全限制。
  • 简单来说跨域是指从一个域名的网页去请求另一个域名的资源,由于有同源策略的关系,一般不允许直接这么访问。
  • 但很多场景会有跨域访问需求的出现,比如前后端分离模式下,如果前后端的域名不一致,此时就出现了跨域问题。

跨域条件(同源策略)

1
2
3
4
5
6
7
8
9
同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。
同源策略,它是由Netscape提出的一个著名的安全策略。
当一个浏览器的两个tab页中分别打开来 百度和谷歌的页面
当浏览器的百度tab页执行一个脚本的时候会检查这个脚本是属于哪个页面的,
即检查是否同源,只有和百度同源的脚本才会被执行。
如果非同源,那么在请求数据时,浏览器会在控制台中报一个异常,提示拒绝访问。
同源策略是浏览器的行为,是为了保护本地数据不被JavaScript代码获取回来的数据污染,因此拦截的是客户端发出的请求回来的数据接收,即请求发送了,服务器响应了,但是无法被浏览器接收。

简单来说就是我在一个网站下调用的所有脚本必须要和这个网站是同源(同一个域名同一个端口同协议),否则,浏览器将会拒绝其调用,除非这个被调用的地址(服务器)设置了允许跨域

协议、域名、端口号都相同,只要有一个不相同,那么都是非同源

在这里插入图片描述

img

跨域模拟

这里用一个nginx来模拟跨域,nginx里面有两个location。/和/ysw两个页面,第一个页面/种加入get第二个页面/ysw的按钮,然后分别用同源和不同源路径来测试模拟跨域。

nginx配置文件:

image-20220817113025818

根目录index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
</head>
<body>
<h2>Hello World!</h2>
<script type="text/javascript">
function fun1(){
var request = new XMLHttpRequest();
request.open("GET","https://192.168.60.199/ysw") #这里是访问域名加上uri:/ysw
request.send();
request.onreadystatechange = function(){
if(request.status==200 && request.readyState == 4){
console.log("响应的结果" + request.responseText)
}
}
}
</script>
</body>
<input type="button" value="跨域调用" onclick="fun1()">
</html>

ysw目录随便写一个index.html即可,只需要保证这个路径可以访问。

同域测试

点击跨域调用,这里同个域下面是完全没问题的,下面测试不同域。

image-20220817112917891

跨域测试

下面把

1
request.open("GET","http://192.168.60.199/ysw")

改成

1
request.open("GET","http://192.168.60.199:81/ysw")

然后把nginx配置文件新加一个server 监听 81端口,把原来的 location /ysw 放进去

image-20220817113215396

由于协议变了,所以这里就跨域了,然后再测试。

image-20220817113334654

查看console页面,已经无法get了

image-20220817113442056

跨域解决

1、前后端结合(JsonP)

  虽然jsonp也可以实现跨域,但是因为jsonp不支持post请求,应用场景受到很大限制,所以这里不对jsonp作介绍。

2、纯后端方式一(CORS方式)

  CORS 是w3c标准的方式,通过在web服务器端设置:响应头Access-Cntrol-Alow-Origin 来指定哪些域可以访问本域的数据,ie8&9(XDomainRequest),10+,chrom4,firefox3.5,safair4,opera12支持这种方式。

  服务器代理,同源策略只存在浏览器端,通过服务器转发请求可以达到跨域请求的目的,劣势:增加服务器的负担,且访问速度慢。

3.纯后端方式二(Nginx代理方式)

​ 通过nginx添加配置解决跨域(添加HTTP头部信息)

nginx解决跨域

在被请求页面所在的服务器(nginx)上加上下面配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
   server {
listen 81;

location /ysw {
root /;
index index.html;
###下面是需要添加的配置
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; #加在server或者location都可以
if ($request_method = 'OPTIONS') {
return 204;
}
###上面是需要添加的配置
}
}

加上以后nginx -s reload 重载配置文件,就可以正常跨域了,如下:

image-20220817120442887

image-20220817120450203

参数说明

Access-Control-Allow-Origin

  服务器默认是不被允许跨域的。给Nginx服务器配置Access-Control-Allow-Origin *后,表示服务器可以接受所有的请求源(Origin),即接受所有跨域的请求。

Access-Control-Allow-Headers

  是为了防止出现以下错误:

1
Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.

  这个错误表示当前请求Content-Type的值不被支持。其实是我们发起了”application/json”的类型请求导致的。这里涉及到一个概念:预检请求(preflight request),请看下面”预检请求”的介绍。

Access-Control-Allow-Methods

  是为了防止出现以下错误:

1
Content-Type is not allowed by Access-Control-Allow-Headers in preflight response.
给OPTIONS 添加 204的返回

  是为了处理在发送POST请求时Nginx依然拒绝访问的错误,发送”预检请求”时,需要用到方法 OPTIONS ,所以服务器需要允许该方法。

Access-Control-Allow-Credentials

允许客户端携带验证信息,例如cookie,这样客户端在发起跨域请求时,就可以携带允许的头。(这个非必选项,如不需要携带验证信息可以不加)

ingress跨域

这里模拟k8s中出现跨域的情况,上面nginx模拟跨域的时候是用的端口来进行做的,这里就使用两个不同的域名来模拟一下跨域。index.html的代码还是和上面一样,第一个域名通过点击按钮来请求不同域的域名。

这里需要准备两个nginx,分别使用不同的域名,配置文件可以使用

跨域测试

域名1:

nginx1.yaml:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
---

apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
type: ClusterIP
selector:
tier: nginx
ports:
- name: rs-nginx
port: 80
targetPort: 80
---
apiVersion: apps/v1 #指定api版本标签
kind: StatefulSet #定义资源的类型/角色,ReplicaSet为控制器
metadata: #定义资源的元数据信息
name: rs-nginx #定义资源的名称,在同一个namespace空间中必须是唯一的
labels: #定义资源标签
app: nginx
spec:
serviceName: "nginx"
replicas: 1 #定义副本数
selector: #定义选择器
matchLabels: #匹配上面的标签
tier: nginx #匹配模板名称
template: #定义模板
metadata:
labels:
tier: nginx
spec:
containers: #定义容器信息
- name: nginx #容器名,与标签名要相同
image: nginx #容器使用的镜像以及版本
ports:
- containerPort: 80
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "nfs-delete"
resources:
requests:
storage: 1Gi

ingress1.yaml:

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
26
27
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-staging
nginx.ingress.kubernetes.io/enable-cors: "true"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
tls:
- hosts:
- "test-ysw.dev.worknote.xyz"
secretName: test-ysw-dev-worknote-xyz
rules:
- host: "test-ysw.dev.worknote.xyz"
http:
paths:
# web页面
- pathType: Prefix
path: "/"
backend:
service:
name: nginx
port:
number: 80

域名2:

nginx2.yaml:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
---
apiVersion: v1
kind: Service
metadata:
name: nginx2
spec:
type: ClusterIP
selector:
tier: nginx2
ports:
- name: rs-nginx2
port: 80
targetPort: 80
---
apiVersion: apps/v1 #指定api版本标签
kind: StatefulSet #定义资源的类型/角色,ReplicaSet为控制器
metadata: #定义资源的元数据信息
name: rs-nginx2 #定义资源的名称,在同一个namespace空间中必须是唯一的
labels: #定义资源标签
app: nginx
spec:
serviceName: "nginx2"
replicas: 1 #定义副本数
selector: #定义选择器
matchLabels: #匹配上面的标签
tier: nginx2 #匹配模板名称
template: #定义模板
metadata:
labels:
tier: nginx2
spec:
containers: #定义容器信息
- name: nginx #容器名,与标签名要相同
image: nginx #容器使用的镜像以及版本
ports:
- containerPort: 80
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "nfs-delete"
resources:
requests:
storage: 1Gi

ingress2.yaml

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
26
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-ysw
annotations:
cert-manager.io/cluster-issuer: letsencrypt-staging
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "true"
kubernetes.io/ingress.class: "nginx"
spec:
tls:
- hosts:
- "test-ysw1.dev.worknote.xyz"
secretName: test1-ysw-dev-worknote-xyz
rules:
- host: "test-ysw1.dev.worknote.xyz"
http:
paths:
# web页面
- pathType: Prefix
path: "/"
backend:
service:
name: nginx2
port:
number: 80

yaml文件解读

1
2
3
4
上面一共四个文件,其实就是两个域名(ingress)文件以及它们对应的svc和应用(nginx)。
这里应用用的是statefulset(这个不重要),主要要讲的是应用用的是nginx,并且做了持久化pv/pvc,由于这里有storageclass(自动创建pv工具),所以这里只需要直接部署即可,做持久化的目录是/usr/share/nginx/html,是nginx的默认访问目录,这里我们只需要部署完后在这个文件对应映射的持久化存储目录中写入index.html即可。
第一个域名为test-ysw.dev.worknote.xyz,作为要测试跨域的域名
第二个域名为test-ysw1.dev.worknote.xyz,作为要被跨域的域名

index.html

test-ysw.dev.worknote.xyz的index.html #和上面同域测试一样的,只不过改了访问的域名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
</head>
<body>
<h2>Hello World!</h2>
<script type="text/javascript">
function fun1(){
var request = new XMLHttpRequest();
request.open("GET","https://test-ysw1.dev.worknote.xyz") #这里是访问域名加上uri
request.send();
request.onreadystatechange = function(){
if(request.status==200 && request.readyState == 4){
console.log("响应的结果" + request.responseText)
}
}
}
</script>
</body>
<input type="button" value="跨域调用" onclick="fun1()">
</html>

test-ysw1.dev.worknote.xyz的index.html 写入了’ysw’这个字符。

部署完yaml文件并写入index.html文件以后开始测试。

image-20220817160441781

image-20220817160451561

这里报错提示跨域了

跨域解决

在test-ysw1.dev.worknote.xyz域名(被跨域的域名)的ingress的annotations中加入以下参数

1
2
3
4
5
6
    nginx.ingress.kubernetes.io/cors-allow-headers: >-
DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Author
ization
nginx.ingress.kubernetes.io/cors-allow-methods: "PUT, GET, POST, OPTIONS"
nginx.ingress.kubernetes.io/cors-allow-origin: "*"
nginx.ingress.kubernetes.io/enable-cors: 'true'

改完配置的ingress2.yaml如下,改完后重新部署该文件(重载)

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
26
27
28
29
30
31
32
33
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-ysw
annotations:
cert-manager.io/cluster-issuer: letsencrypt-staging
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "true"
kubernetes.io/ingress.class: "nginx"
# 跨域处理
nginx.ingress.kubernetes.io/cors-allow-headers: >-
DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Author
ization
nginx.ingress.kubernetes.io/cors-allow-methods: "PUT, GET, POST, OPTIONS"
nginx.ingress.kubernetes.io/cors-allow-origin: "*"
nginx.ingress.kubernetes.io/enable-cors: 'true'
spec:
tls:
- hosts:
- "test-ysw1.dev.worknote.xyz"
secretName: test1-ysw-dev-worknote-xyz
rules:
- host: "test-ysw1.dev.worknote.xyz"
http:
paths:
# web页面
- pathType: Prefix
path: "/"
backend:
service:
name: nginx2
port:
number: 80

效果测试

image-20220817161907116

image-20220817161919567

总结

跨域就是浏览器不允许一个域名下的资源调用别的域名的资源(基于安全考虑),但对于开发者来讲,有时候跨域是必须的,例如你的资源服务和网站服务都不是一个域名,或者前后端域名不一样,都会造成跨域,这个时候需要让后面那个被调用的域名允许被跨域访问,本人作为运维人员,前端后端的解决办法涉及不到,所以这里只做nginx的跨域配置。

另外在生产环境中,被跨域的域名设置Access-Control-Allow-Origin:* #允许所有域名的脚本访问该资源 。显然是不合理的,可以只允许单个域名跨域

1
Access-Control-Allow-Origin:https://www.baidu.com    #允许特定的域名访问。