跨域介绍
跨来源资源共享(Cross-Origin Resource Sharing(CORS))是一种使用额外 HTTP 标头来让目前浏览网站的 user agent 能获得访问不同来源(网域)服务器特定资源之权限的机制。当 user agent 请求一个不是目前文件来源——来自于不同网域(domain)、通信协定(protocol)或通信端口(port)的资源时,会建立一个跨来源HTTP请求(cross-origin HTTP request)。
当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域:
当前页面 url | 被请求页面 url | 是否跨域 | 原因 |
---|---|---|---|
http://www.example.com/ | http://www.example.com/index.html | 否 | 同源(协议、域名、端口号相同) |
http://www.example.com/ | https://www.example.com/index.html | 跨域 | 协议不同(http/https) |
http://www.example.com/ | http://www.baidu.com/ | 跨域 | 主域名不同(example/baidu) |
http://www.example.com/ | http://blog.example.com/ | 跨域 | 子域名不同(www/blog) |
http://www.example.com:8080/ | http://www.example.com:7001/ | 跨域 | 端口号不同(8080/7001) |
跨域种类
一共有 2 种跨域请求:
- 简单请求
- 预检请求
简单请求
当 HTTP 请求出现以下两种情况时,浏览器认为是简单跨域请求:
- 请求方法是 GET、HEAD 或者 POST,并且当请求方法是 POST 时,Content-Type 必须是 application/x-www-form-urlencoded, multipart/form-data 或着 text/plain 中的一个值。
- 请求中没有自定义 HTTP 头部。
对于简单跨域请求,浏览器要做的就是在 HTTP 请求中添加 Origin Header,将 JavaScript 脚本所在域填充进去,向其他域的服务器请求资源。服务器端收到一个简单跨域请求后,根据资源权限配置,在响应头中添加 Access-Control-Allow-Origin Header。浏览器收到响应后,查看 Access-Control-Allow-Origin Header,如果当前域已经得到授权,则将结果返回给 JavaScript。否则浏览器忽略此次响应。相较于同源请求,CORS 简单请求会在头信息中额外增加一个 Origin 字段。
预检请求
当 HTTP 请求出现以下两种情况时,浏览器认为是带预检(Preflighted)的跨域请求:
- 除 GET、HEAD 和 POST(only with application/x-www-form-urlencoded, multipart/form-data, text/plain Content-Type)以外的其他 HTTP 方法。
- 请求中出现自定义 HTTP 头部。
非简单请求的 CORS 请求,会在正式通信之前,增加一次 HTTP 查询请求,称为"预检"请求(preflight)。
预检(preflighted)请求会先用 HTTP 的 OPTIONS 方法请求另一个域名资源,确认后续实际(actual)请求能否可安全送出。由于跨域请求可能会携带使用者的信息,所以要先进行预检请求。
腾讯云SCF 腾讯云API 网关实现跨域
当 SCF 绑定 API 网关触发器后,有 2 种方式实现跨域**(建议使用第 1 种方法)**:
- 借助 API 网关的跨域功能
- 云函数中实现跨域逻辑
本文就来介绍下,如果通过这 2 种方式,来实现跨域功能。建议选择第 1 种方式,来实现跨域功能,这样用户就不需要在函数中实现跨域相关的逻辑代码。
借助 API 网关的跨域功能
Step1. 绑定 API 网关触发器
绑定 API 网关触发器:
- 请求方法:GET/POST/HEAD/PUT/DELETE(根据需要进行选择)
目前 API 网关当请求方法为 ANY 时,无法开启跨域功能,所以这里请求方法不能选择 ANY。
Step2. 在 API 网关产品页面,开启 API 的跨域功能
1、在 API网关 产品页面,选择绑定的 API 服务和绑定的 API,编辑 API:
2、在编辑页面开启:支持CORS 选项
3、保存设置后,发布 API
Step3. 测试跨域功能
1、简单跨域请求
代码语言:javascript复制$ curl -v -X GET -H "Origin: http://example.com" http://service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myzijiebao.com/test/corstest
* About to connect() to service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myzijiebao.com port 80 (#0)
* Trying 111.231.97.251...
* Connected to service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myzijiebao.com (111.231.97.251) port 80 (#0)
> GET /test/corstest HTTP/1.1
> User-Agent: curl/7.29.0
> Host: service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myzijiebao.com
> Accept: */*
> Origin: http://example.com
>
< HTTP/1.1 200 OK
< Content-Type: application/json
< Transfer-Encoding: chunked
< Connection: keep-alive
< X-Api-ID: api-bzh0ss12
< X-Service-RateLimit: 5000/5000
< X-Api-RateLimit: unlimited
< Date: Thu, 31 Oct 2019 23:25:46 GMT
< Access-Control-Allow-Origin: http://example.com
< Access-Control-Allow-Credentials: true
< Access-Control-Expose-Headers: X-Api-ID,X-Service-RateLimit,X-UsagePlan-RateLimit,X-UsagePlan-Quota,Cache-Control,Connection,Content-Disposition,Date,Keep-Alive,Pragma,Via,Accept,Accept-Charset,Accept-Encoding,Accept-Language,Authorization,Cookie,Expect,From,Host,If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since,Range,Origin,Referer,User-Agent,X-Forwarded-For,X-Forwarded-Host,X-Forwarded-Proto,Accept-Range,Age,Content-Range,Content-Security-Policy,ETag,Expires,Last-Modified,Location,Server,Set-Cookie,Trailer,Transfer-Encoding,Vary,Allow,Content-Encoding,Content-Language,Content-Length,Content-Location,Content-Type
<
* Connection #0 to host service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myzijiebao.com left intact
cors template function run successfully
2、预检跨域请求
代码语言:javascript复制$ curl -v -X OPTIONS -H "Access-Control-Request-Method: GET" http://service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myzijiebao.com/test/corstest
* About to connect() to service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myzijiebao.com port 80 (#0)
* Trying 111.231.97.251...
* Connected to service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myzijiebao.com (111.231.97.251) port 80 (#0)
> OPTIONS /test/corstest HTTP/1.1
> User-Agent: curl/7.29.0
> Host: service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myzijiebao.com
> Accept: */*
> Access-Control-Request-Method: GET
>
< HTTP/1.1 204 No Content
< Date: Thu, 31 Oct 2019 23:24:53 GMT
< Connection: keep-alive
< X-Api-ID: api-bzh0ss12
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Headers: X-Api-ID,X-Service-RateLimit,X-UsagePlan-RateLimit,X-UsagePlan-Quota,Cache-Control,Connection,Content-Disposition,Date,Keep-Alive,Pragma,Via,Accept,Accept-Charset,Accept-Encoding,Accept-Language,Authorization,Cookie,Expect,From,Host,If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since,Range,Origin,Referer,User-Agent,X-Forwarded-For,X-Forwarded-Host,X-Forwarded-Proto,Accept-Range,Age,Content-Range,Content-Security-Policy,ETag,Expires,Last-Modified,Location,Server,Set-Cookie,Trailer,Transfer-Encoding,Vary,Allow,Content-Encoding,Content-Language,Content-Length,Content-Location,Content-Type
< Access-Control-Allow-Methods: GET,POST,PUT,DELETE,HEAD,OPTIONS,PATCH
< Access-Control-Max-Age: 86400
< Server: apigw/1.0.15
<
* Connection #0 to host service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myzijiebao.com left intact
可以看到,网关均正确返回跨域需要的 Headers。
开启跨域后,OPTIONS 请求不走鉴权逻辑
云函数中实现跨域逻辑
Step1. 创建带跨域逻辑的云函数
创建函数:
- 运行环境: Python2.7
- 选择 空白模板
- 执行方法: index.main_handle
函数代码为:
代码语言:javascript复制# -*- coding: utf-8 -*-
import logging
logger = logging.getLogger()
response = {"isBase64": False, "statusCode": 200, "headers": {}}
def set_simple_cors():
response["headers"]["Access-Control-Allow-Origin"] = "*"
response["headers"]["Access-Control-Allow-Credentials"] = "true"
response["headers"]["Access-Control-Expose-Headers"] = "X-Api-ID,X-Service-RateLimit,X-UsagePlan-RateLimit,X-UsagePlan-Quota,Cache-Control,Connection,Content-Disposition,Date,Keep-Alive,Pragma,Via,Accept,Accept-Charset,Accept-Encoding,Accept-Language,Authorization,Cookie,Expect,From,Host,If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since,Range,Origin,Referer,User-Agent,X-Forwarded-For,X-Forwarded-Host,X-Forwarded-Proto,Accept-Range,Age,Content-Range,Content-Security-Policy,ETag,Expires,Last-Modified,Location,Server,Set-Cookie,Trailer,Transfer-Encoding,Vary,Allow,Content-Encoding,Content-Language,Content-Length,Content-Location,Content-Type"
return response
def set_preflight_cors():
response["headers"]["Access-Control-Allow-Origin"] = "*"
response["headers"]["Access-Control-Allow-Credentials"] = "true"
response["headers"]["Access-Control-Allow-Methods"] = "GET,POST,PUT,DELETE,HEAD,OPTIONS,PATCH"
response["headers"]["Access-Control-Allow-Headers"] = "X-Api-ID,X-Service-RateLimit,X-UsagePlan-RateLimit,X-UsagePlan-Quota,Cache-Control,Connection,Content-Disposition,Date,Keep-Alive,Pragma,Via,Accept,Accept-Charset,Accept-Encoding,Accept-Language,Authorization,Cookie,Expect,From,Host,If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since,Range,Origin,Referer,User-Agent,X-Forwarded-For,X-Forwarded-Host,X-Forwarded-Proto,Accept-Range,Age,Content-Range,Content-Security-Policy,ETag,Expires,Last-Modified,Location,Server,Set-Cookie,Trailer,Transfer-Encoding,Vary,Allow,Content-Encoding,Content-Language,Content-Length,Content-Location,Content-Type"
response["headers"]["Access-Control-Max-Age"] = 86400
return response
def is_simple_cors(httpMethod, headers):
simple_methods = ['HEAD', 'GET', 'POST']
simple_headers = ['accept', 'accept-language', 'content-language', 'last-event-id', 'content-type', 'origin']
allow_content_types = ['application/x-www-form-urlencoded', 'multipart/form-data', 'text/plain']
if httpMethod not in simple_methods:
return False
if httpMethod == "POST" and headers['content-type'] not in allow_content_types:
return False
for header in headers:
if header not in simple_headers:
return False
return True
def is_preflight_cors(httpMethod, headers):
httpPreflightMethod = headers.get('access-control-request-method', '')
if httpMethod == "OPTIONS" and httpPreflightMethod != "":
return True
return False
def main_handler(event, context):
logger.info('start cors template function')
logger.info("event: %s", event)
httpMethod = event['httpMethod']
headers = event['headers']
# 跨域:预检请求
if is_preflight_cors(httpMethod, headers):
logger.info("receive preflight http cors request")
return set_preflight_cors()
# 跨域:简单请求
if is_simple_cors(httpMethod, headers):
logger.info("receive simple http cors request")
set_simple_cors()
# 同源请求,正常完成业务逻辑
logger.info("receive normal http request.")
body = "cors template function run successfully"
response["body"] = body
return response
Step2. 绑定 API 网关触发器
绑定 API 网关触发器:
- 请求方法: ANY
- 开启 启用集成响应
开启集成响应后,返回的参数需要满足集成响应的格式。
参考文档:集成响应与透传响应 - https://cloud.tencent.com/document/product/583/12513#.E9.9B.86.E6.88.90.E5.93.8D.E5.BA.94.E4.B8.8E.E9.80.8F.E4.BC.A0.E5.93.8D.E5.BA.94
Step3. 测试跨域功能
1、简单跨域请求
代码语言:javascript复制$ curl -v -X GET -H "Origin: http://example.com" http://service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myzijiebao.com/test/corstest
* About to connect() to service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myzijiebao.com port 80 (#0)
* Trying 111.231.97.251...
* Connected to service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myzijiebao.com (111.231.97.251) port 80 (#0)
> GET /test/corstest HTTP/1.1
> User-Agent: curl/7.29.0
> Host: service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myzijiebao.com
> Accept: */*
> Origin: http://example.com
>
< HTTP/1.1 200 OK
< Content-Type: application/json
< Transfer-Encoding: chunked
< Connection: keep-alive
< X-Api-ID: api-51u298ne
< X-Service-RateLimit: 5000/5000
< X-Api-RateLimit: unlimited
< Access-Control-Allow-Credentials: true
< Access-Control-Allow-Headers: X-Api-ID,X-Service-RateLimit,X-UsagePlan-RateLimit,X-UsagePlan-Quota,Cache-Control,Connection,Content-Disposition,Date,Keep-Alive,Pragma,Via,Accept,Accept-Charset,Accept-Encoding,Accept-Language,Authorization,Cookie,Expect,From,Host,If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since,Range,Origin,Referer,User-Agent,X-Forwarded-For,X-Forwarded-Host,X-Forwarded-Proto,Accept-Range,Age,Content-Range,Content-Security-Policy,ETag,Expires,Last-Modified,Location,Server,Set-Cookie,Trailer,Transfer-Encoding,Vary,Allow,Content-Encoding,Content-Language,Content-Length,Content-Location,Content-Type
< Access-Control-Allow-Methods: GET,POST,PUT,DELETE,HEAD,OPTIONS,PATCH
< Access-Control-Allow-Origin: *
< Date: Fri, 01 Nov 2019 00:08:50 GMT
<
* Connection #0 to host service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myzijiebao.com left intact
cors template function run successfully
2、 预检跨域请求
代码语言:javascript复制$ curl -v -X OPTIONS -H "Access-Control-Request-Method: GET" http://service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myzijiebao.com/test/corstest
* About to connect() to service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myzijiebao.com port 80 (#0)
* Trying 111.231.97.251...
* Connected to service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myzijiebao.com (111.231.97.251) port 80 (#0)
> OPTIONS /test/corstest HTTP/1.1
> User-Agent: curl/7.29.0
> Host: service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myzijiebao.com
> Accept: */*
> Access-Control-Request-Method: GET
>
< HTTP/1.1 200 OK
< Content-Type: application/json
< Transfer-Encoding: chunked
< Connection: keep-alive
< X-Api-ID: api-51u298ne
< X-Service-RateLimit: 5000/5000
< X-Api-RateLimit: unlimited
< Access-Control-Allow-Credentials: true
< Access-Control-Allow-Headers: X-Api-ID,X-Service-RateLimit,X-UsagePlan-RateLimit,X-UsagePlan-Quota,Cache-Control,Connection,Content-Disposition,Date,Keep-Alive,Pragma,Via,Accept,Accept-Charset,Accept-Encoding,Accept-Language,Authorization,Cookie,Expect,From,Host,If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since,Range,Origin,Referer,User-Agent,X-Forwarded-For,X-Forwarded-Host,X-Forwarded-Proto,Accept-Range,Age,Content-Range,Content-Security-Policy,ETag,Expires,Last-Modified,Location,Server,Set-Cookie,Trailer,Transfer-Encoding,Vary,Allow,Content-Encoding,Content-Language,Content-Length,Content-Location,Content-Type
< Access-Control-Allow-Methods: GET,POST,PUT,DELETE,HEAD,OPTIONS,PATCH
< Access-Control-Allow-Origin: *
< Date: Fri, 01 Nov 2019 00:07:40 GMT
<
* Connection #0 to host service-4mlv1c3l-1253970226.ap-shanghai.apigateway.myzijiebao.com left intact
可以看到,函数均正确返回跨域需要的 Headers。
API 网关后期产品优化
目前 ANY 方法还不支持跨域设置,这个 API 网关后期会考虑支持。