当一个客户端去访问另一个不同域名或者同域名不同端口的服务时,就会发出跨域请求。如果此时该服务不允许其进行跨域资源访问,那么就会因为跨域问题而导致访问失败。跨源域资源共享CORS(Cross-Origin Resource Sharing)允许Web应用服务器进行跨域访问控制。本文介绍如何在ASM的Virtualservice中配置corsPolicy,实现跨域访问。

跨源域资源共享CORS介绍

浏览器会限制页面脚本内发起的跨源HTTP请求以提高安全性,对于需要跨域访问的场景,ASM提供了跨源域资源共享CORS功能。跨域资源共享CORS是一种基于HTTP头的机制,该机制允许服务器标示除了它自己以外的其它域,协议和端口,使得浏览器可以访问加载这些资源。

跨域资源共享CORS的验证机制分两种模式:简单请求和预先请求。
  • 简单请求模式:

    浏览器直接发送跨域请求,并在请求头中携带Origin的头,表明这是一个跨域的请求。服务器端接到请求后,会根据自己的跨域规则,通过Access-Control-Allow-Origin和Access-Control-Allow-Methods响应头来返回验证结果。

  • 预先请求模式:

    浏览器会先发送Preflighted requests(预先验证请求),Preflighted requests是一个OPTION请求,用于询问要被跨域访问的服务器,是否允许当前域名下的页面发送跨域的请求。在得到服务器的跨域授权后才能发送真正的HTTP请求。

    OPTIONS请求头部中会包含以下头部:Origin、Access-Control-Request-Method、Access-Control-Request-Headers。服务器收到OPTIONS请求后,设置Access-Control-Allow-Origin、Access-Control-Allow-Method、Access-Control-Allow-Headers、Access-Control-Max-Age头部与浏览器沟通来判断是否允许这个请求。如果Preflighted requests验证通过,浏览器才会发送真正的跨域请求。

当请求同时满足下面三个条件时,CORS验证机制会使用简单请求模式进行处理,否则CORS验证机制会使用预先请求模式进行处理。
  • 请求方法是下列之一:

    GET、HEAD、POST

  • 请求头中的Content-Type请求头的值是下列之一:

    ext/plain、application/x-www-form-urlencoded、multipart/form-data

  • Fetch规范定义了CORS安全头的集合,安全头的集合是下列之一:

    Accept、Accept-Language、Content-Language、Content-Type(需要注意额外的限制)

在VirtualService中配置corsPolicy

整个CORS通信过程都是浏览器自动完成,您需要在对应服务的VirtualService中添加corsPolicy字段,使服务允许跨域请求,从而实现跨域通信。
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: ratings-route
spec:
  hosts:
  - ratings.prod.svc.cluster.local
  http:
  - route:
    - destination:
        host: ratings.prod.svc.cluster.local
        subset: v1
    corsPolicy:
      allowOrigins:
      - exact: https://example.com
      - regex: .*     #支持regex。
      allowMethods:
      - POST
      - GET
      allowCredentials: false
      allowHeaders:
      - X-Foo-Bar
      maxAge: "24h"
参数 说明
allowOrigins 允许请求服务的来源,允许带哪些Origin地址的请求,支持regex匹配。对于不需要携带身份凭证的请求,服务器可以指定该字段的值为通配符,表示允许来自所有域的请求。
allowMethods 允许请求服务的方法,实际请求所允许使用的HTTP方法。
allowHeaders 允许请求服务的标头,用于预检请求的响应。其指明了实际请求中允许携带的首部字段。
exposeHeaders 公开请求服务的标头,让服务器把允许浏览器访问的头放入白名单。
maxAge 最大浏览器缓存时间,指定了浏览器能够缓存preflight请求结果的时间。
allowCredentials 允许请求服务的凭证,符合要求的凭证才能请求服务。

跨域访问最佳实践

操作前准备:

步骤一:部署应用

  1. 部署后端应用。
    1. 通过kubectl工具连接集群
    2. 使用以下内容,创建details.yaml 文件。
      apiVersion: v1
      kind: Service
      metadata:
        name: details
        labels:
          app: details
          service: details
      spec:
        ports:
        - port: 9080
          name: http
        selector:
          app: details
      ---
      apiVersion: v1
      kind: ServiceAccount
      metadata:
        name: bookinfo-details
        labels:
          account: details
      ---
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: details-v1
        labels:
          app: details
          version: v1
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: details
            version: v1
        template:
          metadata:
            labels:
              app: details
              version: v1
          spec:
            serviceAccountName: bookinfo-details
            containers:
            - name: details
              image: docker.io/istio/examples-bookinfo-details-v1:1.16.4
              imagePullPolicy: IfNotPresent
              ports:
              - containerPort: 9080
              securityContext:
                runAsUser: 1000
    3. 执行以下命令,在default命名空间部署details应用。
      kubectl apply -f details.yaml -n default
  2. 部署前端应用。
    1. 使用以下内容,创建istio-cors-demo.yaml 文件。
      apiVersion: v1
      kind: ServiceAccount
      metadata:
        name: istio-cors-demo
      ---
      apiVersion: v1
      kind: Service
      metadata:
        name: istio-cors-demo
        labels:
          app: istio-cors-demo
          service: istio-cors-demo
      spec:
        ports:
          - name: http
            port: 8000
            targetPort: 80
        selector:
          app: istio-cors-demo
      ---
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: istio-cors-demo
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: istio-cors-demo
            version: v1
        template:
          metadata:
            labels:
              app: istio-cors-demo
              version: v1
          spec:
            serviceAccountName: istio-cors-demo
            containers:
              - image: registry.cn-hangzhou.aliyuncs.com/build-test/istio-cors-demo:v1.0-g8e215f6-aliyun
                imagePullPolicy: IfNotPresent
                name: istio-cors-demo
                ports:
                  - containerPort: 80
    2. 执行以下命令,在foo命名空间部署istio-cors-demo应用。
      kubectl apply -f istio-cors-demo.yaml -n foo 

步骤二:创建ASM网关

  1. 登录ASM控制台
  2. 在左侧导航栏,选择服务网格 > 网格管理
  3. 网格管理页面,找到待配置的实例,单击实例的名称或在操作列中单击管理
  4. 在网格详情页面左侧导航栏单击ASM网关,然后在右侧页面单击创建
  5. 创建页面设置名称ingressgateway,选择部署集群,设置负载均衡类型公网访问,在新建负载均衡下选择负载均衡规格,其他参数采用默认设置,单击创建
  6. 重复步骤4和步骤5,创建名为ingressgateway2的网关。

步骤三:创建路由规则

  1. 创建后端应用的路由规则。
    1. 创建网关规则。

      创建网关规则,绑定details应用和ingressgateway网关。

      1. 登录ASM控制台
      2. 在左侧导航栏,选择服务网格 > 网格管理
      3. 网格管理页面,找到待配置的实例,单击实例的名称或在操作列中单击管理
      4. 在网格详情页面左侧导航栏选择流量管理中心 > 网关规则,然后在右侧页面单击使用YAML创建
      5. 创建页面设置命名空间为default,选择任意模板,将YAML文本框中的内容替换为以下内容,单击创建
        apiVersion: networking.istio.io/v1beta1
        kind: Gateway
        metadata:
          name: bookinfo-gateway
          namespace: default
        spec:
          selector:
            istio: ingressgateway
          servers:
            - hosts:
                - '*'
              port:
                name: http
                number: 80
                protocol: HTTP
                                           
    2. 创建虚拟服务。
      1. 在网格详情页面左侧导航栏选择流量管理中心 > 虚拟服务,然后在右侧页面单击使用YAML创建
      2. 创建页面设置命名空间为default,选择任意模板,将YAML文本框中的内容替换为以下内容,单击创建
        apiVersion: networking.istio.io/v1beta1
        kind: VirtualService
        metadata:
          name: bookinfo
          namespace: default
        spec:
          gateways:
            - bookinfo-gateway
          hosts:
            - '*'
          http:
            - match:
                - uri:
                    prefix: /details
              route:
                - destination:
                    host: details
                    port:
                      number: 9080
                                                
    3. 访问后端应用。
      1. 获取ingressgateway网关的IP地址,具体操作,请参见添加入口网关服务
      2. 在浏览器地址栏中输入http://<ingressgateway网关的IP>/details/2 detail

        返回以上页面,说明访问后端应用details成功。

  2. 创建前端应用的路由规则。
    1. 创建网关规则。

      创建网关规则,绑定istio-cors-demo应用和ingressgateway2网关。

      1. 在网格详情页面左侧导航栏选择流量管理 > 网关规则,然后在右侧页面单击使用YAML创建
      2. 创建页面设置命名空间为foo,选择任意模板,将YAML文本框中的内容替换为以下内容,单击创建
        apiVersion: networking.istio.io/v1beta1
        kind: Gateway
        metadata:
          name: istio-cors-demo-gateway
          namespace: foo
        spec:
          selector:
            istio: ingressgateway2
          servers:
            - hosts:
                - '*'
              port:
                name: http
                number: 80
                protocol: HTTP
    2. 创建虚拟服务。
      1. 在网格详情页面左侧导航栏选择流量管理中心 > 虚拟服务,然后在右侧页面单击使用YAML创建
      2. 创建页面设置命名空间为foo,选择任意模板,将YAML文本框中的内容替换为以下内容,单击创建
        apiVersion: networking.istio.io/v1beta1
        kind: VirtualService
        metadata:
          name: istio-cors-demo
          namespace: foo
        spec:
          gateways:
            - istio-cors-demo-gateway
          hosts:
            - '*'
          http:
            - route:
                - destination:
                    host: istio-cors-demo
                    port:
                      number: 8000
  3. 使用前端应用访问后端应用。
    1. 获取ingressgateway2网关的IP地址,具体操作,请参见添加入口网关服务
    2. 在谷歌浏览器地址栏中输入http://<ingressgateway2网关的IP地址>
    3. 在URL文本框中输入http://<ingressgateway网关的IP地址>/details/2 ,单击Send
      URL
    4. 在谷歌浏览器右上角单击设置图标,选择更多工具 > 开发者工具
      报错

      由于前端istio-cors-demo应用访问后端details应用需要跨域,访问会失败,因此出现以上报错。

步骤四:配置跨域访问

  1. 登录ASM控制台
  2. 在左侧导航栏,选择服务网格 > 网格管理
  3. 网格管理页面,找到待配置的实例,单击实例的名称或在操作列中单击管理
  4. 在网格详情页面左侧导航栏选择流量管理中心 > 虚拟服务
  5. 虚拟服务页面单击bookinfo右侧操作列下的查看YAML
  6. 编辑对话框http参数下添加以下跨域配置,然后单击确定
    - corsPolicy:
        allowCredentials: false
        allowMethods:
          - POST
          - GET
        allowOrigins:
          - prefix: 'http://<ingressgateway2网关的IP>'
        maxAge: 24h
    代码

步骤五:验证跨域访问是否成功

  1. 在谷歌浏览器地址栏中输入http://<ingressgateway2网关的IP地址>
  2. 在URL文本框中输入http://<ingressgateway网关的IP地址>/details/2 ,单击send
    访问成功

    返回以上页面,说明前端istio-cors-demo应用访问后端details应用成功,跨域访问成功。