带有 WTforms 和 webapp2 的 CSRF
CSRF with WTforms and webapp2
WTForms 文档为 implementing CSRF with Flask 提供了一个很好的示例:
class MyBaseForm(Form):
class Meta:
csrf = True
csrf_class = SessionCSRF
csrf_secret = app.config['CSRF_SECRET_KEY']
@property
def csrf_context(self):
return session
我想做同样的事情,但使用 webapp2 会话而不是 Flask 会话。
csrf_context
是一种快捷方式,因此您不必在每次创建表单时都通过会话。有谁知道如何为 webapp2 会话创建这样的快捷方式?
如果没有这个快捷方式,每次创建表单时都需要这样做:
form = MyForm(meta={'csrf_context': self.session})
这是我希望避免的一些非常笨拙的语法。
我想出了一个解决方案,可以减少我的问题中描述的笨拙语法。我已经像这样修改了 wt.Form
子类的 __init__
:
class MyBaseForm(wt.Form):
class Meta:
csrf = True
csrf_class = SessionCSRF
csrf_secret = settings.SESSION_KEY
csrf_time_limit = timedelta(minutes=60)
def __init__(self, *args, **kwargs):
if "session" in kwargs:
super(MyBaseForm, self).__init__(
*args, meta={'csrf_context': kwargs["session"]}, **kwargs)
else:
super(MyBaseForm, self).__init__(*args, **kwargs)
现在,当我创建表单时,我可以这样做:
form = MyForm(session=self.session)
而不是问题中显示的笨拙语法。
为了处理 POSTed 表单数据,我想出了另一种技术来简化处理。
首先,我创建了一个表单,除了 CSRF 字段外没有其他字段:
class NoFieldForm(MyBaseForm):
pass
其次,我创建了一个用于检查 CSRF 的装饰器:
def check_csrf(func):
def wrapper(*args, **kwargs):
handler = args[0]
session = handler.session
request = handler.request
f = forms.NoFieldForm(request.POST, session=session)
f.validate()
if f.csrf_token.errors:
msg = "The CSRF token expired. Please try again. "
self.session["msg"] = msg
self.redirect(self.request.path)
else:
func(*args, **kwargs)
return wrapper
第三,我装饰了我所有的 POST 处理程序:
class SomeHandler(webapp2.RequestHandler):
@check_csrf
def post(self):
pass
让我解释一下。装饰器(通过调用 post webhandler)将接收一些表单数据,但出于 CSRF 检查的目的,我们将丢弃除 CSRF 之外的所有表单数据。我们可以通过使用忽略任何其他存在的表单数据的 NoFieldForm 来使其通用。我使用 wtforms 检查以确保 CSRF 表单字段和会话令牌匹配并且会话令牌没有过期。
如果CSRF通过,那么我们调用handler进行正常处理,来处理具体的表单。如果 CSRF 失败,那么我们根本不调用处理程序,在我的示例中,我们重定向回我们来自的错误消息。
WTForms 文档为 implementing CSRF with Flask 提供了一个很好的示例:
class MyBaseForm(Form):
class Meta:
csrf = True
csrf_class = SessionCSRF
csrf_secret = app.config['CSRF_SECRET_KEY']
@property
def csrf_context(self):
return session
我想做同样的事情,但使用 webapp2 会话而不是 Flask 会话。
csrf_context
是一种快捷方式,因此您不必在每次创建表单时都通过会话。有谁知道如何为 webapp2 会话创建这样的快捷方式?
如果没有这个快捷方式,每次创建表单时都需要这样做:
form = MyForm(meta={'csrf_context': self.session})
这是我希望避免的一些非常笨拙的语法。
我想出了一个解决方案,可以减少我的问题中描述的笨拙语法。我已经像这样修改了 wt.Form
子类的 __init__
:
class MyBaseForm(wt.Form):
class Meta:
csrf = True
csrf_class = SessionCSRF
csrf_secret = settings.SESSION_KEY
csrf_time_limit = timedelta(minutes=60)
def __init__(self, *args, **kwargs):
if "session" in kwargs:
super(MyBaseForm, self).__init__(
*args, meta={'csrf_context': kwargs["session"]}, **kwargs)
else:
super(MyBaseForm, self).__init__(*args, **kwargs)
现在,当我创建表单时,我可以这样做:
form = MyForm(session=self.session)
而不是问题中显示的笨拙语法。
为了处理 POSTed 表单数据,我想出了另一种技术来简化处理。
首先,我创建了一个表单,除了 CSRF 字段外没有其他字段:
class NoFieldForm(MyBaseForm):
pass
其次,我创建了一个用于检查 CSRF 的装饰器:
def check_csrf(func):
def wrapper(*args, **kwargs):
handler = args[0]
session = handler.session
request = handler.request
f = forms.NoFieldForm(request.POST, session=session)
f.validate()
if f.csrf_token.errors:
msg = "The CSRF token expired. Please try again. "
self.session["msg"] = msg
self.redirect(self.request.path)
else:
func(*args, **kwargs)
return wrapper
第三,我装饰了我所有的 POST 处理程序:
class SomeHandler(webapp2.RequestHandler):
@check_csrf
def post(self):
pass
让我解释一下。装饰器(通过调用 post webhandler)将接收一些表单数据,但出于 CSRF 检查的目的,我们将丢弃除 CSRF 之外的所有表单数据。我们可以通过使用忽略任何其他存在的表单数据的 NoFieldForm 来使其通用。我使用 wtforms 检查以确保 CSRF 表单字段和会话令牌匹配并且会话令牌没有过期。
如果CSRF通过,那么我们调用handler进行正常处理,来处理具体的表单。如果 CSRF 失败,那么我们根本不调用处理程序,在我的示例中,我们重定向回我们来自的错误消息。