活动公告

系统通知
05-18 21:22
系统通知
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,资源失效请在帖子内回复要求补档,会尽快处理!
10-23 09:31

Flask跨域资源共享处理完全指南 从基础配置到高级应用技巧详解 解决前后端分离开发中的常见问题提升应用安全性

SunJu_FaceMall

3万

主题

2860

科技点

3万

积分

白金月票

碾压王

积分
32872

塔罗立华奏

<font color=白金月票" /> 发表于 2025-9-1 09:10:00 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
引言

在现代Web开发中,前后端分离架构已成为主流。这种架构模式下,前端通常运行在一个域(如http://localhost:3000),而后端API服务则运行在另一个域(如http://api.example.com)。当浏览器中的JavaScript代码尝试访问不同源的资源时,就会遇到跨域资源共享(Cross-Origin Resource Sharing,CORS)的限制。这些限制是由浏览器的同源策略(Same-Origin Policy)强制执行的,旨在保护用户免受恶意网站的攻击。

Flask作为Python中流行的轻量级Web框架,在构建RESTful API时经常需要处理CORS问题。正确配置CORS不仅能够解决前后端分离开发中的常见问题,还能提升应用的安全性。本文将全面介绍Flask中处理CORS的各种方法,从基础配置到高级应用技巧,帮助开发者解决实际开发中的CORS相关问题。

CORS基础

同源策略

同源策略是Web浏览器中的一项重要安全机制,它限制了一个源的文档或脚本如何能与另一个源的资源进行交互。所谓”同源”或”同站点”指的是三个方面的相同:协议(如HTTP、HTTPS)、域名(如example.com)和端口(如80、443)。例如,http://example.com/page1.html和http://example.com/page2.html是同源的,但http://example.com和https://example.com则不是同源的,因为协议不同。

同源策略的主要目的是防止恶意网站通过脚本访问另一个网站的敏感数据。例如,如果没有同源策略,恶意网站可能会通过脚本访问用户已登录的银行网站,从而窃取用户信息。

CORS工作原理

CORS是一种机制,它使用额外的HTTP头来告诉浏览器,让运行在一个源上的Web应用被准许访问来自不同源服务器上的指定资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域HTTP请求。

CORS的工作原理如下:

1. 简单请求:对于某些HTTP请求(如GET、HEAD和POST,且Content-Type为特定值),浏览器会直接发送请求,并在请求中包含Origin头部,表示请求的来源。服务器收到请求后,会检查Origin头部,并决定是否允许该请求。如果允许,服务器会在响应中包含Access-Control-Allow-Origin头部,其值为允许的源。
2. 预检请求:对于某些复杂的请求(如使用PUT或DELETE方法,或者Content-Type为application/json),浏览器会先发送一个”预检请求”(Preflight Request),使用OPTIONS方法询问服务器是否允许实际的请求。服务器收到预检请求后,会返回一个响应,包含各种CORS相关的头部,如Access-Control-Allow-Methods(允许的方法)、Access-Control-Allow-Headers(允许的头部)等。浏览器根据这些响应头部决定是否发送实际的请求。
3. 凭证请求:如果请求需要包含凭证(如Cookie或HTTP认证信息),浏览器会发送带有Credentials头部的请求,服务器需要在响应中包含Access-Control-Allow-Credentials: true头部,并且Access-Control-Allow-Origin不能为通配符*。

简单请求:对于某些HTTP请求(如GET、HEAD和POST,且Content-Type为特定值),浏览器会直接发送请求,并在请求中包含Origin头部,表示请求的来源。服务器收到请求后,会检查Origin头部,并决定是否允许该请求。如果允许,服务器会在响应中包含Access-Control-Allow-Origin头部,其值为允许的源。

预检请求:对于某些复杂的请求(如使用PUT或DELETE方法,或者Content-Type为application/json),浏览器会先发送一个”预检请求”(Preflight Request),使用OPTIONS方法询问服务器是否允许实际的请求。服务器收到预检请求后,会返回一个响应,包含各种CORS相关的头部,如Access-Control-Allow-Methods(允许的方法)、Access-Control-Allow-Headers(允许的头部)等。浏览器根据这些响应头部决定是否发送实际的请求。

凭证请求:如果请求需要包含凭证(如Cookie或HTTP认证信息),浏览器会发送带有Credentials头部的请求,服务器需要在响应中包含Access-Control-Allow-Credentials: true头部,并且Access-Control-Allow-Origin不能为通配符*。

CORS相关HTTP头部

以下是一些常见的CORS相关HTTP头部:

• Origin: 表示请求的来源。
• Access-Control-Allow-Origin: 指定哪些源可以访问资源。可以是具体的源(如http://example.com),也可以是通配符*(表示允许任何源)。
• Access-Control-Allow-Methods: 指定允许的HTTP方法(如GET、POST、PUT、DELETE等)。
• Access-Control-Allow-Headers: 指定允许的请求头部(如Content-Type、Authorization等)。
• Access-Control-Allow-Credentials: 指定是否允许发送凭证(如Cookie或HTTP认证信息)。
• Access-Control-Expose-Headers: 指定哪些响应头部可以被客户端访问。
• Access-Control-Max-Age: 指定预检请求的结果可以被缓存多长时间(以秒为单位)。

Flask-CORS扩展安装与基础配置

在Flask中处理CORS有多种方法,但最简单和推荐的方法是使用Flask-CORS扩展。Flask-CORS是一个Flask扩展,用于处理跨域资源共享(CORS),使跨域AJAX请求成为可能。

安装Flask-CORS

首先,我们需要安装Flask-CORS扩展。可以使用pip进行安装:
  1. pip install flask-cors
复制代码

基础配置

安装完成后,我们可以在Flask应用中配置Flask-CORS。最简单的配置方式是全局启用CORS:
  1. from flask import Flask
  2. from flask_cors import CORS
  3. app = Flask(__name__)
  4. # 全局启用CORS
  5. CORS(app)
  6. @app.route('/')
  7. def hello_world():
  8.     return 'Hello, World!'
  9. if __name__ == '__main__':
  10.     app.run(debug=True)
复制代码

在这个例子中,我们通过CORS(app)全局启用了CORS,这意味着所有的路由都会允许跨域请求。这种配置方式适用于开发环境,但在生产环境中可能需要更精细的控制。

基本CORS配置方法

全局配置

全局配置是最简单的CORS配置方式,适用于所有路由都需要允许跨域请求的情况。我们可以通过CORS(app)来全局启用CORS,也可以通过传递参数来配置全局CORS选项:
  1. from flask import Flask
  2. from flask_cors import CORS
  3. app = Flask(__name__)
  4. # 全局启用CORS,并配置选项
  5. CORS(app, resources={r"/*": {"origins": "*"}})
  6. @app.route('/')
  7. def hello_world():
  8.     return 'Hello, World!'
  9. if __name__ == '__main__':
  10.     app.run(debug=True)
复制代码

在这个例子中,我们通过resources参数指定了哪些路径应用CORS策略。r"/*"表示所有路径,{"origins": "*"}表示允许所有源。我们也可以指定特定的源:
  1. CORS(app, resources={r"/*": {"origins": "http://example.com"}})
复制代码

路由级别配置

如果我们只想为特定的路由启用CORS,可以使用装饰器的方式:
  1. from flask import Flask, jsonify
  2. from flask_cors import cross_origin
  3. app = Flask(__name__)
  4. @app.route('/api/public')
  5. @cross_origin()
  6. def public_api():
  7.     return jsonify(message="This is a public API")
  8. @app.route('/api/private')
  9. def private_api():
  10.     return jsonify(message="This is a private API")
  11. if __name__ == '__main__':
  12.     app.run(debug=True)
复制代码

在这个例子中,只有/api/public路由允许跨域请求,而/api/private路由不允许。我们也可以在装饰器中指定CORS选项:
  1. @app.route('/api/public')
  2. @cross_origin(origins="http://example.com", methods=["GET", "POST"])
  3. def public_api():
  4.     return jsonify(message="This is a public API")
复制代码

蓝图级别配置

如果我们使用Flask的蓝图(Blueprint)来组织应用,可以为整个蓝图配置CORS:
  1. from flask import Flask, Blueprint
  2. from flask_cors import CORS
  3. app = Flask(__name__)
  4. api_v1 = Blueprint('api_v1', __name__, url_prefix='/api/v1')
  5. # 为蓝图配置CORS
  6. CORS(api_v1, origins="http://example.com")
  7. @api_v1.route('/users')
  8. def get_users():
  9.     return jsonify(users=["Alice", "Bob", "Charlie"])
  10. app.register_blueprint(api_v1)
  11. if __name__ == '__main__':
  12.     app.run(debug=True)
复制代码

在这个例子中,我们为api_v1蓝图配置了CORS,只允许来自http://example.com的请求。

高级CORS配置选项

预检请求处理

对于复杂的请求(如使用PUT或DELETE方法,或者Content-Type为application/json),浏览器会先发送一个预检请求(OPTIONS方法)。Flask-CORS会自动处理预检请求,但我们也可以自定义预检请求的处理:
  1. from flask import Flask, request, jsonify
  2. from flask_cors import cross_origin
  3. app = Flask(__name__)
  4. @app.route('/api/data', methods=['GET', 'POST', 'OPTIONS'])
  5. @cross_origin(
  6.     origins="http://example.com",
  7.     methods=["GET", "POST", "OPTIONS"],
  8.     allow_headers=["Content-Type", "Authorization"],
  9.     max_age=86400
  10. )
  11. def handle_data():
  12.     if request.method == 'POST':
  13.         data = request.get_json()
  14.         return jsonify(data=data)
  15.     else:
  16.         return jsonify(message="GET request received")
  17. if __name__ == '__main__':
  18.     app.run(debug=True)
复制代码

在这个例子中,我们明确指定了允许的方法、头部和预检请求的缓存时间。max_age=86400表示预检请求的结果可以被缓存86400秒(24小时)。

动态源配置

在某些情况下,我们可能需要根据请求的动态信息来决定是否允许跨域请求。Flask-CORS支持动态源配置:
  1. from flask import Flask, request, jsonify
  2. from flask_cors import CORS
  3. app = Flask(__name__)
  4. def dynamic_origin(origin):
  5.     # 这里可以根据origin的值动态决定是否允许
  6.     allowed_origins = ["http://example.com", "http://test.com"]
  7.     return origin in allowed_origins
  8. CORS(app, resources={r"/api/*": {"origins": dynamic_origin}})
  9. @app.route('/api/data')
  10. def get_data():
  11.     return jsonify(data=["item1", "item2", "item3"])
  12. if __name__ == '__main__':
  13.     app.run(debug=True)
复制代码

在这个例子中,我们定义了一个dynamic_origin函数,它根据请求的Origin头部动态决定是否允许跨域请求。

凭证处理

如果请求需要包含凭证(如Cookie或HTTP认证信息),我们需要在服务器端明确允许:
  1. from flask import Flask, jsonify, session
  2. from flask_cors import cross_origin
  3. app = Flask(__name__)
  4. app.secret_key = 'your-secret-key'
  5. @app.route('/api/login', methods=['POST'])
  6. @cross_origin(supports_credentials=True)
  7. def login():
  8.     session['user_id'] = '123'
  9.     return jsonify(message="Logged in successfully")
  10. @app.route('/api/profile')
  11. @cross_origin(
  12.     origins="http://example.com",
  13.     supports_credentials=True,
  14.     allow_headers=["Content-Type", "Authorization"]
  15. )
  16. def profile():
  17.     if 'user_id' in session:
  18.         return jsonify(user_id=session['user_id'])
  19.     else:
  20.         return jsonify(error="Not logged in"), 401
  21. if __name__ == '__main__':
  22.     app.run(debug=True)
复制代码

在这个例子中,我们通过supports_credentials=True允许发送凭证。注意,当允许凭证时,Access-Control-Allow-Origin不能为通配符*,必须指定具体的源。

自定义头部处理

如果需要自定义响应头部,可以使用expose_headers参数:
  1. from flask import Flask, jsonify
  2. from flask_cors import cross_origin
  3. app = Flask(__name__)
  4. @app.route('/api/data')
  5. @cross_origin(
  6.     origins="http://example.com",
  7.     expose_headers=["X-Custom-Header", "X-Another-Header"]
  8. )
  9. def get_data():
  10.     response = jsonify(data=["item1", "item2", "item3"])
  11.     response.headers['X-Custom-Header'] = 'Custom Value'
  12.     response.headers['X-Another-Header'] = 'Another Value'
  13.     return response
  14. if __name__ == '__main__':
  15.     app.run(debug=True)
复制代码

在这个例子中,我们通过expose_headers参数指定了哪些自定义响应头部可以被客户端访问。

安全性考虑与最佳实践

限制允许的源

在生产环境中,应该避免使用通配符*作为Access-Control-Allow-Origin的值,因为这会允许任何网站访问你的API。相反,应该明确指定允许的源:
  1. from flask import Flask
  2. from flask_cors import CORS
  3. app = Flask(__name__)
  4. # 生产环境中的推荐配置
  5. allowed_origins = [
  6.     "https://your-frontend-domain.com",
  7.     "https://www.your-frontend-domain.com"
  8. ]
  9. CORS(app, resources={r"/*": {"origins": allowed_origins}})
  10. # 或者使用正则表达式匹配
  11. # CORS(app, resources={r"/*": {"origins": r"https://.*\.your-frontend-domain\.com"}})
  12. if __name__ == '__main__':
  13.     app.run(debug=True)
复制代码

限制允许的方法和头部

同样,应该限制允许的HTTP方法和请求头部,只允许实际需要的方法和头部:
  1. from flask import Flask
  2. from flask_cors import CORS
  3. app = Flask(__name__)
  4. CORS(app, resources={
  5.     r"/api/public/*": {
  6.         "origins": ["https://your-frontend-domain.com"],
  7.         "methods": ["GET", "POST"],
  8.         "allow_headers": ["Content-Type", "Authorization"]
  9.     },
  10.     r"/api/admin/*": {
  11.         "origins": ["https://admin.your-frontend-domain.com"],
  12.         "methods": ["GET", "POST", "PUT", "DELETE"],
  13.         "allow_headers": ["Content-Type", "Authorization", "X-Admin-Token"]
  14.     }
  15. })
  16. if __name__ == '__main__':
  17.     app.run(debug=True)
复制代码

谨慎使用凭证支持

启用凭证支持(supports_credentials=True)会增加安全风险,因为它允许浏览器发送敏感信息(如Cookie)。只有在确实需要时才启用凭证支持,并且确保只允许可信的源:
  1. from flask import Flask
  2. from flask_cors import CORS
  3. app = Flask(__name__)
  4. # 只对需要认证的路由启用凭证支持
  5. CORS(app, resources={
  6.     r"/api/auth/*": {
  7.         "origins": ["https://your-frontend-domain.com"],
  8.         "supports_credentials": True,
  9.         "methods": ["GET", "POST"],
  10.         "allow_headers": ["Content-Type", "Authorization", "X-CSRF-Token"]
  11.     }
  12. })
  13. if __name__ == '__main__':
  14.     app.run(debug=True)
复制代码

使用HTTPS

在生产环境中,应该使用HTTPS来保护数据传输的安全性。HTTPS可以防止中间人攻击,确保数据在传输过程中不被篡改或窃取。同时,某些CORS功能(如发送凭证)要求使用HTTPS:
  1. from flask import Flask
  2. from flask_cors import CORS
  3. app = Flask(__name__)
  4. # 生产环境中的HTTPS配置
  5. app.config['SESSION_COOKIE_SECURE'] = True  # 只通过HTTPS发送Cookie
  6. app.config['SESSION_COOKIE_HTTPONLY'] = True  # 防止JavaScript访问Cookie
  7. app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'  # 防止CSRF攻击
  8. CORS(app, resources={
  9.     r"/*": {
  10.         "origins": ["https://your-frontend-domain.com"],
  11.         "supports_credentials": True
  12.     }
  13. })
  14. if __name__ == '__main__':
  15.     # 在生产环境中,应该使用生产级的WSGI服务器(如Gunicorn)和反向代理(如Nginx)
  16.     app.run(ssl_context='adhoc', debug=True)  # 仅用于开发测试
复制代码

实施CSRF保护

跨域请求可能会增加CSRF(Cross-Site Request Forgery)攻击的风险。为了防止CSRF攻击,应该实施CSRF保护机制:
  1. from flask import Flask, session, request, jsonify
  2. from flask_cors import cross_origin
  3. import secrets
  4. app = Flask(__name__)
  5. app.secret_key = 'your-secret-key'
  6. @app.route('/api/csrf-token', methods=['GET'])
  7. @cross_origin(origins=["https://your-frontend-domain.com"])
  8. def get_csrf_token():
  9.     if 'csrf_token' not in session:
  10.         session['csrf_token'] = secrets.token_hex(16)
  11.     return jsonify(csrf_token=session['csrf_token'])
  12. @app.route('/api/data', methods=['POST'])
  13. @cross_origin(
  14.     origins=["https://your-frontend-domain.com"],
  15.     supports_credentials=True,
  16.     allow_headers=["Content-Type", "X-CSRF-Token"]
  17. )
  18. def post_data():
  19.     csrf_token = request.headers.get('X-CSRF-Token')
  20.     if 'csrf_token' not in session or session['csrf_token'] != csrf_token:
  21.         return jsonify(error="Invalid CSRF token"), 403
  22.    
  23.     data = request.get_json()
  24.     # 处理数据...
  25.     return jsonify(success=True)
  26. if __name__ == '__main__':
  27.     app.run(debug=True)
复制代码

在这个例子中,我们实现了一个简单的CSRF保护机制。客户端首先需要获取一个CSRF令牌,然后在发送请求时在请求头部中包含这个令牌。服务器会验证令牌的有效性,只有令牌有效的请求才会被处理。

常见问题与解决方案

问题1:预检请求失败

现象:浏览器控制台显示预检请求失败,错误信息类似于”Response to preflight request doesn’t pass access control check”。

原因:这通常是因为服务器没有正确处理OPTIONS请求,或者没有返回正确的CORS头部。

解决方案:

1. 确保服务器正确处理OPTIONS请求。Flask-CORS会自动处理OPTIONS请求,但如果使用了自定义的CORS配置,可能需要明确允许OPTIONS方法:
  1. from flask import Flask, request, jsonify
  2. from flask_cors import cross_origin
  3. app = Flask(__name__)
  4. @app.route('/api/data', methods=['GET', 'POST', 'OPTIONS'])
  5. @cross_origin(
  6.     origins=["https://your-frontend-domain.com"],
  7.     methods=["GET", "POST", "OPTIONS"],
  8.     allow_headers=["Content-Type", "Authorization"]
  9. )
  10. def handle_data():
  11.     if request.method == 'POST':
  12.         data = request.get_json()
  13.         return jsonify(data=data)
  14.     else:
  15.         return jsonify(message="GET request received")
  16. if __name__ == '__main__':
  17.     app.run(debug=True)
复制代码

1. 确保服务器返回正确的CORS头部。可以使用浏览器开发者工具检查响应头部,确保包含以下头部:
  1. Access-Control-Allow-Origin: https://your-frontend-domain.com
  2. Access-Control-Allow-Methods: GET, POST, OPTIONS
  3. Access-Control-Allow-Headers: Content-Type, Authorization
复制代码

问题2:凭证请求失败

现象:浏览器控制台显示凭证请求失败,错误信息类似于”Credentials mode is ‘include’, but the ‘Access-Control-Allow-Credentials’ header is ““。

原因:这通常是因为服务器没有设置Access-Control-Allow-Credentials头部为true,或者设置了Access-Control-Allow-Origin为通配符*。

解决方案:

1. 确保服务器设置Access-Control-Allow-Credentials头部为true:
  1. from flask import Flask, jsonify, session
  2. from flask_cors import cross_origin
  3. app = Flask(__name__)
  4. app.secret_key = 'your-secret-key'
  5. @app.route('/api/data')
  6. @cross_origin(
  7.     origins=["https://your-frontend-domain.com"],
  8.     supports_credentials=True
  9. )
  10. def get_data():
  11.     if 'user_id' in session:
  12.         return jsonify(user_id=session['user_id'])
  13.     else:
  14.         return jsonify(error="Not logged in"), 401
  15. if __name__ == '__main__':
  16.     app.run(debug=True)
复制代码

1. 确保不使用通配符*作为Access-Control-Allow-Origin的值,而是指定具体的源:
  1. # 错误的配置
  2. @cross_origin(origins="*", supports_credentials=True)
  3. # 正确的配置
  4. @cross_origin(origins=["https://your-frontend-domain.com"], supports_credentials=True)
复制代码

问题3:自定义响应头部无法访问

现象:浏览器无法访问自定义的响应头部,错误信息类似于”Cannot access custom header”。

原因:这通常是因为服务器没有设置Access-Control-Expose-Headers头部,或者没有包含需要访问的自定义头部。

解决方案:

1. 确保服务器设置Access-Control-Expose-Headers头部,并包含需要访问的自定义头部:
  1. from flask import Flask, jsonify
  2. from flask_cors import cross_origin
  3. app = Flask(__name__)
  4. @app.route('/api/data')
  5. @cross_origin(
  6.     origins=["https://your-frontend-domain.com"],
  7.     expose_headers=["X-Custom-Header", "X-Another-Header"]
  8. )
  9. def get_data():
  10.     response = jsonify(data=["item1", "item2", "item3"])
  11.     response.headers['X-Custom-Header'] = 'Custom Value'
  12.     response.headers['X-Another-Header'] = 'Another Value'
  13.     return response
  14. if __name__ == '__main__':
  15.     app.run(debug=True)
复制代码

问题4:CORS配置在生产环境中不工作

现象:CORS配置在开发环境中工作正常,但在生产环境中不工作。

原因:这通常是因为生产环境中的反向代理(如Nginx)没有正确配置CORS头部,或者缓存了错误的CORS头部。

解决方案:

1. 确保反向代理正确配置CORS头部。例如,在Nginx中:
  1. server {
  2.     listen 80;
  3.     server_name api.example.com;
  4.     location / {
  5.         # 如果Flask应用已经处理了CORS,可以添加以下头部
  6.         add_header 'Access-Control-Allow-Origin' 'https://your-frontend-domain.com';
  7.         add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
  8.         add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
  9.         
  10.         # 处理预检请求
  11.         if ($request_method = 'OPTIONS') {
  12.             add_header 'Access-Control-Allow-Origin' 'https://your-frontend-domain.com';
  13.             add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
  14.             add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
  15.             add_header 'Access-Control-Max-Age' 1728000;
  16.             add_header 'Content-Type' 'text/plain; charset=utf-8';
  17.             add_header 'Content-Length' 0;
  18.             return 204;
  19.         }
  20.         proxy_pass http://127.0.0.1:5000;
  21.         proxy_set_header Host $host;
  22.         proxy_set_header X-Real-IP $remote_addr;
  23.         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  24.     }
  25. }
复制代码

1. 确保缓存不会干扰CORS头部。可以在响应中添加Cache-Control头部来控制缓存:
  1. from flask import Flask, jsonify
  2. from flask_cors import cross_origin
  3. app = Flask(__name__)
  4. @app.route('/api/data')
  5. @cross_origin(
  6.     origins=["https://your-frontend-domain.com"]
  7. )
  8. def get_data():
  9.     response = jsonify(data=["item1", "item2", "item3"])
  10.     response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
  11.     response.headers['Pragma'] = 'no-cache'
  12.     response.headers['Expires'] = '0'
  13.     return response
  14. if __name__ == '__main__':
  15.     app.run(debug=True)
复制代码

实际应用案例

案例1:构建一个支持CORS的RESTful API

在这个案例中,我们将构建一个简单的RESTful API,支持CORS,并实现基本的CRUD操作。
  1. from flask import Flask, request, jsonify
  2. from flask_cors import CORS
  3. import uuid
  4. app = Flask(__name__)
  5. # 配置CORS
  6. CORS(app, resources={
  7.     r"/api/*": {
  8.         "origins": ["http://localhost:3000", "https://your-frontend-domain.com"],
  9.         "methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
  10.         "allow_headers": ["Content-Type", "Authorization"]
  11.     }
  12. })
  13. # 模拟数据库
  14. items = {
  15.     "1": {"name": "Item 1", "description": "Description 1"},
  16.     "2": {"name": "Item 2", "description": "Description 2"},
  17.     "3": {"name": "Item 3", "description": "Description 3"}
  18. }
  19. # 获取所有项目
  20. @app.route('/api/items', methods=['GET'])
  21. def get_items():
  22.     return jsonify(items)
  23. # 获取单个项目
  24. @app.route('/api/items/<item_id>', methods=['GET'])
  25. def get_item(item_id):
  26.     if item_id in items:
  27.         return jsonify(items[item_id])
  28.     else:
  29.         return jsonify({"error": "Item not found"}), 404
  30. # 创建新项目
  31. @app.route('/api/items', methods=['POST'])
  32. def create_item():
  33.     data = request.get_json()
  34.     if not data or 'name' not in data:
  35.         return jsonify({"error": "Invalid data"}), 400
  36.    
  37.     item_id = str(uuid.uuid4())
  38.     items[item_id] = {
  39.         "name": data['name'],
  40.         "description": data.get('description', '')
  41.     }
  42.    
  43.     return jsonify({"id": item_id, **items[item_id]}), 201
  44. # 更新项目
  45. @app.route('/api/items/<item_id>', methods=['PUT'])
  46. def update_item(item_id):
  47.     if item_id not in items:
  48.         return jsonify({"error": "Item not found"}), 404
  49.    
  50.     data = request.get_json()
  51.     if not data:
  52.         return jsonify({"error": "Invalid data"}), 400
  53.    
  54.     if 'name' in data:
  55.         items[item_id]['name'] = data['name']
  56.     if 'description' in data:
  57.         items[item_id]['description'] = data['description']
  58.    
  59.     return jsonify(items[item_id])
  60. # 删除项目
  61. @app.route('/api/items/<item_id>', methods=['DELETE'])
  62. def delete_item(item_id):
  63.     if item_id not in items:
  64.         return jsonify({"error": "Item not found"}), 404
  65.    
  66.     del items[item_id]
  67.     return jsonify({"result": "Item deleted"})
  68. if __name__ == '__main__':
  69.     app.run(debug=True)
复制代码

在这个例子中,我们构建了一个简单的RESTful API,支持CORS,并实现了基本的CRUD操作。我们使用Flask-CORS扩展来处理CORS,配置了允许的源、方法和头部。

案例2:构建一个需要认证的API

在这个案例中,我们将构建一个需要认证的API,支持CORS,并使用JWT进行认证。
  1. from flask import Flask, request, jsonify
  2. from flask_cors import cross_origin
  3. import jwt
  4. import datetime
  5. from functools import wraps
  6. app = Flask(__name__)
  7. app.config['SECRET_KEY'] = 'your-secret-key'
  8. # 模拟数据库
  9. users = {
  10.     "user1": {"password": "password1", "role": "user"},
  11.     "admin": {"password": "adminpass", "role": "admin"}
  12. }
  13. # 认证装饰器
  14. def token_required(f):
  15.     @wraps(f)
  16.     def decorated(*args, **kwargs):
  17.         token = request.headers.get('Authorization')
  18.         
  19.         if not token:
  20.             return jsonify({"error": "Token is missing"}), 401
  21.         
  22.         try:
  23.             # 去掉"Bearer "前缀
  24.             token = token.split(" ")[1]
  25.             data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=["HS256"])
  26.             current_user = users.get(data['username'])
  27.             if not current_user:
  28.                 return jsonify({"error": "Invalid token"}), 401
  29.         except:
  30.             return jsonify({"error": "Invalid token"}), 401
  31.         
  32.         return f(current_user, *args, **kwargs)
  33.    
  34.     return decorated
  35. # 登录路由
  36. @app.route('/api/login', methods=['POST'])
  37. @cross_origin(
  38.     origins=["http://localhost:3000", "https://your-frontend-domain.com"],
  39.     allow_headers=["Content-Type"]
  40. )
  41. def login():
  42.     auth = request.get_json()
  43.    
  44.     if not auth or not auth.get('username') or not auth.get('password'):
  45.         return jsonify({"error": "Could not verify"}), 401
  46.    
  47.     user = users.get(auth['username'])
  48.    
  49.     if not user or user['password'] != auth['password']:
  50.         return jsonify({"error": "Invalid credentials"}), 401
  51.    
  52.     token = jwt.encode({
  53.         'username': auth['username'],
  54.         'role': user['role'],
  55.         'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1)
  56.     }, app.config['SECRET_KEY'])
  57.    
  58.     return jsonify({"token": token})
  59. # 受保护的路由
  60. @app.route('/api/protected', methods=['GET'])
  61. @cross_origin(
  62.     origins=["http://localhost:3000", "https://your-frontend-domain.com"],
  63.     allow_headers=["Authorization"]
  64. )
  65. @token_required
  66. def protected(current_user):
  67.     return jsonify({"message": f"Hello, {current_user['role']}!"})
  68. # 管理员路由
  69. @app.route('/api/admin', methods=['GET'])
  70. @cross_origin(
  71.     origins=["http://localhost:3000", "https://your-frontend-domain.com"],
  72.     allow_headers=["Authorization"]
  73. )
  74. @token_required
  75. def admin(current_user):
  76.     if current_user['role'] != 'admin':
  77.         return jsonify({"error": "Admin access required"}), 403
  78.    
  79.     return jsonify({"message": "Welcome, admin!"})
  80. if __name__ == '__main__':
  81.     app.run(debug=True)
复制代码

在这个例子中,我们构建了一个需要认证的API,支持CORS,并使用JWT进行认证。我们实现了登录功能,以及两个受保护的路由:一个需要任何用户认证,另一个需要管理员权限。我们使用装饰器来处理认证逻辑,并使用Flask-CORS来处理CORS。

案例3:构建一个支持文件上传的API

在这个案例中,我们将构建一个支持文件上传的API,支持CORS,并处理文件上传。
  1. import os
  2. from flask import Flask, request, jsonify, send_from_directory
  3. from flask_cors import cross_origin
  4. from werkzeug.utils import secure_filename
  5. app = Flask(__name__)
  6. app.config['UPLOAD_FOLDER'] = 'uploads'
  7. app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024  # 16MB max file size
  8. # 确保上传目录存在
  9. os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
  10. # 允许的文件扩展名
  11. ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'}
  12. def allowed_file(filename):
  13.     return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
  14. # 文件上传路由
  15. @app.route('/api/upload', methods=['POST'])
  16. @cross_origin(
  17.     origins=["http://localhost:3000", "https://your-frontend-domain.com"],
  18.     allow_headers=["Content-Type", "Authorization"]
  19. )
  20. def upload_file():
  21.     if 'file' not in request.files:
  22.         return jsonify({"error": "No file part"}), 400
  23.    
  24.     file = request.files['file']
  25.    
  26.     if file.filename == '':
  27.         return jsonify({"error": "No selected file"}), 400
  28.    
  29.     if file and allowed_file(file.filename):
  30.         filename = secure_filename(file.filename)
  31.         file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
  32.         return jsonify({"message": "File uploaded successfully", "filename": filename})
  33.     else:
  34.         return jsonify({"error": "File type not allowed"}), 400
  35. # 文件下载路由
  36. @app.route('/api/download/<filename>', methods=['GET'])
  37. @cross_origin(
  38.     origins=["http://localhost:3000", "https://your-frontend-domain.com"]
  39. )
  40. def download_file(filename):
  41.     return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
  42. # 获取所有文件列表
  43. @app.route('/api/files', methods=['GET'])
  44. @cross_origin(
  45.     origins=["http://localhost:3000", "https://your-frontend-domain.com"]
  46. )
  47. def list_files():
  48.     files = []
  49.     for filename in os.listdir(app.config['UPLOAD_FOLDER']):
  50.         if allowed_file(filename):
  51.             files.append(filename)
  52.     return jsonify(files=files)
  53. if __name__ == '__main__':
  54.     app.run(debug=True)
复制代码

在这个例子中,我们构建了一个支持文件上传的API,支持CORS,并实现了文件上传、下载和列表功能。我们使用Flask-CORS来处理CORS,并实现了文件类型检查和文件名安全处理。

总结

在本文中,我们全面介绍了Flask中处理跨域资源共享(CORS)的各种方法,从基础配置到高级应用技巧。我们首先了解了CORS的基本概念和工作原理,然后介绍了如何使用Flask-CORS扩展来处理CORS,包括全局配置、路由级别配置和蓝图级别配置。我们还探讨了高级CORS配置选项,如预检请求处理、动态源配置、凭证处理和自定义头部处理。

在安全性考虑与最佳实践部分,我们讨论了如何限制允许的源、方法和头部,谨慎使用凭证支持,使用HTTPS,以及实施CSRF保护。在常见问题与解决方案部分,我们解决了一些常见的CORS问题,如预检请求失败、凭证请求失败、自定义响应头部无法访问以及CORS配置在生产环境中不工作。

最后,我们提供了三个实际应用案例,展示了如何构建支持CORS的RESTful API、需要认证的API以及支持文件上传的API。这些案例涵盖了实际开发中的常见场景,可以帮助开发者更好地理解和应用CORS配置。

正确配置CORS不仅能够解决前后端分离开发中的常见问题,还能提升应用的安全性。通过遵循本文中的最佳实践,开发者可以构建既安全又灵活的Web应用,满足现代Web开发的需求。
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则