如何在 CherryPy 中为 WTForms 集成验证码 (Recaptcha)

How to integrate Captcha (Recaptcha) for WTForms in CherryPy

我想在我的 WTForms 中使用 RecaptchaField(),例如:

class MyForm(Form):
    name = StringField('Your name', [InputRequired(message='Please enter your name!')])
    recaptcha = RecaptchaField() # RecaptchaField as provided by Flask-WTF
    submit = SubmitField('Send')

由于我使用的是CherryPy,我不确定是否应该使用Flask-WTF,因为Flask 本身就是一个完整的框架。我想知道我是否可以在我的 CherryPy 解决方案中使用 Flask-WTF 的 Recaptcha 功能。我尝试了以下方法:

from wtforms import StringField
from wtforms.validators import InputReqired
from flask.ext.wtf import Form
from flask.ext.wtf.recaptcha import RecaptchaField

# ...

form = MyForm() # Somewhere in my code

this Example here 所示。我得到以下异常:

RuntimeError: working outside of application context

这意味着我必须考虑到正确的上下文来正确设置 Flask 应用程序...这是我开始怀疑我是否采用了正确方法的地方。除了在我的 CherryPy 应用程序中设置一个单独的 Flask 应用程序之外没有其他方法吗??

我的回答主要与问题的 CherryPy 和 reCaptcha 部分相关。在我看来,使用 WTForms 和其他类似库会导致设计问题,就类似 MVC 的设计而言,您将视图和控制器分散到您自己的代码和 WTForms code/configuration 中。当您在一个地方管理一件事时,事情就简单了。因此,我建议在控制器中使用像 Jinja2 for the view (you can create a macro for a repetitive form element) and use input validation library like voluptuous 这样的模板引擎(您可以使用相同的模式进行表单和 API 验证)。

如果你无法避免 WTForms,只需抓住 validateRecaptcha 并将其变成 WTForms custom validator

#!/usr/bin/env python
# -*- coding: utf-8 -*-


import urllib
import json

import cherrypy
import voluptuous as volu


config = {
  'global' : {
    'server.socket_host' : '127.0.0.1',
    'server.socket_port' : 8080,
    'server.thread_pool' : 8
  },
  '/' : {
    'recaptcha' : {
        # By default, all keys work on localhost
       'siteKey' : '6LeYIbsSAAAAACRPIllxA7wvXjIE411PfdB2gt2J',
       'secret'  : '6LeYIbsSAAAAAJezaIq3Ft_hSTo0YtyeFG-JgRtu'
     }
  }
}

def validateRecaptcha(value):
  '''https://developers.google.com/recaptcha/docs/verify'''

  if 'g-recaptcha-response' not in cherrypy.request.params:
    raise volu.Invalid('Recaptcha response is missing') 

  payload = urllib.urlencode({
    'secret'   : cherrypy.request.config['recaptcha']['secret'],
    'remoteip' : cherrypy.request.headers['remote-addr'],
    'response' : cherrypy.request.params['g-recaptcha-response']
  })

  url      = 'https://www.google.com/recaptcha/api/siteverify'
  response = json.load(urllib.urlopen(url, payload))
  if not response['success']:
    cherrypy.log(str(response))
    raise volu.Invalid(response['error-codes'])


class App:

  @cherrypy.expose
  def index(self, **kwargs):
    form = dict(form = {'value': ''}, errors = '')

    if cherrypy.request.method == 'POST':
      schema = volu.Schema({
        'value'                : volu.All(unicode, volu.Length(min = 8, max = 16)),
        'g-recaptcha-response' : validateRecaptcha,
      }, required = True, extra = True)
      try:
        kwargs = schema(kwargs)
      except volu.MultipleInvalid as ex:
        form = dict(form = kwargs, errors = {e.path[0] for e in ex.errors})
      else:
        raise cherrypy.HTTPRedirect('#success')

    return '''<!DOCTYPE html>
      <html>
        <head>
          <title>reCAPTCHA demo</title>
          <script src="https://www.google.com/recaptcha/api.js" type="text/javascript"></script>
        </head>
        <body>
          <form action="/" method="POST">
            <div style='border: 1px red solid'>{errors}</div>
            <div>Name</div>
            <input type="text" name="value" value="{form[value]}"/>
            <br/>
            <div class="g-recaptcha" data-sitekey="{0}"></div>
            <br/>
            <input type="submit" value="Submit"/>
          </form>
        </body>
      </html>
    '''.format(cherrypy.request.config['recaptcha']['siteKey'], **form)


if __name__ == '__main__':
  cherrypy.quickstart(App(), '/', config)