当前位置: 首页 > news >正文

一文掌握Flask:从基础使用到高级应用

在这里插入图片描述

文章目录

    • 1 Flask框架概述
      • 1.1 什么是Flask?
      • 1.2 Flask的核心特性
      • 1.3 Flask与Django的对比
      • 1.4 Flask的适用场景
    • 2 Flask基本使用
      • 2.1 安装与环境配置
        • 2.1.1 创建虚拟环境
        • 2.1.2 安装Flask
        • 2.1.3 验证安装
      • 2.2 基本程序结构
        • 2.2.1 初始化应用实例
        • 2.2.2 定义路由和视图函数
        • 2.2.3 启动开发服务器
      • 2.3 核心组件详解
        • 2.3.1 路由系统
        • 2.3.2 请求对象
        • 2.3.3 响应对象
        • 2.3.4 模板渲染
        • 2.3.5 静态文件处理
      • 2.4 扩展Flask功能
    • 3 Flask项目配置
      • 3.1 配置基础
        • 3.1.1 直接设置配置值
        • 3.1.2 常用内置配置变量
      • 3.2 配置方法
        • 3.2.1 从Python文件加载
        • 3.2.2 从对象加载
        • 3.2.3 从环境变量加载
        • 3.2.4 多重配置加载策略
      • 3.3 大型项目配置
        • 3.3.1 项目结构
        • 3.3.2 应用工厂模式
        • 3.3.3 蓝图(Blueprint)使用
      • 3.4 扩展配置
        • 3.4.1 数据库配置
        • 3.4.2 日志配置
      • 3.5 部署配置
        • 3.5.1 使用Gunicorn部署
        • 3.5.2 使用Nginx + uWSGI部署
        • 3.5.3 使用Supervisor管理进程
    • 4 接口鉴权
      • 4.1 认证与授权基础
        • 4.1.1 Session-based认证
        • 4.1.2 Token-based认证
      • 4.2 Session认证实现
        • 4.2.1 基本Session配置
        • 4.2.2 登录和会话管理
        • 4.2.3 使用Redis存储Session
      • 4.3 Token认证(JWT)实现
        • 4.3.1 JWT基础概念
        • 4.3.2 安装和配置JWT扩展
        • 4.3.3 实现JWT认证
        • 4.3.4 高级JWT特性
      • 4.4 基于角色的访问控制(RBAC)
        • 4.4.1 实现角色系统
        • 4.4.2 基于角色的权限装饰器
        • 4.4.3 组织级别的权限控制
      • 4.5 OAuth2和第三方认证
        • 4.5.1 使用Authlib实现OAuth2
        • 4.5.2 使用Logto进行API保护
    • 5 自定义中间件
      • 5.1 中间件概念与原理
        • 5.1.1 Flask中间件工作原理
      • 5.2 内置请求钩子
        • 5.2.1 before_request
        • 5.2.2 after_request
        • 5.2.3 teardown_request
        • 5.2.4 错误处理
      • 5.3 自定义WSGI中间件
        • 5.3.1 基本WSGI中间件结构
        • 5.3.2 认证中间件示例
      • 5.4 实用中间件实现
        • 5.4.1 日志记录中间件
        • 5.4.2 跨域中间件(CORS)
        • 5.4.3 速率限制中间件
      • 5.5 中间件最佳实践
        • 5.5.1 中间件执行顺序
        • 5.5.2 中间件组织与管理
        • 5.5.3 性能考虑
    • 6 总结
      • 6.1 关键要点回顾
      • 6.2 最佳实践建议
      • 6.3 进一步学习方向

在这里插入图片描述

1 Flask框架概述

1.1 什么是Flask?

Flask是一个轻量级的Python Web框架,它基于Werkzeug WSGI工具箱和Jinja2模板引擎构建。Flask被设计为可扩展灵活的框架,它不强制要求特定的项目结构或依赖组件,这使得开发者可以根据项目需求灵活选择组件和架构。Flask的"微框架"并不意味着功能欠缺,而是指其核心保持简单且易于扩展,允许开发者自由选择数据库、认证方式等其他组件。

Flask的核心哲学是为开发者提供基础构建块,同时给予他们足够的自由度来构建符合特定需求的应用程序。与Django等"全能型"框架不同,Flask遵循"按需配置"的原则,这使得它成为构建从小型简单应用到大型复杂系统的理想选择。

1.2 Flask的核心特性

Flask具有以下几个显著特性:

  • 轻量级且简单:Flask代码库小巧,API简洁易懂,学习曲线平缓,让开发者可以快速上手并构建应用。
  • 灵活性高:不强制使用特定的项目结构或库,开发者可以根据需要选择适合的组件和架构。
  • 扩展性强:拥有丰富的扩展生态系统,可以轻松添加数据库集成、表单验证、认证等功能。
  • Jinja2模板引擎:内置强大的模板引擎,支持模板继承、自动HTML转义等特性。
  • 开发服务器和调试器:内置开发服务器和交互式调试器,便于开发和调试。
  • RESTful请求分发:支持RESTful风格的请求处理,便于构建Web API。
  • 单元测试支持:提供良好的测试客户端和支持,便于编写和运行单元测试。
  • Cookies支持:支持客户端会话管理,包括安全的cookie支持。

1.3 Flask与Django的对比

特性FlaskDjango
设计哲学微框架,简单核心,高度可扩展全能型框架,内置大量功能
学习曲线平缓,易于入门较陡峭,需要学习更多概念
灵活性高,可以自由选择组件较低,遵循"约定优于配置"
数据库支持需要通过扩展添加内置ORM和支持多种数据库
管理后台需要通过扩展添加内置功能强大的管理后台
模板引擎Jinja2自研模板系统
适用场景小型到大型应用,API服务内容管理系统,大型Web应用

1.4 Flask的适用场景

Flask适用于多种Web开发场景:

  • RESTful API开发:Flask的轻量级特性和灵活性使其成为构建Web API的理想选择。
  • 微服务架构:Flapp的简单性使其适合作为微服务架构中的单个服务组件。
  • 原型开发:快速构建概念验证或产品原型。
  • 小型Web应用:构建不需要Django全部功能的小型应用。
  • 大型应用:通过适当的架构设计和扩展,Flask也能支持大型复杂应用。
  • 嵌入式Web服务:为设备或应用程序提供嵌入式Web界面。

2 Flask基本使用

2.1 安装与环境配置

要使用Flask,首先需要安装Python和pip(Python包管理器)。推荐使用Python 3.6或更高版本。

2.1.1 创建虚拟环境

使用虚拟环境是Python开发的最佳实践,它可以隔离项目依赖,避免包冲突。创建虚拟环境有多种方式:

# 使用venv模块(Python 3.3+)
python -m venv my_flask_env# 使用virtualenv(需要先安装)
pip install virtualenv
virtualenv my_flask_env# 使用virtualenvwrapper(更高级的虚拟环境管理)
pip install virtualenvwrapper
mkvirtualenv my_flask_env

激活虚拟环境的方法取决于操作系统:

# Windows
my_flask_env\Scripts\activate# Linux/Mac
source my_flask_env/bin/activate
2.1.2 安装Flask

激活虚拟环境后,使用pip安装Flask:

pip install flask

安装Flask时会自动安装以下依赖包:

  • Werkzeug:WSGI工具箱,处理Web服务器网关接口规范
  • Jinja2:模板引擎,用于渲染HTML页面
  • itsdangerous:安全地签名数据,用于保护cookie内容
  • click:命令行界面创建工具
2.1.3 验证安装

创建一个简单的Flask应用来验证安装是否成功:

# app.py
from flask import Flaskapp = Flask(__name__)@app.route('/')
def hello():return 'Hello, Flask!'if __name__ == '__main__':app.run()

运行应用:

python app.py

在浏览器中访问http://localhost:5000,应该能看到"Hello, Flask!"消息。

2.2 基本程序结构

一个基本的Flask应用包含以下组件:

2.2.1 初始化应用实例

每个Flask应用都必须创建一个Flask实例:

from flask import Flask
app = Flask(__name__)

__name__参数帮助Flask确定应用的位置,以便查找资源文件。

2.2.2 定义路由和视图函数

路由将URL映射到Python函数(视图函数),处理请求并返回响应:

@app.route('/')
def index():return 'Home Page'@app.route('/user/<username>')
def show_user(username):return f'User: {username}'

路由可以包含变量部分,用<variable_name>表示,这些变量会作为参数传递给视图函数。

2.2.3 启动开发服务器

Flask包含一个内置开发服务器,适合开发和测试:

if __name__ == '__main__':app.run(debug=True, host='0.0.0.0', port=5000)

参数说明:

  • debug=True:启用调试模式,代码更改后自动重载,并提供详细错误页面
  • host='0.0.0.0':使服务器对外可访问
  • port=5000:指定端口号(默认5000)

2.3 核心组件详解

2.3.1 路由系统

Flask的路由系统非常灵活,支持多种路由定义方式:

# 基本路由
@app.route('/hello')
def hello():return 'Hello, World!'# 带变量的路由
@app.route('/user/<username>')
def show_user(username):return f'User: {username}'# 指定变量类型
@app.route('/post/<int:post_id>')
def show_post(post_id):return f'Post: {post_id}'# 多HTTP方法路由
@app.route('/login', methods=['GET', 'POST'])
def login():if request.method == 'POST':return do_login()else:return show_login_form()

支持的路由变量类型:

  • string:默认类型,接受任何不包含斜杠的文本
  • int:接受正整数
  • float:接受正浮点数
  • path:类似string,但接受斜杠
  • uuid:接受UUID字符串
2.3.2 请求对象

Flask的request对象包含所有 incoming 请求数据:

from flask import request@app.route('/login', methods=['POST'])
def login():# 获取表单数据username = request.form['username']password = request.form['password']# 获取查询参数page = request.args.get('page', 1, type=int)# 获取JSON数据if request.is_json:data = request.get_json()return 'Login processed'
2.3.3 响应对象

视图函数可以返回多种类型的响应:

from flask import make_response, jsonify@app.route('/')
def index():# 返回字符串return 'Hello, World!'@app.route('/json')
def json_data():# 返回JSON数据return jsonify({'name': 'John', 'age': 30})@app.route('/custom')
def custom_response():# 创建自定义响应response = make_response('Custom Response')response.headers['X-Custom-Header'] = 'Value'response.status_code = 201return response@app.route('/redirect')
def redirect_example():# 重定向return redirect('/new-location')@app.route('/render')
def render_example():# 渲染模板return render_template('index.html', name='John')
2.3.4 模板渲染

Flask使用Jinja2模板引擎渲染动态内容。模板通常存放在项目目录下的templates文件夹中:

from flask import render_template@app.route('/hello/<name>')
def hello(name):return render_template('hello.html', name=name)

模板文件示例(templates/hello.html):

<!DOCTYPE html>
<html>
<head><title>Hello Page</title><!-- 三种导入静态文件的方式 --><!-- <link rel="stylesheet" href="../static/index.css"> --><!-- <link rel="stylesheet" href="/static/index.css"> --><link rel="stylesheet" href="{{ url_for('static', filename='index.css') }}">
</head>
<body><h1>Hello, {{ name }}!</h1>
</body>
</html>

Jinja2模板支持变量替换、控制结构和模板继承等高级特性。

2.3.5 静态文件处理

静态文件(CSS、JavaScript、图片)通常存放在static文件夹中,可以使用url_for函数生成URL:

url_for('static', filename='style.css')

在模板中使用静态文件:

<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
<img src="{{ url_for('static', filename='images/logo.png') }}" alt="Logo">

2.4 扩展Flask功能

Flask的核心功能可以通过扩展来增强。常用的扩展包括:

  • Flask-SQLAlchemy:数据库ORM
  • Flask-WTF:表单处理
  • Flask-Login:用户认证
  • Flask-RESTful:构建RESTful API
  • Flask-Mail:电子邮件支持
  • Flask-Migrate:数据库迁移

安装和使用扩展的通常模式:

pip install flask-extension-name
from flask_extension_name import ExtensionClassextension = ExtensionClass(app)  # 或者使用初始化模式

3 Flask项目配置

3.1 配置基础

Flask应用配置通过app.config对象实现,它是一个类似字典的对象,可以用来存储各种配置变量。

3.1.1 直接设置配置值
app = Flask(__name__)
app.config['DEBUG'] = True
app.config['SECRET_KEY'] = 'your-secret-key'

或者使用属性语法:

app.debug = True
3.1.2 常用内置配置变量

Flask提供了许多内置配置变量:

配置变量说明默认值
DEBUG启用/禁用调试模式False
TESTING启用/禁用测试模式False
SECRET_KEY加密签名所需的密钥None
JSON_AS_ASCII禁用ASCII编码JSON响应True
JSON_SORT_KEYS排序JSON键True
SESSION_COOKIE_NAME会话cookie名称‘session’
SESSION_COOKIE_HTTPONLY禁止JS访问会话cookieTrue
SESSION_COOKIE_SECURE仅HTTPS传输会话cookieFalse
PERMANENT_SESSION_LIFETIME永久会话生存期timedelta(days=31)

3.2 配置方法

Flask支持多种配置加载方式,适合不同场景。

3.2.1 从Python文件加载

创建配置文件(如config.py):

# config.py
DEBUG = True
SECRET_KEY = 'your-secret-key'
DATABASE_URI = 'sqlite:///app.db'

在应用中加载配置:

app.config.from_pyfile('config.py')
3.2.2 从对象加载

创建配置类:

class Config:DEBUG = FalseTESTING = FalseSECRET_KEY = 'development-key'DATABASE_URI = 'sqlite:///app.db'class ProductionConfig(Config):DATABASE_URI = 'postgresql://user:pass@localhost/prod_db'class DevelopmentConfig(Config):DEBUG = TrueDATABASE_URI = 'sqlite:///dev.db'class TestingConfig(Config):TESTING = TrueDATABASE_URI = 'sqlite:///test.db'

在应用中加载配置:

app.config.from_object('configmodule.ProductionConfig')
3.2.3 从环境变量加载

首先设置环境变量:

export APP_SETTINGS="config.py"
export SECRET_KEY="your-secret-key"

在应用中加载配置:

app.config.from_envvar('APP_SETTINGS')  # 从文件
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY')  # 单个值
3.2.4 多重配置加载策略

在实际项目中,通常组合使用多种配置方法:

app = Flask(__name__)# 首先加载默认配置
app.config.from_object('app.config.DefaultConfig')# 然后根据环境加载特定配置
env = os.environ.get('FLASK_ENV', 'development')
if env == 'production':app.config.from_object('app.config.ProductionConfig')
elif env == 'testing':app.config.from_object('app.config.TestingConfig')
else:app.config.from_object('app.config.DevelopmentConfig')# 最后从环境变量文件覆盖配置(可选)
app.config.from_envvar('APP_SETTINGS', silent=True)

3.3 大型项目配置

对于大型项目,需要更复杂的配置结构和组织方式。

3.3.1 项目结构

一个典型的大型Flask项目结构如下:

flask_project/
├── apps/                    # 存放各个蓝图模块
│   ├── app01/
│   │   ├── __init__.py     # 存放蓝图的配置
│   │   ├── views.py        # 视图函数
│   │   └── models.py       # 数据模型
│   └── app02/
│       ├── __init__.py
│       ├── views.py
│       └── models.py
├── conf/                   # 存放项目的配置文件
│   └── application.yaml
├── middleware/            # 存放中间件的配置
│   └── request_middleware.py
├── sdk/                  # 存放一些第三方包
│   ├── logs/            # 日志相关
│   ├── http/            # http请求相关
│   └── es/              # es相关
├── utils/               # 存放项目中公共的工具类
├── static/              # 静态文件
│   ├── css/
│   ├── js/
│   └── images/
├── templates/           # 模板文件
├── extensions.py        # 存放SQLAlchemy等扩展的配置
├── scheduler.py         # 存放定时任务的配置
├── app.py              # flask应用的初始化文件,也是入口文件
├── settings.py         # 存放整个flask项目的配置
└── requirements.txt    # 项目依赖
3.3.2 应用工厂模式

应用工厂模式允许创建多个应用实例,便于测试和不同环境部署:

# app.py
from flask import Flask
from apps.app01 import app01
from apps.extensions import dbdef create_app(config=None):"""App工厂函数"""app = Flask(__name__)# 加载配置if config is None:app.config.from_object('settings')else:app.config.from_object(config)# 初始化扩展db.init_app(app)# 添加蓝图app.register_blueprint(app01)# 注册中间件register_middleware(app)return app# 创建app实例
app = create_app()if __name__ == '__main__':app.run(host='0.0.0.0', port=8080, debug=True)
3.3.3 蓝图(Blueprint)使用

蓝图允许将应用模块化,适合大型项目结构:

# apps/app01/__init__.py
from flask import Blueprint
from flask_restful import Api# 创建蓝图实例
app01 = Blueprint('app01', __name__,url_prefix='/api/v1/app01',template_folder='templates',static_folder='static',static_url_path='assets')# 初始化API(如果使用Flask-RESTful)
api = Api(app01)# 导入视图
from . import views
# apps/app01/views.py
from . import app01@app01.route('/endpoint')
def endpoint():return 'App01 Endpoint'

在主应用中注册蓝图:

# app.py
from apps.app01 import app01 as app01_blueprint
from apps.app02 import app02 as app02_blueprintapp.register_blueprint(app01_blueprint)
app.register_blueprint(app02_blueprint)

3.4 扩展配置

3.4.1 数据库配置

使用Flask-SQLAlchemy配置数据库:

# extensions.py
from flask_sqlalchemy import SQLAlchemydb = SQLAlchemy()
# settings.py
import os
from pathlib import PathBASE_DIR = Path(__file__).resolve().parent# 数据库配置
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(BASE_DIR, 'app.db')
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ECHO = False  # 是否输出SQL语句# MySQL配置示例
# user, password, port, host, database, charset = config_parser.get_mysql_set()
# SQLALCHEMY_DATABASE_URI = f"mysql+pymysql://{user}:{password}@{host}:{port}/{database}?charset={charset}"

初始化数据库:

# app.py
from extensions import dbdef create_app():app = Flask(__name__)# 加载配置...db.init_app(app)return app
3.4.2 日志配置

配置应用日志系统:

# settings.py
import logging.configLOGGING = {'version': 1,'disable_existing_loggers': False,'formatters': {'file_formatter': {'format': '[%(asctime)s][%(threadName)s:%(thread)d][%(filename)s[line:%(lineno)d][%(levelname)s] - %(message)s'},'simple': {'format': '[%(asctime)s][%(levelname)s] - %(message)s'},},'handlers': {'console': {'class': 'logging.StreamHandler','formatter': 'simple','level': 'DEBUG'},'file': {'class': 'logging.handlers.RotatingFileHandler','formatter': 'file_formatter','filename': 'app.log','maxBytes': 1024 * 1024 * 10,  # 10MB'backupCount': 5},},'loggers': {'flask_logger': {'handlers': ['console', 'file'],'level': 'INFO',  # 设置日志级别,可以根据需要调整'propagate': False,},'sqlalchemy': {'handlers': ['console'],'level': 'INFO','propagate': False}},
}# 加载日志配置
logging.config.dictConfig(LOGGING)

3.5 部署配置

3.5.1 使用Gunicorn部署

创建Gunicorn配置文件:

# gunicorn_config.py
import multiprocessingbind = "0.0.0.0:5000"
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "gevent"
worker_connections = 1000
timeout = 30
keepalive = 2

启动命令:

gunicorn -c gunicorn_config.py app:app
3.5.2 使用Nginx + uWSGI部署

uWSGI配置:

; uwsgi.ini
[uwsgi]
; 配合nginx使用
socket = 127.0.0.1:5005
; 项目路径
chdir = /path/to/your/project
; wsgi文件
module = app:app
; 指定工作进程
processes = 4
; 主进程
master = true
; 每个工作进程有2个线程
threads = 2
; 保存主进程的进程号
pidfile = /tmp/project_uwsgi.pid

Nginx配置:

# /etc/nginx/conf.d/yourapp.conf
server {listen 80;server_name yourdomain.com;access_log /var/log/nginx/yourapp_access.log main;location / {include uwsgi_params;uwsgi_pass 127.0.0.1:5005;proxy_read_timeout 150;client_max_body_size 20M;}location /static {alias /path/to/your/project/static;expires 30d;}
}
3.5.3 使用Supervisor管理进程

Supervisor配置:

; /etc/supervisor/conf.d/yourapp.conf
[program:yourapp_uwsgi]
command=/path/to/venv/bin/uwsgi /path/to/uwsgi.ini
numprocs=1
directory=/path/to/your/project
priority=999
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
startsecs=10
startretries=10
exitcodes=0,2
stopsignal=QUIT
user=www-data
log_stdout=true
log_stderr=true
redirect_stderr=true
stdout_logfile=/var/log/supervisor/yourapp_uwsgi.log
logfile_maxbytes=10MB
logfile_backups=10
environment=MODE="PRODUCTION",FLASK_ENV="production"

4 接口鉴权

4.1 认证与授权基础

在Web应用中,**认证(Authentication)是验证用户身份的过程,而授权(Authorization)**是确定已认证用户有权访问哪些资源的过程。Flask提供了灵活的机制来实现这两种功能。

4.1.1 Session-based认证

Session认证是一种基于服务器存储的认证机制:

  1. 用户登录成功后,服务器为其生成一个唯一的session_id,并将其存储在客户端的Cookie中
  2. 服务器在服务端(内存、数据库或Redis)存储session数据
  3. 每次请求,客户端携带包含session_id的Cookie
  4. 服务器通过验证session_id来识别用户身份
4.1.2 Token-based认证

Token认证是一种基于客户端的认证机制,通常使用JSON Web Token(JWT):

  1. 用户登录后,服务器生成一个Token返回给客户端
  2. 客户端在后续请求中携带该Token(通常在Authorization头中)
  3. 服务器通过解析和验证Token确定用户身份
  4. 服务器无需存储会话状态,Token包含所有必要信息

4.2 Session认证实现

4.2.1 基本Session配置
from flask import Flask, session
import osapp = Flask(__name__)
app.secret_key = os.urandom(24)  # 或者使用固定密钥
app.config['SESSION_TYPE'] = 'filesystem'  # 或者 'redis', 'memcached'等
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=1)
4.2.2 登录和会话管理
from flask import session, request, jsonify, redirect, url_for
from werkzeug.security import generate_password_hash, check_password_hash# 模拟用户数据
USER_DATA = {"test_user": generate_password_hash("password123")
}@app.route('/login', methods=['POST'])
def login():data = request.jsonusername = data.get('username')password = data.get('password')if username in USER_DATA and check_password_hash(USER_DATA[username], password):# 创建会话session['username'] = usernamesession['logged_in'] = True# 设置会话为永久性session.permanent = Truereturn jsonify({"message": "Login successful"}), 200return jsonify({"message": "Invalid credentials"}), 401@app.route('/protected', methods=['GET'])
def protected():if 'logged_in' in session and session['logged_in']:username = session['username']return jsonify({"message": f"Welcome {username}!"}), 200return jsonify({"message": "Unauthorized"}), 401@app.route('/logout', methods=['POST'])
def logout():# 清除会话session.pop('username', None)session.pop('logged_in', None)return jsonify({"message": "Logout successful"}), 200
4.2.3 使用Redis存储Session

对于生产环境,建议使用Redis等外部存储保存Session:

from flask import Flask
from flask_session import Session
import redisapp = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_REDIS'] = redis.from_url('redis://localhost:6379')# 初始化Session扩展
Session(app)

4.3 Token认证(JWT)实现

4.3.1 JWT基础概念

JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间安全地传输信息。JWT由三部分组成:

  1. Header:包含令牌类型和算法
  2. Payload:包含声明(claims)
  3. Signature:用于验证令牌完整性
4.3.2 安装和配置JWT扩展
pip install flask-jwt-extended
from flask import Flask
from flask_jwt_extended import JWTManagerapp = Flask(__name__)# 配置JWT
app.config['JWT_SECRET_KEY'] = 'your-jwt-secret-key'  # 生产环境应使用更安全的密钥
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(hours=1)
app.config['JWT_REFRESH_TOKEN_EXPIRES'] = timedelta(days=30)
app.config['JWT_TOKEN_LOCATION'] = ['headers']  # 可以从headers, cookies, query_string等位置获取token
app.config['JWT_HEADER_NAME'] = 'Authorization'
app.config['JWT_HEADER_TYPE'] = 'Bearer'# 初始化JWT管理器
jwt = JWTManager(app)
4.3.3 实现JWT认证
from flask import request, jsonify
from flask_jwt_extended import (create_access_token, create_refresh_token,jwt_required,get_jwt_identity,get_jwt
)
from werkzeug.security import generate_password_hash, check_password_hash# 模拟用户数据
USER_DATA = {"test_user": generate_password_hash("password123")
}@app.route('/login', methods=['POST'])
def login():data = request.jsonusername = data.get('username')password = data.get('password')if username in USER_DATA and check_password_hash(USER_DATA[username], password):# 创建访问令牌和刷新令牌access_token = create_access_token(identity=username)refresh_token = create_refresh_token(identity=username)return jsonify({"access_token": access_token,"refresh_token": refresh_token,"message": "Login successful"}), 200return jsonify({"message": "Invalid credentials"}), 401@app.route('/protected', methods=['GET'])
@jwt_required()  # 需要有效的JWT访问令牌
def protected():current_user = get_jwt_identity()return jsonify({"message": f"Welcome {current_user}!"}), 200@app.route('/refresh', methods=['POST'])
@jwt_required(refresh=True)  # 需要有效的刷新令牌
def refresh():current_user = get_jwt_identity()new_access_token = create_access_token(identity=current_user)return jsonify({"access_token": new_access_token}), 200@app.route('/logout', methods=['POST'])
@jwt_required()
def logout():# JWT通常是无状态的,客户端通过丢弃令牌实现"登出"# 如果需要服务端失效令牌,可以使用令牌黑名单return jsonify({"message": "Logout successful"}), 200
4.3.4 高级JWT特性
# 添加自定义声明到JWT
@jwt.user_identity_loader
def user_identity_lookup(user):return user.username@jwt.additional_claims_loader
def add_claims_to_access_token(identity):# 可以根据用户身份添加自定义声明if identity == "admin":return {"is_admin": True}return {"is_admin": False}# 保护管理员路由
@app.route('/admin', methods=['GET'])
@jwt_required()
def admin():claims = get_jwt()if not claims.get('is_admin', False):return jsonify({"message": "Admin required"}), 403return jsonify({"message": "Welcome Admin!"}), 200# 处理令牌失效(黑名单)
blacklisted_tokens = set()@jwt.token_in_blocklist_loader
def check_if_token_revoked(jwt_header, jwt_payload):jti = jwt_payload["jti"]return jti in blacklisted_tokens@app.route('/revoke', methods=['POST'])
@jwt_required()
def revoke_token():jti = get_jwt()["jti"]blacklisted_tokens.add(jti)return jsonify({"message": "Token revoked"}), 200

4.4 基于角色的访问控制(RBAC)

RBAC(Role-Based Access Control)通过角色管理用户权限。

4.4.1 实现角色系统
# models.py
from extensions import db
from flask import current_app
from werkzeug.security import generate_password_hash, check_password_hash# 角色权限关联表
role_permissions = db.Table('role_permissions',db.Column('role_id', db.Integer, db.ForeignKey('role.id'), primary_key=True),db.Column('permission_id', db.Integer, db.ForeignKey('permission.id'), primary_key=True)
)# 用户角色关联表
user_roles = db.Table('user_roles',db.Column('user_id', db.Integer, db.ForeignKey('user.id'), primary_key=True),db.Column('role_id', db.Integer, db.ForeignKey('role.id'), primary_key=True)
)class User(db.Model):id = db.Column(db.Integer, primary_key=True)username = db.Column(db.String(80), unique=True, nullable=False)password_hash = db.Column(db.String(120), nullable=False)is_active = db.Column(db.Boolean, default=True)# 关系roles = db.relationship('Role', secondary=user_roles, backref=db.backref('users', lazy=True))def set_password(self, password):self.password_hash = generate_password_hash(password)def check_password(self, password):return check_password_hash(self.password_hash, password)def has_permission(self, permission_name):for role in self.roles:for permission in role.permissions:if permission.name == permission_name:return Truereturn Falseclass Role(db.Model):id = db.Column(db.Integer, primary_key=True)name = db.Column(db.String(80), unique=True, nullable=False)description = db.Column(db.String(255))# 关系permissions = db.relationship('Permission', secondary=role_permissions, backref=db.backref('roles', lazy=True))class Permission(db.Model):id = db.Column(db.Integer, primary_key=True)name = db.Column(db.String(80), unique=True, nullable=False)description = db.Column(db.String(255))
4.4.2 基于角色的权限装饰器
from functools import wraps
from flask import jsonify
from flask_jwt_extended import verify_jwt_in_request, get_jwt_identitydef permission_required(permission_name):def decorator(f):@wraps(f)def decorated_function(*args, **kwargs):# 验证JWTverify_jwt_in_request()# 获取当前用户身份username = get_jwt_identity()user = User.query.filter_by(username=username).first()if not user or not user.has_permission(permission_name):return jsonify({"message": "Insufficient permissions"}), 403return f(*args, **kwargs)return decorated_functionreturn decorator# 使用示例
@app.route('/admin/dashboard')
@jwt_required()
@permission_required('admin_dashboard')
def admin_dashboard():return jsonify({"message": "Welcome to admin dashboard"}), 200
4.4.3 组织级别的权限控制

对于多租户应用,需要实现组织级别的权限控制:

def organization_permission_required(permission_name):def decorator(f):@wraps(f)def decorated_function(*args, **kwargs):# 验证JWTverify_jwt_in_request()# 获取当前用户身份和组织IDusername = get_jwt_identity()organization_id = kwargs.get('organization_id')user = User.query.filter_by(username=username).first()# 检查用户是否属于该组织并有相应权限if not user or not user.has_organization_permission(organization_id, permission_name):return jsonify({"message": "Insufficient organization permissions"}), 403return f(*args, **kwargs)return decorated_functionreturn decorator# 使用示例
@app.route('/organizations/<organization_id>/settings')
@jwt_required()
@organization_permission_required('manage_organization')
def organization_settings(organization_id):return jsonify({"message": f"Organization {organization_id} settings"}), 200

4.5 OAuth2和第三方认证

4.5.1 使用Authlib实现OAuth2
from authlib.integrations.flask_client import OAuthoauth = OAuth(app)# 配置Google OAuth2
google = oauth.register(name='google',client_id='your-google-client-id',client_secret='your-google-client-secret',access_token_url='https://accounts.google.com/o/oauth2/token',access_token_params=None,authorize_url='https://accounts.google.com/o/oauth2/auth',authorize_params=None,api_base_url='https://www.googleapis.com/oauth2/v1/',client_kwargs={'scope': 'email profile'},
)@app.route('/login/google')
def login_google():redirect_uri = url_for('authorize_google', _external=True)return google.authorize_redirect(redirect_uri)@app.route('/login/google/authorize')
def authorize_google():token = google.authorize_access_token()resp = google.get('userinfo')user_info = resp.json()# 在这里处理用户信息,创建或登录用户# ...return jsonify(user_info)
4.5.2 使用Logto进行API保护

Logto是一个开源的CIAM(客户身份和访问管理)解决方案:

from flask import Flask, jsonify, request
from logto import LogtoClient, LogtoConfigapp = Flask(__name__)# Logto配置
logto_config = LogtoConfig(endpoint='https://your-tenant.logto.app',app_id='your-app-id',app_secret='your-app-secret',resource='https://api.yourapp.com'  # 你的API资源标识
)logto_client = LogtoClient(logto_config)@app.route('/protected')
async def protected():# 从请求头中获取访问令牌auth_header = request.headers.get('Authorization')if not auth_header or not auth_header.startswith('Bearer '):return jsonify({"error": "Unauthorized"}), 401access_token = auth_header[7:]  # 去掉"Bearer "前缀try:# 验证访问令牌claims = await logto_client.verify_access_token(access_token)# 检查权限if not claims.get('scopes', []).contains('read:data'):return jsonify({"error": "Insufficient permissions"}), 403return jsonify({"message": "Access granted", "user": claims.get('sub')})except Exception as e:return jsonify({"error": str(e)}), 401

5 自定义中间件

5.1 中间件概念与原理

中间件是WSGI应用的重要组成部分,它可以在请求被处理前和响应被发送前执行特定代码,用于实现跨切面关注点如认证、日志、错误处理等。

5.1.1 Flask中间件工作原理

在Flask中,中间件主要通过两种方式实现:

  1. 装饰器模式:使用@app.before_request@app.after_request等装饰器
  2. WSGI中间件:包装Flask应用的WSGI应用

Flask的请求处理流程如下:

  1. 请求到达WSGI服务器
  2. 经过可能的WSGI中间件
  3. 到达Flask应用
  4. 执行before_request钩子
  5. 执行视图函数
  6. 执行after_request钩子
  7. 响应经过WSGI中间件
  8. 返回给客户端

5.2 内置请求钩子

Flask提供了多种装饰器来实现中间件功能。

5.2.1 before_request
@app.before_request
def before_request():# 在每个请求之前执行print(f"Request received: {request.method} {request.path}")# 可以进行身份验证if not session.get('user_id') and request.endpoint not in ['login', 'static']:return redirect(url_for('login'))# 设置全局变量g.request_start_time = time.time()
5.2.2 after_request
@app.after_request
def after_request(response):# 在每个请求之后执行print(f"Response sent: {response.status_code}")# 添加自定义响应头response.headers['X-Frame-Options'] = 'SAMEORIGIN'response.headers['X-Content-Type-Options'] = 'nosniff'# 记录请求处理时间if hasattr(g, 'request_start_time'):processing_time = time.time() - g.request_start_timeresponse.headers['X-Processing-Time'] = str(processing_time)return response
5.2.3 teardown_request
@app.teardown_request
def teardown_request(exception=None):# 在请求结束时执行,即使发生异常也会执行# 常用于资源清理if hasattr(g, 'db_connection'):g.db_connection.close()
5.2.4 错误处理
@app.errorhandler(404)
def not_found_error(error):return render_template('404.html'), 404@app.errorhandler(500)
def internal_error(error):db.session.rollback()  # 发生错误时回滚数据库会话return render_template('500.html'), 500# 全局异常处理
def handle_ex(error):return jsonify({"code": -1, "msg": f"{error}"})# 注册全局异常处理
app.register_error_handler(Exception, handle_ex)

5.3 自定义WSGI中间件

除了使用Flask的装饰器,还可以创建WSGI中间件。

5.3.1 基本WSGI中间件结构
class Middleware:def __init__(self, app):self.app = appdef __call__(self, environ, start_response):# 请求前处理print('请求前的操作')# 调用应用response = self.app(environ, start_response)# 响应后处理print('请求之后操作')return response# 应用中间件
if __name__ == '__main__':app.wsgi_app = Middleware(app.wsgi_app)app.run()
5.3.2 认证中间件示例
class AuthenticationMiddleware:def __init__(self, app):self.app = appdef __call__(self, environ, start_response):# 从environ中获取请求信息path = environ.get('PATH_INFO', '')method = environ.get('REQUEST_METHOD', '')# 跳过登录和静态资源if path == '/login' or path.startswith('/static/'):return self.app(environ, start_response)# 检查认证if not self.is_authenticated(environ):# 未认证,返回401错误start_response('401 Unauthorized', [('Content-Type', 'text/html')])return [b'<h1>Unauthorized</h1>']# 已认证,继续处理请求return self.app(environ, start_response)def is_authenticated(self, environ):# 检查cookie或Authorization头cookies = environ.get('HTTP_COOKIE', '')auth_header = environ.get('HTTP_AUTHORIZATION', '')# 这里实现具体的认证逻辑# ...return True  # 或False# 应用中间件
app.wsgi_app = AuthenticationMiddleware(app.wsgi_app)

5.4 实用中间件实现

5.4.1 日志记录中间件
import logging
import time
from flask import request, gclass RequestLoggingMiddleware:def __init__(self, app):self.app = appself.logger = logging.getLogger('request_logger')self.logger.setLevel(logging.INFO)# 创建文件处理器file_handler = logging.FileHandler('app.log')formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')file_handler.setFormatter(formatter)self.logger.addHandler(file_handler)def __call__(self, environ, start_response):start_time = time.time()def custom_start_response(status, headers, exc_info=None):# 计算处理时间processing_time = time.time() - start_time# 记录日志self.logger.info(f"{request.remote_addr} - {request.method} {request.path} - "f"{status} - {processing_time:.2f}s - {request.user_agent}")return start_response(status, headers, exc_info)return self.app(environ, custom_start_response)# 或者使用装饰器方式
@app.before_request
def log_request_info():g.start_time = time.time()app.logger.info(f"Request: {request.method} {request.path} - IP: {request.remote_addr}")@app.after_request
def log_response_info(response):if hasattr(g, 'start_time'):processing_time = time.time() - g.start_timeapp.logger.info(f"Response: {response.status} - Time: {processing_time:.2f}s")return response
5.4.2 跨域中间件(CORS)
class CORSMiddleware:def __init__(self, app, origins=None, methods=None, headers=None):self.app = appself.origins = origins or ['*']self.methods = methods or ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS']self.headers = headers or ['Content-Type', 'Authorization']def __call__(self, environ, start_response):if environ['REQUEST_METHOD'] == 'OPTIONS':# 处理预检请求start_response('200 OK', [('Access-Control-Allow-Origin', ', '.join(self.origins)),('Access-Control-Allow-Methods', ', '.join(self.methods)),('Access-Control-Allow-Headers', ', '.join(self.headers)),('Access-Control-Max-Age', '86400'),  # 24小时])return [b'']def custom_start_response(status, headers, exc_info=None):# 添加CORS头headers.append(('Access-Control-Allow-Origin', ', '.join(self.origins)))headers.append(('Access-Control-Allow-Credentials', 'true'))return start_response(status, headers, exc_info)return self.app(environ, custom_start_response)# 应用CORS中间件
app.wsgi_app = CORSMiddleware(app.wsgi_app)
5.4.3 速率限制中间件
from collections import defaultdict
import timeclass RateLimitingMiddleware:def __init__(self, app, max_requests=100, time_window=60):self.app = appself.max_requests = max_requestsself.time_window = time_windowself.requests = defaultdict(list)def __call__(self, environ, start_response):client_ip = environ.get('REMOTE_ADDR')current_time = time.time()# 清理过期的请求记录if client_ip in self.requests:self.requests[client_ip] = [t for t in self.requests[client_ip] if current_time - t < self.time_window]# 检查是否超过限制if len(self.requests[client_ip]) >= self.max_requests:start_response('429 Too Many Requests', [('Content-Type', 'text/plain')])return [b'Rate limit exceeded. Please try again later.']# 记录当前请求self.requests[client_ip].append(current_time)return self.app(environ, start_response)# 应用速率限制中间件
app.wsgi_app = RateLimitingMiddleware(app.wsgi_app, max_requests=100, time_window=60)

5.5 中间件最佳实践

5.5.1 中间件执行顺序

在Flask中,中间件的执行顺序很重要:

# 执行顺序: 1 → 2 → 视图函数 → 4 → 3
@app.before_request  # 1
def before1():print('before1')@app.before_request  # 2
def before2():print('before2')@app.after_request  # 3
def after1(response):print('after1')return response@app.after_request  # 4
def after2(response):print('after2')return response

注意before_request钩子按注册顺序执行,after_request钩子按反向顺序执行。如果某个before_request钩子返回了响应,后续的before_request钩子和视图函数将不会执行,但所有已注册的after_request钩子仍会执行。

5.5.2 中间件组织与管理

对于大型项目,应该将中间件组织在单独的模块中:

# middleware/__init__.py
def init_middleware(app):# 注册请求扩展app.before_request(before_request)app.after_request(after_request)# 注册错误处理app.register_error_handler(404, not_found_error)app.register_error_handler(500, internal_error)# 应用WSGI中间件app.wsgi_app = AuthenticationMiddleware(app.wsgi_app)app.wsgi_app = CORSMiddleware(app.wsgi_app)app.wsgi_app = RequestLoggingMiddleware(app.wsgi_app)# middleware/request_middleware.py
def before_request():"""请求前的逻辑"""g.start_time = time.time()g.user_id = session.get('user_id')g.ip_address = request.remote_addrdef after_request(response):"""请求后的逻辑"""if hasattr(g, 'start_time'):processing_time = time.time() - g.start_timeresponse.headers['X-Processing-Time'] = f'{processing_time:.3f}s'return response# middleware/error_middleware.py
def not_found_error(error):return render_template('errors/404.html'), 404def internal_error(error):db.session.rollback()return render_template('errors/500.html'), 500# 在应用中初始化中间件
from middleware import init_middlewareapp = Flask(__name__)
init_middleware(app)
5.5.3 性能考虑

使用中间件时需要注意性能影响:

  1. 避免阻塞操作:中间件中的操作应该尽可能高效,避免阻塞I/O操作
  2. 缓存昂贵操作:对于昂贵的操作,考虑使用缓存
  3. 选择性应用:不是所有中间件都需要应用于所有请求,可以根据路径或其他条件选择性应用
  4. 异步处理:对于耗时的操作,考虑使用异步处理或消息队列
# 选择性应用中间件
@app.before_request
def selective_middleware():if request.path.startswith('/api/'):# 只对API路由执行某些操作pass

6 总结

本文全面介绍了Flask框架的基本使用、项目配置、接口鉴权和自定义中间件的实现。通过深入理解这些概念和技术,您可以构建出健壮、安全且可扩展的Flask应用程序。

6.1 关键要点回顾

  1. Flask基础:Flask是一个轻量级但功能强大的Web框架,适合从简单应用到复杂系统的各种场景。
  2. 项目配置:Flask提供灵活的配置系统,支持多种配置源和环境特定的配置。
  3. 接口鉴权:实现了Session-based和Token-based(JWT)两种认证方式,以及基于角色的访问控制(RBAC)。
  4. 中间件:通过装饰器和WSGI中间件两种方式实现自定义中间件,处理跨切面关注点。

6.2 最佳实践建议

  1. 安全性:始终使用HTTPS,妥善管理密钥和密码,验证和清理所有用户输入。
  2. 性能:使用缓存,优化数据库查询,异步处理耗时任务。
  3. 可维护性:遵循模块化设计原则,使用蓝图组织大型项目,编写清晰的文档和测试。
  4. 监控:记录适当的日志,监控应用性能和错误。

6.3 进一步学习方向

要深入学习Flask和Web开发,可以考虑以下方向:

  1. 数据库集成:深入学习SQLAlchemy和数据库设计
  2. 异步编程:学习Flask的异步支持和相关技术
  3. 微服务架构:了解如何使用Flask构建微服务
  4. 测试:掌握Flask应用的测试策略和工具
  5. 部署和DevOps:学习容器化部署和CI/CD流程

Flask是一个强大而灵活的工具,随着经验的积累,您将能够更好地利用其功能来构建符合各种需求的Web应用程序。

在这里插入图片描述

http://www.dtcms.com/a/393495.html

相关文章:

  • 23种设计模式之【责任链模式】-核心原理与 Java 实践
  • 执行 conda update -n base -c defaults conda 后仍提示需要升级
  • 学习日报 20250921|NIO
  • 【Linux操作系统】Linux基础指令和权限
  • 谷歌nano banana官方Prompt模板
  • 基于Python大数据的新闻推荐分析
  • ​​[硬件电路-315]:AD7606BSTZ如何测量失调电压?
  • 微服务-分布式追踪 / 监控工具大全
  • The Library: 2靶场渗透
  • 金融知识“厦”乡趣 平安产险厦门分公司启动2025年“金融教育宣传周”活动
  • C++学习笔记——模板初阶
  • Windows 下 WSL2 生态:Ubuntu/Docker Desktop 关系、VLLM 部署差异与性能解析
  • 智能体技术革命:从华为金融智能体FAB看AI智能体的未来发展与行业影响
  • CIKM 2025 | FinCast:用于金融时间序列预测的基础模型
  • 论文解读——矩形隧道中MIMO信道特性的模态理论解释
  • Mac brew VSCode Python3
  • 【C++】list 的使用及迭代器底层详解
  • NumPy 系列(三):numpy 数组的索引
  • STL源码探秘(一):深入剖析List的迭代器设计与实现
  • PNP机器人九月具身智能引领FRANKA机器人具身智能技术创新与人形机器人产业全球化新格局,全球具身领先生态推动模型部署和泛化
  • ACP(八):用插件扩展答疑机器人的能力边界
  • 迁移学习:从理论到实践,让模型 “举一反三” 的核心技术
  • ACP(六)自动化评测机器人的表现
  • 【MySQL数据库】MySQL的第一步:从安装启动到用户权限配置的一站式实战指南
  • MySQL笔记7
  • 【C语言】C语言预处理详解,从基础到进阶的全面讲解
  • Spotify:递归嵌入与聚类(四)
  • 三种查询语言比较:SQL、SPL、PromQL
  • [Windows] 迅连科技音频处理工具 CyberLink AudioDirector 2026 16.0.5703.0 中文多语免费版
  • (一)React面试(虚拟DOM/类组件)