问题描述
情况:
我在前端和 Flask api 服务器中使用 React。我想将数据从 React 发送到 api,完成此操作后,我想在处理数据之前使用 WTForms 对数据运行验证。该问题可能看起来与 CSRF Protection with Flask/WTForms and React 类似,但这并没有回答问题,请仔细阅读我为写出一个好问题付出了很多努力。
我有什么
目前数据作为json对象成功发送,其中键与wtform结构中的名称匹配,目的是让wtforms获取该json数据并将其插入对象并从那里正常处理
正在发送的 JSON 对象
{'username': 'tster','fullname': 'Tester test','phone': '038287827216','email': 'test@example.com','password': 'Tester1010','repeatPassword': 'Tester1010'}
整个概念是从 React 组件发送它并将其添加到 wtforms 以便相应地验证和使用。
Python 代码:
我尝试了多种不同的方法,这是最新的:
@bp.route('/api/register_user',methods=['POST','GET'])
def register():
""" End-point to register users """
if request.method == 'GET':
return ('',{'csrf_token': generate_csrf()})
elif request.method == 'POST':
req = request.get_json(force=True)
if validate_csrf(request.headers['X-Csrftoken']):
print('validated')
return{"message": "posted"}
这个概念是,如果我不能让它与 wtforms 一起工作,我可以手动生成 csrf_token,验证它,一旦验证通过,对数据运行手动验证,而不是依赖 wtforms,但是它似乎没有相应地验证。这取自https://flask-wtf.readthedocs.io/en/stable/api.html#flask_wtf.csrf.generate_csrf
在此之前我尝试过:
@bp.route('/api/register_user','GET'])
def register():
""" End-point to register users """
form = RegistrationForm()
print(request.headers)
if request.method == 'GET':
print(form.csrf_token._value())
return ('',{'csrf_token': form.csrf_token._value()})
elif request.method == 'POST':
req = request.get_json(force=True)
form = RegistrationForm.from_json(req)
print(form.username)
return{"message": "posted"}
else:
return { 'errors': form.errors }
这背后的想法是像其他情况一样发送 csrf_token,因为我在之前的链接中阅读了:
生成 CSRF 令牌。令牌是为请求缓存的,因此多次调用此函数将生成相同的令牌。
time_limit -- 令牌有效的秒数。默认值为 WTF_CSRF_TIME_LIMIT 或 3600 秒(60 分钟)。
阅读本文后,我从逻辑上认为这很有意义,然后我基本上可以从标准表单对象中获取 csrf_token,就像您通常对它们进行操作一样,然后当请求方法为 POST 时,我可以重新渲染表单并插入请求中来自 JSON 的数据,我的想法来自 https://wtforms-json.readthedocs.io/en/latest/
然而,这会遇到相同的行为,即无法访问从以下位置接收多个错误的表单对象中的数据:
print(form.username.data)
AttributeError: 'tuple' 对象没有属性 'data'
print(form.username)
(
因此我什至无法访问我插入到表单对象中的数据。 (如果它甚至像我想象的那样插入。)
在此之前:
我尝试了使用 wtforms 的标准操作方式:
@bp.route('/api/register_user',{'csrf_token': form.csrf_token._value()})
elif form.validate_on_submit():
req = request.get_json(force=True)
return{"message": "posted"}
else:
return { 'errors': form.errors }
现在它从未通过验证,这很奇怪,因为验证器只是 Datarequired()。 然而,它在测试时确实通过了 form.is_submitted(),但我在打印时遇到了同样的错误:
print(form.username.data)
AttributeError: 'tuple' 对象没有属性 'data'
print(form.username)
(
此外,反应代码必须稍微改变才能工作,否则它会发回错误的请求,请求必须是:
handleSubmit = (details) => {
const finalSend = {
'csrf_token': this.state.csrf_token,'username': this.state.usernameInput,'fullname': this.state.fullnameInput,'phone': this.state.phoneInput,'email': this.state.emailInput,'password': this.state.passwordInput,'repeatPassword': this.state.repeatPasswordInput
}
axios({
method: 'post',url: '/api/register_user',data: finalSend,headers: {
'content-type': 'application/json'
}
})
.then(res => res.json()).catch(e => console.log(e));
}
问题
所以在这之后我基本上要问的是:
- 如何让 wtforms 将数据作为 JSON 对象发送?
- 与 WTForms 分开操作会更好吗?如果是这样,如何操作?
- 如果我执行上述操作,如何设置 CSRF 安全性,正如我最近尝试的那样,但它无法按需要工作。
额外需要:
组件发送的JSON格式为:
{'csrf_token': 'ImQ5MjhlY2VlYzM5Zjg0NmY4ZTg0NDk5ZjNlMjlkNzVlZGM4OGZhY2Ui.YBPbuw.D6BW8XpwEyXySCaBBeS0jIKYabU','username': 'charlie','fullname': 'charlie char','phone': '344444444','email': 'cindy@example.com','password': 'charlieandcindy','repeatPassword': 'charlieandcindy'}
我正在使用的 WTForm:
class RegistrationForm(FlaskForm):
username = StringField('username',validators=[Datarequired()]),fullname = StringField('fullname',email = BooleanField('email',validators=[Datarequired(),Email()]),phone = StringField('phone',password = StringField('password',repeatPassword = StringField('repeatPassword',EqualTo('password')])
def validate_username(self,username):
print("validating username")
我还咨询了以下网站以寻求解决方案: Flask wtforms - 'UnboundField' object is not callable,dynamic field won't init properly https://wtforms.readthedocs.io/en/2.3.x/forms/ https://wtforms-json.readthedocs.io/en/latest/ Flask-WTF set time limit on CSRF token https://flask-wtf.readthedocs.io/en/stable/api.html#flask_wtf.csrf.generate_csrf How do I get the information from a meta tag with JavaScript? https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#uploading_json_data https://flask-wtf.readthedocs.io/en/stable/csrf.html#javascript-requests CSRF Protection with Flask/WTForms and React
我再次尽最大努力找到解决方案,并投入大量精力写出一个好问题,如果您还需要提问。
编辑
在这方面的更多工作我又玩了一些 wtforms_json 并让它稍微填充,但是它只填充了 repeatPassword 字段,我不确定为什么。
为了做到这一点,我不得不关闭 csrf 验证并在路由中使用以下代码:
@bp.route('/api/register_user',methods=['POST'])
def register():
""" End-point to register users """
json = request.get_json()
form = RegistrationForm.from_json(json)
print(request.get_json())
print(form.data)
if form.validate():
print("validated")
return{"message": "posted"}
else:
return { 'errors': form.errors }
打印 form.data 时的结果是设置了重复密码,但没有别的,不知道为什么......这个进步的来源在这里WTForms-JSON not working with FormFields
解决方法
我也找到了答案。
为了做到这一点,我最终使用了 json 方法中的 wtforms_json,如下所示:
@bp.route('/api/register_user',methods=['GET','POST'])
def register():
""" End-point to register users """
reg_json = request.get_json()
form = RegistrationForm.from_json(reg_json)
if form.validate():
user = Users(
username = form.username.data,name = form.fullname.data,phone = form.phone.data,email = form.email.data,password = guard.hash_password(form.password.data),roles = 'User'
)
user.save()
return{"message": "inserted successfully"}
else:
return { 'errors': form.errors }
以及不得不调整表格:
class RegistrationForm(FlaskForm):
username = StringField('username',validators=[DataRequired()])
fullname = StringField('fullname',validators=[DataRequired()])
email = StringField('email',validators=[DataRequired()])
phone = StringField('phone',validators=[DataRequired()])
password = StringField('password',validators=[DataRequired()])
repeatPassword = StringField('repeatPassword',validators=[DataRequired(),EqualTo('password')])
def validate_username(self,username):
""" Ensure email isn't in use """
user = Users.objects(username=username.data).first()
if user != None:
raise ValidationError('Please use a different username.')
唯一的缺点是我必须在配置中关闭 csrf_token:
WTF_CSRF_ENABLED = False