定义
CORS(Cross-Origin Resource Sharing)是一种机制,它使用额外的 HTTP 头来告诉浏览器允许一个网页从另一个域(不同于该网页所在的域)请求资源。这样可以在服务器和客户端之间进行安全的跨域通信
名词解释
Origin(域)
Origin = 协议 + 域名 + 端口
域由三个部分组成【协议(Protocol)、主机名(Host/Domain Name)、端口号(Port Number)】只有当这三个部分完全相同的时候,两个 URL 才被认为是同一个“源”或“域”。例如:
-
Example Domain 和 Example Domain 是不同的域,因为协议不同
-
http://example.com:8080 和 Example Domain 是不同的域,因为端口不同
-
http://sub.example.com 和 Example Domain 是不同的域,因为主机名不同
跨域请求
一个网页通过 JavaScript 发起的请求目标是不同于该网页所在的源(协议、域名、端口)
例如我在百度首页页面去访问我本地的web服务
抓包可以看到浏览器检测到了跨域请求origin头被浏览器加到了请求中,我的web服务是允许这个origin的,所以成功收到了我的服务的响应,状态码为200
同源策略
同源策略(Same-Origin Policy, SOP)是浏览器的一种安全机制,用于防止恶意网站通过脚本对其他网站的内容进行访问。“同源”,是指协议、域名和端口都相同
CORS 工作流程
CORS 通过在 HTTP(s) 请求和响应中使用特定的头部字段来实现跨域资源共享,CORS 分为两种类型的请求处理方式:简单请求和预检请求
-
简单请求:对于某些简单的 HTTP 请求(如GET、POST请求且不包含自定义头部),浏览器会直接发送请求,并在响应中检查 CORS 头部。
-
预检请求:对于复杂请求(如使用PUT、DELETE方法,或包含自定义头部),浏览器会首先发送一个OPTIONS请求,称为预检请求(Preflight Request),以确定服务器是否允许实际请求。
简单请求
-
简单请求是指满足以下条件的 HTTP 请求:
1、使用GET、POST、HEAD方法
2、请求头部仅包含以下字段:Accept、Accept-Language、Content-Language、Content-Type(且值为application/x-www-form-urlencoded、multipart/form-data或text/plain)
-
对于简单请求,浏览器会直接发送请求并在响应中检查以下 CORS 头部:
1、Access-Control-Allow-Origin:指示允许访问资源的源。
2、Access-Control-Allow-Credentials:指示是否允许发送凭据(如Cookies)。
3、Access-Control-Expose-Headers:指示哪些头部可以作为响应的一部分被访问
-
例如
GET /api/data HTTP/1.1
Host: www.yuanjava.com
Origin: https://yuanjava.com
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://yuanjava.com
Content-Type: application/json
{"message": "Hello, CORS!"}
预检请求
-
对于复杂请求,浏览器会首先发送一个 OPTIONS 请求,包含以下头部字段:
Origin:指示请求的源。
Access-Control-Request-Method:指示实际请求将使用的方法。
Access-Control-Request-Headers:指示实际请求将包含的自定义头部。
-
服务器收到预检请求后,会返回一个响应,包含以下头部字段以指示是否允许请求:
Access-Control-Allow-Origin:表明允许访问资源的源,可以是具体的源或通配符 *
Access-Control-Allow-Methods:表明允许的方法,如 GET, POST, PUT, DELETE
Access-Control-Allow-Headers:表明允许的自定义头部
Access-Control-Allow-Credentials:表明是否允许发送凭据(如 Cookies)
Access-Control-Expose-Headers:表明哪些头部可以作为响应的一部分被访问
Access-Control-Max-Age:表明预检请求的结果可以被缓存的时间,单位是秒
-
如果预检请求通过,浏览器会继续发送实际请求
-
例如
OPTIONS /api/data HTTP/1.1
Host: api.yuanjava.com
Origin: https://yuanjava.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://yuanjava.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 3600
PUT /api/data HTTP/1.1
Host: api.yuanjava.com
Origin: https://yuanjava.com
Content-Type: application/json
{"data": "example"}
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://yuanjava.com
Content-Type: application/json
{"message": "Data updated"}
CORS测试
Access-Control-Allow-Origin测试
web服务,用python起了一个web服务,配置了些CORS规则且支持处理预检请求
from flask import Flask, request, jsonify
from flask_cors import CORS
app = Flask(__name__)
# 配置 CORS 策略
CORS(app, resources={
r"/data/*": {
"origins": ["https://www.baidu.com"], # 只允许来自特定源的请求
"methods": ["GET", "POST", "DELETE", "OPTIONS"],
"allow_headers": ["Content-Type"],
"supports_credentials": False,
"max_age": 86400
}
})
@app.route('/data', methods=['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'])
def handle_data():
origin = request.headers.get('Origin')
# 检查 Origin 是否在允许列表中
allowed_origins = ["https://www.baidu.com"]
if origin not in allowed_origins:
response = jsonify({"status": "Access denied", "reason": "Source not allowed"})
return response, 403
# 处理 OPTIONS 请求
if request.method == 'OPTIONS':
response = app.make_default_options_response()
response.headers['Access-Control-Allow-Origin'] = origin
response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE'
response.headers['Access-Control-Allow-Headers'] = 'Content-Type'
return response
# 模拟返回一些数据
data = {"message": "Data successfully retrieved"}
response = jsonify(data)
response.headers['Access-Control-Allow-Origin'] = origin # 设置响应头部为允许的 Origin
return response
if __name__ == '__main__':
app.run(port=5006) # 在端口 5006 上运行
burp测试
origin头为https://www.baidu.com,正常收到服务器响应,状态码200
origin头非https://www.baidu.com,例如端口有变,协议有变,域名有变均无法访问服务
浏览器访问测试
在百度首页发送请求访问我的服务,浏览器识别到跨域,自动加上origin头,因为我的服务上的cors规则允许的origin头为https://www.baiud.com,刚好匹配即正常收到服务器响应
在csdn首页发送请求访问我的服务,可以看到被阻断了,无法访问,因为origin没在服务的允许范围内
Access-Control-Allow-Methods测试
用put方式发送请求,预检请求可以看到服务不允许PUT类型访问,所以访问报错响应405 METHOD NOT ALLOWED