Google App Engine アプリで reCAPTCHA を利用する

※2016年6月30日に書いたブログ記事からの転記です。

# 1. reCAPTCHA にサイトを登録して site-key と secret-key を取得

まず、[reCAPTCHAのサイト](https://www.google.com/recaptcha/admin)で site-key と secret-key を取得します。

「Register a new site」というフォームがあるので、「Label」と「Domains」の欄を記入して「Register」をクリックします。

導入手順が表示され、site-key および secret-key が取得できます。

* site-key は reCAPTCHA ウィジェットを reCAPTHCA API から取得する際に送信されるトークン
* secret-key はサーバーサイドプログラムから reCAPTCHA の API に送信するトークン

この２つは、`app.yaml` に環境変数として記述しておきます。

```yaml
env_variables:
  # reCAPTCHA
  RECAPTCHA_SITE_KEY: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
  RECAPTCHA_SECRET_KEY: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
```

# 2. クライアントサイド（HTML）の実装

さて、では表示された導入手順にしたがって、まずはクライアントサイドの実装です。フォームに reCAPTCHA ウィジェットが表示されるようにHTMLを編集します。

```HTML
<html>
  <head>
    <script src='https://www.google.com/recaptcha/api.js'></script>
  </head>
  <body>
    <form method="post" action="">
      <label>コメント：<input type="text" name="comment" value=""></label>
        <div class="g-recaptcha" data-sitekey="{{ env.RECAPTCHA_SITE_KEY }}"></div>
    </form>
  </body>
</html>
```

こうすると、フォームに reCAPTCHA ウィジェットが表示されるようになります。

# 3.サーバーサイドの実装（Python での例）

次に、サーバーサイドの実装です。フォームから送信された値を受け取るリクエストハンドラーを作成します。

```Python
import json
import urllib

from google.appengine.api import urlfetch
from google.appengine.ext import ndb

import jinja2
import webapp2


class Comment(ndb.Model)
    created_at = ndb.DateTimeProperty(auto_now_add=True)
    body = ndb.TextProperty()


class CommentsHandler(webapp2.RequestHandler):

    def render(self, form_values={}, form_errors={}):

        query = Comment.query()
        query = query.order(-Comment.created_at)
        comments = query.fetch(25)

        template_values = {
            'form_values': form_values,
            'form_errors': form_errors,
            'comments': comments,
            'env': os.environ,
        }

        template = JINJA_ENVIRONMENT.get_template('comments.html')
        self.response.write(template.render(template_values))

    def get(self):

        self.render()

    def post(self):

        form_values = {}
        form_errors = {}

        form_values['comment'] = self.request.get('comment')

        if form_values['comment'] == '':
            form_errors['comment'] = u'コメント本文を入力してください'

        # reCAPTCHA認証
        recaptcha_url = 'https://www.google.com/recaptcha/api/siteverify'
        form_fields = {
            'secret': os.environ['RECAPTCHA_SECRET_KEY'],  # app.yaml に記述した環境変数から取得
            'response': self.request.get('g-recaptcha-response'),
            'remoteip': self.request.remote_addr,
        }

        form_data = urllib.urlencode(form_fields)
        response = urlfetch.fetch(
            url=recaptcha_url,
            payload=form_data,
            method=urlfetch.POST,
        )

        if response.status_code == 200:
            json_data = response.content
            decode_json_data = json.loads(json_data)
            if not decode_json_data['success']:
                form_errors['captcha'] = u'スパム判定をクリアしてください'
        else:
            self.abort(500)

        if len(form_errors) > 0:
            self.render(
                form_values=form_values,
                form_errors=form_errors,
            )
        else:
            comment_key = Comment(
                body=form_values['comment'],
            ).put()

            self.redirect('/comments/%s' % comment_key.urlsafe())  # 適当なURLにリダイレクト    
```

POST で送信されてくる reCAPTCHA ウィジェットの判定結果と secret-key、および IPアドレスを reCAPTHCA の API に渡してあげると、reCAPTCHA のサーバーが判定結果を JSON 形式で返してきます。

あとはその JSON をパースして `status_code` が `200` で　`decode_json_data['success']` が `True` だったらロボットではない、ということになります。

以上です。