欢迎来到十九分快乐的博客

生死看淡,不服就干。

2. flask - 钩子 - 异常 - 终端 - 模板引擎

请求钩子

在客户端和服务器交互的过程中,有些准备工作或扫尾工作需要处理,比如:

  • 在请求开始时,建立数据库连接;
  • 在请求开始时,根据需求进行权限校验;
  • 在请求结束时,指定数据的交互格式;

为了让每个视图函数避免编写重复功能的代码,Flask提供了通用设置的功能,即请求钩子

from flask import Flask
app = Flask(__name__)

@app.before_first_request
def first_request():
    print('before_first_request')
    print('------初始化,网站运行后,第一次请求施行前的操作-------')

@app.before_request
def every_request():
    print('before_request')
    print('----------客户端每次请求前执行的操作-----------')

@app.after_request
def every_response(response):
    # 需要接收响应对象,返回响应对象
    # 进行响应收尾工作,处理公共响应
    print('after_request')
    print('------------每一次请求视图执行返回之后的操作-----------')
    return response

@app.teardown_request
def teardown_request(exc):
    # debug=False才能接收异常对象,但是处理不了异常,只能用作异常记录等
    print('teardown_request')
    print('----------在每一次请求以后,执行这个钩子方法---------')
    print('----------如果有异常错误,则会传递错误异常对象到当前方法的参数中---------')
    print(exc)

@app.route(rule='/')
def index():
    print('---------视图执行了---------')
    return 'hello world!!'

# 声明和加载配置
class Config():
    DEBUG = True
app.config.from_object(Config)

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

执行情况

# 第一次请求时打印结果
    before_first_request
    ------初始化,网站运行后,第一次请求施行前的操作-------
    before_request
    ----------客户端每次请求前执行的操作-----------
    ---------视图执行了---------
    after_request
    ------------每一次请求视图执行返回之后的操作-----------
    teardown_request
    ----------在每一次请求以后,执行这个钩子方法---------
    ----------如果有异常错误,则会传递错误异常对象到当前方法的参数中---------
    None
# 第二次请求打印结果 - before_first_request没有了
    before_request
    ----------客户端每次请求前执行的操作-----------
    ---------视图执行了---------
    after_request
    ------------每一次请求视图执行返回之后的操作-----------
    teardown_request
    ----------在每一次请求以后,执行这个钩子方法---------
    ----------如果有异常错误,则会传递错误异常对象到当前方法的参数中---------
    None

异常捕获

errorhandler 装饰器

  • 注册一个错误处理程序,当程序抛出指定错误状态码的时候,就会调用该装饰器所装饰的方法

参数:

  • HTTP的错误状态码或指定异常类型
from flask import Flask,abort
app = Flask(__name__)

@app.route(rule='/')
def index():
    abort(404) # http爆出404异常错误
    return 'hello world!!'

# 1.errorhandler通过错误状态码捕获异常
@app.errorhandler(404)
def error(e):
    return '服务器离家出走了'
'''
	访问 / ,直接被异常捕获,显示 -- 服务器离家出走了
'''

# 2.errorhandler捕获异常异常类型 - 系统内置异常
@app.route('/raise')
def raise_ex():
    raise TypeError('类型错误!')
    return 'ok'

@app.errorhandler(TypeError)
def error_type(e):
    return f'错误:{e}'
'''
	访问 /raise ,直接被异常捕获,显示 - 错误:类型错误!
'''

# 3.捕捉自定义异常
'''自定义异常'''
class APIError(Exception):
    pass

@app.route("/zdy")
def zdy():
    raise APIError("api接口调用参数有误!")
    return "OK"

@app.errorhandler(APIError)
def error_apierror(e):
    return "错误: %s" % e

# 声明和加载配置
class Config():
    DEBUG = True
app.config.from_object(Config)

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

context - 上下文

Flask中上下文对象:相当于一个容器,保存了 Flask 程序运行过程中的一些信息[变量、函数、类与对象等信息]。

Flask中有两种上下文:

  • 请求上下文(request context) - 保存了客户端和服务器交互的数据,一般来自于客户端。
  • 应用上下文(application context) - flask 应用程序运行过程中,保存的一些配置信息,比如路由列表,程序名、数据库连接、应用信息等

请求上下文(request context)

在 flask 中,可以直接在视图函数中使用 request 这个对象进行获取相关数据,而 request 就是请求上下文的对象,保存了当前本次请求的相关数据,

  • 请求上下文对象有:request对象session对象

注意: 请求上下文提供的变量/属性/方法/函数/类与对象,只能在视图中或者被视图调用的地方使用

应用上下文(application context)

它不是一直存在的,它只是request context 中操作当前falsk应用对象 app 的代理(人),它的作用主要是帮助 request 获取当前的flask应用相关的信息,它是伴 request 而生,随 request 而灭的。

application 指的就是当你调用app = Flask(__name__)创建的这个对象app

  • 应用上下文对象有:current_app公共对象g变量

current_app

current_app相对于flask实例化的公共对象,用于存储应用实例对象中的变量

manage.py

from flask import Flask,request,session,current_app,g

# 初始化
app = Flask(import_name=__name__)

# 声明和加载配置
class Config():
    DEBUG = True
app.config.from_object(Config)

# 编写路由视图
@app.route(rule='/')
def index():
	print(current_app) # <Flask 'manage'> current_app就是flask应用实例对象app
    print(current_app == app) # True
    print(current_app.config)   # 获取当前项目的所有配置信息
    print(current_app.url_map)  # 获取当前项目的所有路由信息

    return "ok"

if __name__ == '__main__':
    # 运行flask
    app.run(host="0.0.0.0",port=5000)

g变量

g 作为 flask 程序全局的一个临时变量,充当者中间媒介的作用,我们可以通过它传递一些数据,g 保存的是当前请求的全局变量,不同的请求会有不同的全局变量

from flask import Flask,request,session,current_app,g

# 初始化
app = Flask(import_name=__name__)

# 声明和加载配置
class Config():
    DEBUG = True
app.config.from_object(Config)

# 每次http请求之前执行语句
@app.before_request
def before_request():
    g.name = "root"

def get_two_func():
    name = g.name
    print("g.name=%s" % name)

def get_one_func():
    get_two_func()

# 编写路由视图
@app.route(rule='/')
def index():
    get_one_func()
    return "<h1>hello world!</h1>"

if __name__ == '__main__':
    # 运行flask
    app.run(host="0.0.0.0",port=5000)

Flask-script扩展

终端启动项目

这个模块的作用可以让我们通过终端来控制flask项目的运行,类似于django的manage.py

安装:

pip install flask-script

应用:manage.py

from flask import Flask
# 引用Flask-script扩展类
from flask_script import Manager

app = Flask(__name__)

# 使用flask-script脚手架[终端脚本]启动项目
manage = Manager(app)

@app.route(rule='/')
def index():
    return 'hello world!!'

if __name__ == '__main__':
    manage.run()

启动终端脚本的命令:运行项目

# 默认IP端口 - 127.0.0.1:5000
python manage.py runserver  # 默认关闭debug调试

python manage.py runserver -d  # 开启debug调试

# 指定ip和端口运行项目
python manage.py runserver -h127.0.0.1 -p5555

# 或者pycharm中,配置manage.py文件,根Django一样
# - 在启动下拉框 Parameter中添加runserver后,启动文件就可以运行项目

自定义终端脚本命令

Flask-Script 还可以为当前应用程序添加脚本命令

1. 引入Command命令基类
2. 创建命令类必须直接或间接继承Command,并在内部实现run方法或__call__,同时如果有自定义的其他参数Option
3. 使用flask_script应用对象manage.add_command对命令类进行注册,并设置调用终端别名。

基本使用

manage.py

from flask import Flask
# 引入Command命令基类,Option设置传递参数
from flask_script import Manager,Command,Option

app = Flask(__name__)

"""使用脚手架[终端脚本]启动项目"""
manage = Manager(app)

# 自定义终端命令
class PrintCommand(Command):
    '''命令描述:创建目录及文件'''
    # 设置终端命令传递参数
    def get_options(self):
        options = (
            Option('-q', '--home', dest='home', default="hello", type=str),
            Option('-h', '--host', dest='host', default="host"),
        )
        return options

    # 也可以使用option_list设置参数
    # option_list = (
    #     Option('-q', '--home', dest='home', default="hello", type=str),
    #     Option('-h', '--host', dest='host', default="host"),
    # )


    # 没有传递app对象
    def run(self,home,host):
        print(f'-q = {home}')
        print(f'-h = {host}')

    # # 会自动传递当前flask实例对象进来
    # def __call__(self,app, *args, **kwargs):
    #     print('打印APP对象',app)

# 注册终端命令并命名
manage.add_command('print',PrintCommand)

if __name__ == '__main__':
    manage.run()

测试终端输入:

# 使用run方法,没传app对象
python manage.py print -qhehe -h127.0.0.1

# 使用__call__方法,传递了app对象
python manage.py print

练习:

1. 能在终端下面,输入 python manage.py startapp -nhome 命令则可以在当前目录下创建以下目录和文件
   home/
    |- views.py
    |- models.py
    |- urls.py
    |- test.py

manage.py

from flask import Flask
# 引入Command命令基类,Option设置传递参数
from flask_script import Manager,Command,Option
import os

app = Flask(__name__)

"""使用脚手架[终端脚本]启动项目"""
manage = Manager(app)

# 自定义终端命令
class MkdirCommand(Command):
    '''命令描述:创建目录及文件'''
    def get_options(self):
        options = (
            Option('-n', '--home', dest='home', default="hello", type=str),
        )
        return options

    def run(self,home):
        # 判断目录是否存在
        if os.path.isdir(home):
            return
        os.mkdir(f'{home}')
        os.mknod(f'{home}/views.py')
        os.mknod(f'{home}/models.py')
        os.mknod(f'{home}/urls.py')
        os.mknod(f'{home}/test.py')

# 注册终端命令并命名
manage.add_command('startapp',MkdirCommand)

# 加载配置
class Config(object):
    DEBUG = True
    SECRET_KEY = 'jyh123'
app.config.from_object(Config)

if __name__ == '__main__':
    manage.run()

输入终端命令,创建目录结构:

python manage.py startapp -nhome

Jinja2模板引擎

Flask内置的模板语言,它的设计思想来源于 Django 的模板引擎DTP(Django Template),并扩展了其语法和一系列强大的功能。

渲染模版函数

  • Flask提供的 render_template 函数封装了该模板引擎
  • render_template 函数的第一个参数是模板的文件名,后面的参数都是键值对,表示模板中变量对应的真实值。

模板中特有的变量和函数:

config

你可以从模板中直接访问Flask当前的config对象:

request

就是flask中代表当前请求的request对象:

session

为Flask的session对象,显示session数据

g变量

在视图函数中设置g变量的 name 属性的值,然后在模板中直接可以取出

url_for()

url_for会根据传入的路由器函数名,返回该路由对应的URL,在模板中始终使用url_for()就可以安全的修改路由绑定的URL,则不比担心模板中渲染出错的链接:

基本使用

根目录下创建templates目录,主要用来储存.html文件

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h1>欢迎来的py33期</h1>

<h1>标题:{{title}}</h1>
<h1>list_a:{{list[0]}}</h1>
<h1>list_a:{{list.0}}</h1>
<h1>dict_name{{dict['name']}}</h1>
<h1>dict_name{{dict.name}}</h1>

<!--自己的模板中访问一些 Flask 默认内置的函数和对象-->
<h1>{{config.DEBUG}}</h1>
<h1>name={{request.args.name}}</h1>
<h1>index_url={{url_for("index")}}</h1>
<h1>session_name={{session.name}}</h1>

</body>
</html>

manage.py

from flask import Flask,render_template,render_template_string
from flask_script import Manager

# app = Flask(__name__,template_folder='templates')
# 不定义,默认到templates寻找html页面
app = Flask(__name__)

# 使用脚手架[终端脚本]启动项目
manage = Manager(app)

@app.route(rule='/')
def index():
    session['name'] = 'jyh' # 设置session值
    data = {}
    data["title"] = "我的项目"
    data["list"] = ["a", "b", "c"]
    data["dict"] = {
        "name": "xiaoming",
        "id": 100,
    }
    # 返回html路径
    return render_template('index.html',**data)


@app.route('/cont')
def content():
    content = """<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{ title }}</title>
</head>
<body>

{{ test }}

</body>
</html>
    """
    data = {"title":"py33","test":"hello"}
    # 返回html内容
    return render_template_string(content,**data)

# 声明和加载配置
class Config():
    DEBUG = True
    SECRET_KEY = 'jyh123'
app.config.from_object(Config)

if __name__ == '__main__':
    manage.run()

终端启动项目

python manage.py runserver

流程控制

pycharm中设置当前项目的模板语言

files/settings/languages & frameworks/python template languages

设置下拉框为jinja2,保存

**主要包含两个: ** if语句 和 for循环

if语句

Jinja2 语法中的if语句跟 Python 中的 if 语句相似,后面的布尔值或返回布尔值的表达式将决定代码中的哪个流程会被执行.

用 {%%} 定义的控制代码块,可以实现一些语言层次的功能,比如循环或者if语句

基本使用

视图manage.py

from flask import Flask,render_template,request
from flask_script import Manager

app = Flask(__name__)

"""使用脚手架[终端脚本]启动项目"""
manage = Manager(app)

# 加载配置
class Config(object):
    DEBUG = True
    SECRET_KEY = 'jyh123'
app.config.from_object(Config)

@app.route('/')
def index():
    data = {}
    data['uid'] = int(request.args.get('uid'))
    data["book_list"] = [
        {"id": 1, "price": 20.50, "title": "javascript入门"},
        {"id": 2, "price": 75.50, "title": "python入门"},
        {"id": 3, "price": 68.50, "title": "django项目实战"}
    ]
    return render_template('index.html',**data)

if __name__ == '__main__':
    manage.run()

templats/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h1>欢迎来的py33期</h1>

<table>
    <tr>
        <th>id</th>
        <th>标题</th>
        <th>价格</th>
    </tr>
    {% for book in book_list %}
    <tr>
        <td>{{ book.id }}</td>
        <td>{{ book.title }}</td>
        <td>{{ book.price }}</td>
    </tr>
    {% endfor %}
</table>

{# 判断查询参数几奇偶性 #}
{% if uid %2 ==0 %}
    <h1>查询参数uid是偶数</h1>
{% else %}
    <h1>查询参数uid是奇数</h1>
{% endif %}

</body>
</html>

启动项目

python manage.py runserver
访问网站: http://127.0.0.1:5000/?uid=3

循环语句for

  • 在一个 for 循环块中你可以访问这些特殊的变量:
变量 描述
loop.index 当前循环迭代的次数(从 1 开始)
loop.index0 当前循环迭代的次数(从 0 开始)
loop.revindex 到循环结束需要迭代的次数(从 1 开始)
loop.revindex0 到循环结束需要迭代的次数(从 0 开始)
loop.first 如果是第一次迭代,为 True 。
loop.last 如果是最后一次迭代,为 True 。
loop.length 序列中的项目数。
loop.cycle 在一串序列间期取值的辅助函数。cycle函数会在每次循环的时候,返回其参数中的下一个元素

manage.py

from flask import Flask,render_template,request
from flask_script import Manager

app = Flask(__name__)

"""使用脚手架[终端脚本]启动项目"""
manage = Manager(app)

# 加载配置
class Config(object):
    DEBUG = True
    SECRET_KEY = 'jyh123'
app.config.from_object(Config)

@app.route('/')
def index():
    data = {}
    data['uid'] = int(request.args.get('uid'))
    data["book_list"] = [
        {"id": 1, "price": 20.50, "title": "javascript入门"},
        {"id": 2, "price": 75.50, "title": "python入门"},
        {"id": 3, "price": 68.50, "title": "django项目实战"}
    ]
    return render_template('index.html',**data)

if __name__ == '__main__':
    manage.run()

templates/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<table>
    <tr>
        <th>id</th>
        <th>标题</th>
        <th>价格</th>
    </tr>
    {% for book in book_list %}
    <tr>
        <td>{{ book.id }} -- 当前迭代次数:{{ loop.index }} -- 项目数:{{ loop.length }}</td>
        <td>{{ book.title }} -- 到循环结束需要迭代的次数:{{ loop.revindex }} -- {{ loop.cycle('odd','even') }}</td>
        <td>{{ book.price }} -- 是否第一次迭代:{{ loop.first }} -- 是否最后一次迭代:{{ loop.last }}</td>
    </tr>
    {% endfor %}
</table>
</body>
</html>

过滤器

过滤器的本质就是函数。有时候我们不仅仅只是需要输出变量的值,我们还需要修改变量的显示,甚至格式化、运算等等,而在模板中是不能直接调用 Python 中的某些方法,那么这就用到了过滤器。

过滤器的使用方式为:变量名 | 过滤器

{{variable | filter_name(*args)}}
{{variable | filter_name}}  # 如果没有任何参数传给过滤器,则可以把括号省略掉
{{ "hello world" | reverse | upper }} # 在jinja2中,过滤器是可以支持链式调用的

常见的内建过滤器,等等

字符串操作 作用 列表操作 作用
safe 禁止转义字符 first 取第一个元素
capitalize 把变量值的首字母大写,其他小写 last 取最后一个元素
lower 把变量值转成小写 length 获取列表长度
upper 把变量值转成大写 sum 列表求和
title 把变量值的每个单词首字母都转成大写 min 最小值
reverse 字符串反转 max 最大值
format 格式化输出 sort 列表排序
striptags 渲染之前把值中所有的HTML标签都删掉
truncate 字符串截断
# 语句块过滤 - 批量过滤
{% filter upper %}
<p>hello</p>
<p>world</p>
...
{% endfilter %}

视图: manage.py

from flask import Flask,render_template
from flask_script import Manager

# 初始化
app = Flask(__name__)

"""使用脚手架[终端脚本]启动项目"""
manage = Manager(app)

@app.route("/")
def index():
    return render_template('index.html')

if __name__ == '__main__':
    manage.run()

templates/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h1>字符串操作</h1>
<p>safe禁止转义 = {{ '<em>hello</em>' | safe }}</p>
<p>首字母大写 = {{ 'we are fimaly' | capitalize }}</p>
<p>把值转成小写 = {{ 'HELLO World !' | lower }}</p>
<p>把值转成大写 = {{ 'we are fimaly' | upper }}</p>
<p>每个单词的首字母都转成大写 = {{ 'we are fimaly' | title }}</p>
<p>字符串反转 = {{ 'hello' | reverse }}</p>
<p>格式化输出 = {{ '%s is %s' | format('我','好人') }}</p>
<p>删掉html标签 = {{ '<em>hello</em>' | striptags }}</p>
<p>字符串截断6个字符 = {{ 'we are fimaly' | truncate(6)  }}</p>

<h1>列表操作</h1>
<p>first = {{ [1,2,3,4,5]|first }}</p>
<p>last = {{ [1,2,3,4,5]|last }}</p>
<p>length = {{ [1,2,3,4,5]|length }}</p>
<p>sum = {{ [1,2,3,4,5]|sum }}</p>
<p>sort = {{ [3,4,2,5,1]|sort }}</p>
<p>max = {{ [3,4,2,5,1]|max }}</p>
<p>min = {{ [3,4,2,5,1]|min }}</p>
<p>去重 = {{ [1,3,3,5,5,1] | unique | list }}</p>

<!--代码块批量过滤-->
{% filter upper %}
<p>hello</p>
<p>world</p>
{% endfilter %}
    
</body>
</html>

启动项目:

python manage.py runserver -d

自定义过滤器

自定义过滤器有两种实现方式:

  • 一种是通过Flask应用对象的 add_template_filter(函数名,'自定义过滤器别名') 方法
  • 通过装饰器来实现自定义过滤器@app.template_filter('自定义过滤器别名')

manage.py

from flask import Flask,render_template
from flask_script import Manager

# 初始化
app = Flask(__name__)

"""使用脚手架[终端脚本]启动项目"""
manage = Manager(app)

# 自定义过滤器
'''1.通过注册方式自定义过滤器 - 实现列表反转'''
def list_reverse(data):
	# 过滤器必须有返回值
    return data[::-1]

# 注册过滤器
app.add_template_filter(list_reverse,'lrev')


'''2.通过装饰器方式自定义过滤器 - 实现手机号码的屏蔽效果'''
@app.template_filter('mobile')
def mobile(data,str):
    '''str是传入替换号码的符号参数'''
    return data[:3] + str*4 + data[7:]

@app.route("/")
def index():
    data = {}
    data['name_list'] = ['胸大','熊二','光头强']
    data["user_list"] = [
        {"id": 1, "name": "张一", "mobile": "13112345678"},
        {"id": 2, "name": "张二", "mobile": "13112345678"},
        {"id": 4, "name": "张三", "mobile": "13112345678"},
    ]
    return render_template('index.html',**data)

if __name__ == '__main__':
    manage.run()

templates/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h1>--列表反转--</h1>
<p>原列表 = {{ name_list }}</p>
<p>反转列表 = {{ name_list | lrev}}</p>

<h1>--手机号码屏蔽--</h1>
<table border="1" align="left" width="400">
    <tr>
        <th>ID</th>
        <th>姓名</th>
        <th>手机号</th>
    </tr>
    {% for user in user_list %}
     <tr>
         <td>{{ user.id }}</td>
         <td>{{ user.name }}</td>
         <td>{{ user.mobile | mobile(str='*') }}</td>
     </tr>
    {% endfor %}

</table>

</body>
</html>

运行项目:

python manage.py runserver -d

页面显示结果:

--列表反转--
原列表 = ['胸大', '熊二', '光头强']
反转列表 = ['光头强', '熊二', '胸大']

--手机号码屏蔽--
ID	姓名	手机号
1	张一	131****5678
2	张二	131****5678
4	张三	131****5678

模板继承

在模板中,可能会遇到以下情况:

  • 多个模板具有完全相同的顶部和底部内容
  • 多个模板中具有相同的模板代码内容,但是内容中部分值不一样
  • 多个模板中具有完全相同的 html 代码块内容

像遇到这种情况,可以使用 JinJa2 模板中的 继承 来进行实现

模板继承是为了重用模板中的公共内容。一般Web开发中,继承主要使用在网站的顶部菜单、底部。这些内容可以定义在父模板中,子模板直接继承,而不需要重复书写。

  • 标签定义的内容
# 定义block块
{% block top %}
各种内容..
{% endblock %}
  • 相当于在父模板中挖个坑,当子模板继承父模板时,可以指定block块进行填充。
  • 子模板使用 extends 指令声明这个模板继承自哪个模板
  • 父模板中定义的块在子模板中被重新定义,在子模板中调用父模板的内容可以使用{{ super() }}

视图:manage.py

from flask import Flask,render_template
from flask_script import Manager

# 初始化
app = Flask(__name__)

"""使用脚手架[终端脚本]启动项目"""
manage = Manager(app)

@app.route("/")
def index():
    # 直接跳到子模板
    return render_template('son.html')

if __name__ == '__main__':
    manage.run()

父模板 templates/father.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>父模板</title>
</head>
<body>

{% block top %}
<p>这是顶部导航栏</p>
{% endblock %}

{% block content %}
<p>父模板正文部分:我们都一样</p>
{% endblock %}

{% block bottom %}
<p>这是底部导航栏</p>
{% endblock bottom%}

</body>
</html>

**子模板 **templates/son.html

{% extends 'father.html' %} <!--继承模板-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

{% block content %}
{{ super() }} <!--调用父摸版的内容-->
<h1>继承父模板,修改正文内容</h1> <!--子模板修改的内容-->
{% endblock %}

</body>
</html>

运行项目

python manage.py runserver -d

在 Flask 项目中解决 CSRF 跨站伪造攻击

在 Flask 中, Flask-wtf 扩展有一套完善的 csrf 防护体系,对于我们开发者来说,使用起来非常简单

pip install flask-wtf

1.设置应用程序的 secret_key,用于加密生成的 csrf_token 的值

class Config(object):
    DEBUG = True
    SECRET_KEY = "jyh123" # 随机字符串
    
"""加载配置"""
app.config.from_object(Config)

2.导入 flask_wtf中的 CsrfProtect 类,进行初始化,并在初始化的时候关联 app

from flask_wtf import CsrfProtect
CsrfProtect(app)

3.在表单中使用 CSRF 令牌:

<form method="post" action="/">
    <input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
</form>

视图: manage.py

from flask import Flask,render_template
from flask_script import Manager
from flask_wtf import CsrfProtect

# 初始化
app = Flask(__name__,template_folder='templates')

"""使用脚手架[终端脚本]启动项目"""
manage = Manager(app)

# 初始化csrf防范跨站攻击机制
CsrfProtect(app)

# 加载配置项
class Config(object):
    '''csrf必须配置secret_key,用于加密生成的 csrf_token 的值'''
    SECRET_KEY = 'jyh123'
app.config.from_object(Config)

@app.route("/")
def index():
    return render_template('index.html')

@app.route("/login",methods=['post'])
def login():
    return '登录成功!'

if __name__ == '__main__':
    manage.run()

模板: templates/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<form action="{{ url_for("login") }}" method="post">
    <!--提交表单携带csrf_token值,才能通过csrf防范机制,否则提交不上去-->
    <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
    用户名:<input type="text" name="username"><br>
    密  码:<input type="password" name="password"><br>
    <input type="submit">
</form>

</body>
</html>

csrf怎么识别提交过来的csrf_token是正确的?

​ 原理和jwt认证是一样的。
​ csrf_token也是token令牌的应用方式,也是分 "头部.载荷.签证"
​ 在表单提交到服务端时,WTForms内部会直接截取提交的csrf_token的头部和载荷,并从flask中提取秘钥SECRET_KEY,生成一个新的签证,新签证与客户端提交的csrf_token签证进行字符串比较,相同则表示token没有问题。

posted @ 2021-05-24 15:42  十九分快乐  阅读(92)  评论(0编辑  收藏  举报