flask_wtf flask 的 CSRF 源代码初研究
因为要搞一个基于flask的前后端分离的个人网站,所以需要研究下flask的csrf防护原理.
用的扩展是flask_wtf,也算是比较官方的扩展库了.
先上相关源代码:
def validate_csrf(data, secret_key=None, time_limit=None, token_key=None):
"""Check if the given data is a valid CSRF token. This compares the given
signed token to the one stored in the session. :param data: The signed CSRF token to be checked.
:param secret_key: Used to securely sign the token. Default is
``WTF_CSRF_SECRET_KEY`` or ``SECRET_KEY``.
:param time_limit: Number of seconds that the token is valid. Default is
``WTF_CSRF_TIME_LIMIT`` or 3600 seconds (60 minutes).
:param token_key: Key where token is stored in session for comparision.
Default is ``WTF_CSRF_FIELD_NAME`` or ``'csrf_token'``. :raises ValidationError: Contains the reason that validation failed. .. versionchanged:: 0.14
Raises ``ValidationError`` with a specific error message rather than
returning ``True`` or ``False``.
""" secret_key = _get_config(
secret_key, 'WTF_CSRF_SECRET_KEY', current_app.secret_key,
message='A secret key is required to use CSRF.'
)
field_name = _get_config(
token_key, 'WTF_CSRF_FIELD_NAME', 'csrf_token',
message='A field name is required to use CSRF.'
)
time_limit = _get_config(
time_limit, 'WTF_CSRF_TIME_LIMIT', 3600, required=False
) if not data:
raise ValidationError('The CSRF token is missing.') if field_name not in session:
raise ValidationError('The CSRF session token is missing.') s = URLSafeTimedSerializer(secret_key, salt='wtf-csrf-token') try:
token = s.loads(data, max_age=time_limit)
except SignatureExpired:
raise ValidationError('The CSRF token has expired.')
except BadData:
raise ValidationError('The CSRF token is invalid.') if not safe_str_cmp(session[field_name], token):
raise ValidationError('The CSRF tokens do not match.') class CSRFProtect(object):
"""Enable CSRF protection globally for a Flask app. :: app = Flask(__name__)
csrf = CsrfProtect(app) Checks the ``csrf_token`` field sent with forms, or the ``X-CSRFToken``
header sent with JavaScript requests. Render the token in templates using
``{{ csrf_token() }}``. See the :ref:`csrf` documentation.
""" def __init__(self, app=None):
self._exempt_views = set()
self._exempt_blueprints = set() if app:
self.init_app(app) def init_app(self, app):
app.extensions['csrf'] = self app.config.setdefault('WTF_CSRF_ENABLED', True)
app.config.setdefault('WTF_CSRF_CHECK_DEFAULT', True)
app.config['WTF_CSRF_METHODS'] = set(app.config.get(
'WTF_CSRF_METHODS', ['POST', 'PUT', 'PATCH', 'DELETE']
))
app.config.setdefault('WTF_CSRF_FIELD_NAME', 'csrf_token')
app.config.setdefault(
'WTF_CSRF_HEADERS', ['X-CSRFToken', 'X-CSRF-Token']
)
app.config.setdefault('WTF_CSRF_TIME_LIMIT', 3600)
app.config.setdefault('WTF_CSRF_SSL_STRICT', True) app.jinja_env.globals['csrf_token'] = generate_csrf <><><><><><><><><><><><><><><><><><><>
app.context_processor(lambda: {'csrf_token': generate_csrf}) @app.before_request
def csrf_protect():
if not app.config['WTF_CSRF_ENABLED']:
return if not app.config['WTF_CSRF_CHECK_DEFAULT']:
return if request.method not in app.config['WTF_CSRF_METHODS']:
return if not request.endpoint:
return view = app.view_functions.get(request.endpoint) if not view:
return if request.blueprint in self._exempt_blueprints:
return dest = '%s.%s' % (view.__module__, view.__name__) if dest in self._exempt_views:
return self.protect() def _get_csrf_token(self):
# find the ``csrf_token`` field in the subitted form
# if the form had a prefix, the name will be
# ``{prefix}-csrf_token``
field_name = current_app.config['WTF_CSRF_FIELD_NAME'] for key in request.form:
if key.endswith(field_name):
csrf_token = request.form[key] if csrf_token:
return csrf_token for header_name in current_app.config['WTF_CSRF_HEADERS']:
csrf_token = request.headers.get(header_name) if csrf_token:
return csrf_token return None def protect(self):
if request.method not in current_app.config['WTF_CSRF_METHODS']:
return try:
validate_csrf(self._get_csrf_token())
except ValidationError as e:
logger.info(e.args[0])
self._error_response(e.args[0]) if request.is_secure and current_app.config['WTF_CSRF_SSL_STRICT']:
if not request.referrer:
self._error_response('The referrer header is missing.') good_referrer = 'https://{0}/'.format(request.host) if not same_origin(request.referrer, good_referrer):
self._error_response('The referrer does not match the host.') g.csrf_valid = True # mark this request as CSRF valid
先说明下csrftoken的普通机制,上面代码中有一行代码后面被我加了一串<>符号,这行代码表明,默认的jinja2渲染的方式就是通过generate_csrf 方法生成csrftoken字符串,所以前后端分离的话,可以直接通过这个方法获取csrftoken,效果是一样的.
进入generate_csrf函数内部,会发现他做了这么点事:生成token,放在session里,然后返回一个加工过的token.这一块说明每当不同的访问触发该函数,那么服务器session内的csrftoken值就会不一样,所以,你可以这么做,获取一次之后在有效期(一个小时内)可以重复使用,但是不建议这么做.然后如果不是form表单提交的话,该csrf系统不会从json中获取token,而会从请求头获取,所以需要在请求头内添加关键字段:X-CSRFToken,将这个值赋值为获取的token即可.
首先获取csrftoken的方式: _get_csrf_token
会先从表单中查找关键字段,如果获取,那么返回该值,获取不到,从请求头获取,方式和django的基本一致,毕竟也就这两种规范方式.
91 @app.before_request
92 def csrf_protect():
这两行代码表明wtf是如何实现校验的,通过flask的钩子函数在每次请求开始时进行校验,这是在初始化wtf init_app(app)的时候就已经添加了该钩子函数.
在django里面,一旦中间件的process_request返回任何值,中间件即开始执行响应回调,视图不在执行,那么上面的两行代码下面好像不停地return了好多次,到底啥意思呢,只好再找源码看看.相关源码在下面:
@setupmethod
def before_request(self, f):
"""Registers a function to run before each request. For example, this can be used to open a database connection, or to load
the logged in user from the session. The function will be called without any arguments. If it returns a
non-None value, the value is handled as if it was the return value from
the view, and further request handling is stopped.
"""
self.before_request_funcs.setdefault(None, []).append(f)
return f
可以看到添加钩子函数的装饰器执行了什么操作,他只是把钩子函数放进了一个函数列表里,然后我们看看这个函数列表是什么方式处理的.源码如下:
def preprocess_request(self):
"""Called before the request is dispatched. Calls
:attr:`url_value_preprocessors` registered with the app and the
current blueprint (if any). Then calls :attr:`before_request_funcs`
registered with the app and the blueprint. If any :meth:`before_request` handler returns a non-None value, the
value is handled as if it was the return value from the view, and
further request handling is stopped.
""" bp = _request_ctx_stack.top.request.blueprint funcs = self.url_value_preprocessors.get(None, ())
if bp is not None and bp in self.url_value_preprocessors:
funcs = chain(funcs, self.url_value_preprocessors[bp])
for func in funcs:
func(request.endpoint, request.view_args) funcs = self.before_request_funcs.get(None, ())
if bp is not None and bp in self.before_request_funcs:
funcs = chain(funcs, self.before_request_funcs[bp])
for func in funcs:
rv = func()
if rv is not None:
return rv
该方法的注释说明了,如果钩子函数返回任意不为空的数据,那么等同于视图的响应,所以仅仅return 不会导致钩子函数结束,仍然可以访问视图.
现在可以解释def csrf_protect():函数的内容了,即,请求方式不在保护范围内时,跳过校验,未开启防护时,跳过校验,视图无效时跳过校验.
csrf_protect 中会执行 protect ,protect 会执行 validate_csrf(),validate_csrf()是校验的关键,源代码如下:
def validate_csrf(data, secret_key=None, time_limit=None, token_key=None):
"""Check if the given data is a valid CSRF token. This compares the given
signed token to the one stored in the session. :param data: The signed CSRF token to be checked.
:param secret_key: Used to securely sign the token. Default is
``WTF_CSRF_SECRET_KEY`` or ``SECRET_KEY``.
:param time_limit: Number of seconds that the token is valid. Default is
``WTF_CSRF_TIME_LIMIT`` or 3600 seconds (60 minutes).
:param token_key: Key where token is stored in session for comparision.
Default is ``WTF_CSRF_FIELD_NAME`` or ``'csrf_token'``. :raises ValidationError: Contains the reason that validation failed. .. versionchanged:: 0.14
Raises ``ValidationError`` with a specific error message rather than
returning ``True`` or ``False``.
""" secret_key = _get_config(
secret_key, 'WTF_CSRF_SECRET_KEY', current_app.secret_key,
message='A secret key is required to use CSRF.'
)
field_name = _get_config(
token_key, 'WTF_CSRF_FIELD_NAME', 'csrf_token',
message='A field name is required to use CSRF.'
)
time_limit = _get_config(
time_limit, 'WTF_CSRF_TIME_LIMIT', 3600, required=False
) if not data:
raise ValidationError('The CSRF token is missing.') if field_name not in session:
raise ValidationError('The CSRF session token is missing.') s = URLSafeTimedSerializer(secret_key, salt='wtf-csrf-token') try:
token = s.loads(data, max_age=time_limit)
except SignatureExpired:
raise ValidationError('The CSRF token has expired.')
except BadData:
raise ValidationError('The CSRF token is invalid.') if not safe_str_cmp(session[field_name], token):
raise ValidationError('The CSRF tokens do not match.')
该方法前面部分就是在获取相关秘钥和关键字,如果不自己自定义的话,这一块通常不会出问题,后面可以看到,方法会从全局变量session中寻找csrftoken字段名,然后最后一步进行校验,所以,wtf是通过比对session中的CSRFtoken和表单中的csrftoken是否一致.
所以前后端分离方式开发的话,需要将csrftoken通过接口或者cookie的方式传给前端,前端将该部分数据取出保存,提交表单的时候带上.
至于关键字,最上面那段代码写的很清楚,默认的,表单是csrf_token, 请求头是 X-CSRFToken.
flask_wtf flask 的 CSRF 源代码初研究的更多相关文章
- 关于flask线程安全的简单研究
flask是python web开发比较主流的框架之一,也是我在工作中使用的主要开发框架.一直对其是如何保证线程安全的问题比较好奇,所以简单的探究了一番,由于只是简单查看了源码,并未深入细致研究,因此 ...
- TensorFlow 源代码初读感受
把自己微博发的文章:http://www.weibo.com/1804230372/En7PdlgLb?from=page_1005051804230372_profile&wvr=6& ...
- Flask开发系列之初体验
Flask开发初探 介绍 在日常开发中,如果需要开发一个小型应用或者Web接口,一般我是极力推崇Flask的,主要是因为其简洁.扩展性高. 从这篇文章开始,我会写一个关于Flask的系列文章,通过多个 ...
- (4)Flask项目模板渲染初体验
一.准备静态资源 将项目使用到的静态资源拷贝到static目录 二.创建前台首页html 创建templates/home/home.html页面,内容包含导航和底部版权两部分,中间内容区域为模板标签 ...
- QWidget 键盘事件 焦点(源代码级别研究)
在Qt中,键盘事件和QWidget的focus密不可分:一般来说,一个拥有焦点(focus)的QWidget或者grabKeyboard()的QWidget才可以接受键盘事件. 键盘事件派发给谁? 如 ...
- Prism初研究之使用Prism实现WPF的MVVM模式
转自:http://www.cnblogs.com/qianzi067/p/5804880.html
- 19、Flask实战第19天:CSRF攻击与防御
CSRF攻击原理 网站是通过cookie来实现登录功能的.而cookie只要存在浏览器中,那么浏览器在访问这个cookie的服务器的时候,就会自动的携带cookie信息到服务器上去.那么这时候就存在一 ...
- csrf原理及flask的处理方法
csrf原理及flask的处理方法 为什么需要CSRF? Flask-WTF 表单保护你免受 CSRF 威胁,你不需要有任何担心.尽管如此,如果你有不包含表单的视图,那么它们仍需要保护. 例如,由 A ...
- Inside Flask - Flask 简介
Inside Flask - Flask 简介 前言 Flask 的设计目标是实现一个 wsgi 的微框架,其核心代码保持简单和可扩展性,很容易学习.对于有一定经验初学者而言,跟着例子和一些书的代码来 ...
随机推荐
- 除了 Microsoft Office我们还可以选择哪些软件?
不同的人有不同爱好,不同的人有着不同的人生追求,软件公司也是如此.尽管 Microsoft Office 比之前要便宜得多了,但其按时间累计的完整的安装版本的价格仍然很高,基于对普通用户亦或手头比较紧 ...
- ubuntu -redis
ubentu 布置redis,基本操作和CentO感觉相差不多,主要是使用命令有所差异 mark如下: ① download ② tar -zxvf xxx.tar.gz ③ cd redis-xxx ...
- WHU 1538 Stones II 动态规划
赛后写的,动态规划,学长和题解,提供了两种状态设计的思路,都写了下……结果在写第二种的时候,不小心把下标的起点写错了,导致WA了无数发…… 无奈啊……每次都是这种错误…… 题意: 大概就是有n块石头, ...
- 洛谷 P1096 Hanoi双塔问题
P1096 Hanoi双塔问题 题目描述 给定A.B.C三根足够长的细柱,在A柱上放有2n个中间有孔的圆盘,共有n个不同的尺寸,每个尺寸都有两个相同的圆盘,注意这两个圆盘是不加区分的(下图为n=3的情 ...
- Java 中的事件监听机制
看项目代码时遇到了好多事件监听机制相关的代码.现学习一下: java事件机制包含三个部分:事件.事件监听器.事件源. 1.事件:继承自java.util.EventObject类,开发人员自己定义. ...
- Codeforces 528A Glass Carving STL模拟
题目链接:点击打开链接 题意: 给定n*m的矩阵.k个操作 2种操作: 1.H x 横向在x位置切一刀 2.V y 竖直在y位置切一刀 每次操作后输出最大的矩阵面积 思路: 由于行列是不相干的,所以仅 ...
- C语言keywordstatic的绝妙用途
为什么要说static妙,它确实是妙,在软件开发或者单片机开发过程中,大家总以为static就是一个静态变量.在变量类型的前面加上就自己主动清0了.还有就是加上statickeyword的,无论是变量 ...
- BZOJ5204: [CodePlus 2018 3 月赛]投票统计
[传送门:BZOJ5204] 简要题意: 有n个选手,每个选手会选择一道题投票,求出投票最多的题目个数和这些题目的编号,如果所有题目的投票数相同,则输出-1 题解: 直接搞 离散化,然后判断就可以了 ...
- poj--1237--Drainage Ditches(最大流)
Drainage Ditches Time Limit: 1000MS Memory Limit: 10000KB 64bit IO Format: %I64d & %I64u Sub ...
- 124.C++输出小结
#include <iostream> #include <iomanip> using namespace std; void main() { ////调用cout的成员函 ...