reCAPTCHA Version 2 mit Rails verwenden

Seit kurzem arbeite ich mich in Ruby bzw. Rails ein. Dazu verwende ich ein kleines Beispiel-Projekt, was ich bereits bei anderen Frameworks zum Einstieg verwendet habe. Damit lässt sich die Funktionalität meines Erachtens besser erkunden als wenn man von Null an beginnen würde bzw. sich nur an den eher “fleischlosen” Beispielen aus Tutorials und Büchern orientiert.

Insofern sind meine Rails-Kenntnisse auch noch nicht besonders weit fortgeschritten, dennoch schildere ich im Folgenden kurz eine Lösung, die ich ad hoc so nicht bei meinen Recherchen gefunden habe. Google bietet mit reCAPTCHA eine einfach einzubindende Lösung zur Abwehr von SPAM bei Formulareingaben auf Web-Sites an. Mein Beispiel umfasst ein Feedback-Formular, was öffentlich zur Verfügung stehen könnte, insofern bietet sich hier der Einsatz des reCAPTCHA an.

Somit stand ich vor der Herausforderung, das reCAPTCHA in der neuen Version 2.0 in das Formular einzubauen. Vermutlich werden die Ruby-Experten an dieser Stelle betonen, dass es dafür fertige Pakete in Form der RubyGems gibt. Absolut richtig, und natürlich existieren auch Gems für reCAPTCHA. Doch gerade in der Einstiegsphase halte ich es für sinnvoll, eher den manuellen Weg einzuschlagen, als nur vorgefertigte Pakete zu verwenden. Der Lerneffekt ist einfach höher, und wenn man einmal die über die Implementierung Bescheid weiß, spricht auch nichts gegen dein Einsatz von vorhandenen Modulen.

Also los! ReCAPTCHA besteht aus zwei Teilen. Eine umfassende Einführung befindet sich auf den Seiten von Google, zunächst muss die Domain, auf der reCAPTCHA verwendet werden soll, bei Google registriert werden. Dieser Schritt ist binnen einer Minute erledigt, woraufhin man weiter von Google angeleitet wird.

Im ersten Schritt sind zwei JavaScript-Snippets auf der Formular-Seite zu integrieren. Vor dem schließenden head-Tag zunächst die Einbindung der reCAPTCHA-JavaScript-API:

<script src='https://www.google.com/recaptcha/api.js'></script>

An der Stelle, an der das reCAPTCHA-Widgett eingebunden werden soll, steht weiterer JavaScript-Code:

<div class="g-recaptcha" data-sitekey="Websiteschüssel"></div>

Als “Websiteschlüssel” ist der von Google erzeugte String einzusetzen, freundlicherweise setzt Google diesen bereits ein, ansonsten finden sich jene Angaben auf der Detailseite innerhalb der Admin-Übersicht.

Damit ist die Einbindung auf Client-Seite bereits erledigt. Das Formular sieht nun wie folgt aus:

recaptcha_v2

 

 

Nachdem die Bestätigung “Ich bin kein Roboter” angeklickt und von Google bestätigt ist, fühlt man sich doch gleich ein wenig besser…

Beim Absenden des Formulars wird nun neben der Verarbeitung der übrigen Felder der zweite Schritt des SPAM-Schutzes erledigt. Dazu muss ein POST-Request an die Google-API stattfinden, der den geheimen Schüssel (secret), die Remote-IP-Adresse (remoteip) und die Reponse aus dem reCAPTCHA-Widget (im Feld ‘g-recaptcha-response’) enthält. Die Antwort von Google erfolgt im JSON-Format und kann entweder success=true sein, in dem Fall ist alles ok, der Benutzer hat sich erfolgreich als (vermutlich) menschlich identifiziert. Im Fehlerfall kommt success=false zurück, daneben befinden sich Angaben über den Fehler im Array “error-codes”.

Der Einfachheit halber habe ich diese Funktionalität zunächst direkt im Controller umgesetzt. Im Folgenden der relevante Code:

require 'uri'
require 'net/http'
require 'json'

class XyzController < ApplicationController

    before_filter :check_recaptcha, :only => [:create]

    #[...]

    def create
      @xyz = Xyz.new(xyz_params)
    
      if not @recaptchaChecked
         # redirect_to ...
      end
      #[...]

    end

    def success
        render 'success'
    end

    private
  	
    def xyz_params
    	#[...]
    end
 
    def check_recaptcha
      uri = URI(" https://www.google.com/recaptcha/api/siteverify")

      https = Net::HTTP.new(uri.host, uri.port)
      https.use_ssl = true

      req = Net::HTTP::Post.new(uri)
      req.set_form_data('secret' => 'GEHEIMER SCHLÜSSEL',
        'remoteip' => request.remote_ip,
        'response' => params['g-recaptcha-response'])

      res = https.request(req)

      result = JSON.parse(res.body)
      if result['success'] == true
        @recaptchaChecked = true
      else
        @recaptchaChecked = false
      end

    end

end

 

Zunächst werden die benötigten Module eingebunden. Die Integration der Prüfung des reCAPTCHA findet mittels before_filter in der Methode check_recaptcha statt. Diese wird nur beim Anlegen, d.h. der create-Methode verwendet.

Innerhalb der create-Methode wird geprüft, ob die Prüfung des reCAPTCHA erfolgreich war. Es empfiehlt sich hierbei, das Formular, durch eine Fehlermeldung ergänzt, erneut auszugeben. Bis hierher war alles Standard, ggf. bereits durch Scaffolding erzeugt. Die entscheidende Prüfung findet in der Methode check_recaptcha statt. Darin wird der Request an die API ausgeführt, für den geheimen Schlüssel muss der von Google bereit gestellte String (secret) eingesetzt werden.

Das Ergebnis wird mittels JSON-Parser verarbeitet, so dass die Interpretation der Antwort möglich ist. Das war es auch schon. Tatsächlich ist die Integration sehr einfach, wenngleich an dieser Stelle bei weitem nicht perfekt. Die erste Verbesserung wäre insofern das Herauslösen der Prüf-Routine aus dem Controller. Diese kann an anderen Stellen wieder verwendet werden, insofern gehört die Methode in ein allgemeines Modul. Darüber hinaus könnten API-URL, Secret und Websiteschlüssel in Config-Variablen platziert werden. Der hauptsächliche Effekt bestand jedoch für mich darin, ein paar Library-Funktionen von Ruby kennen zu lernen, was angesichts der einfachen Beispiels auch gut funktioniert hat. So unterscheidet sich die Syntax zwar ein wenig von den bisher gewohnten Sprachen, aber die Funktionalität ist letztlich dieselbe.

Immerhin wieder einen kleinen Schritt weiter gekommen auf den Spuren von Rails… 🙂

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Tags:
Kategorie: Programmierung