reCAPTCHA V2 and Vaadin 7

We all know captchas, a blessing for one and a curse for another. Google has finally put an end to this and released a user-friendly captcha called reCAPTCHA V2. Thus it was over for all time cryptic and barely legible characters that you had to type.In most cases Frameworks, CMS and other systems offer ready-to-use solutions that you just have to activate and everything works right from the start. In my case, the long search for a suitable solution was unfortunately not successful and the existing solutions, with illegible characters, were not an option. Finally, user-friendly interface is very high in the course.Vaadin is continuoulsy involved in Performing Performance Testing in order to Keep the Performance of the tools upto the mark.Our software MyMs uses the Vaadin Framework for the visual presentation of the application in sap. The solution I developed contains only the important aspects of reCAPTCHA V2. It offers the public test keys and allows easy porting to other projects with Vaadin 7.5+. package de.epicsoft.recaptcha.ui; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.Set; import org.apache.http.HttpEntity; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.ObjectMapper; import com.vaadin.server.Page; import com.vaadin.server.VaadinSession; import com.vaadin.spring.annotation.UIScope; import com.vaadin.ui.Component; import com.vaadin.ui.CustomLayout; import com.vaadin.ui.JavaScript; /* * The MIT License - https://opensource.org/licenses/MIT * * Copyright (c) 2015-2016 epicsoft.de / Alexander Schwarz * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software * without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit * persons to whom the Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. / /* * Using Google reCAPTCHA v2 in Vaadin 7

  • Content-Security-Policy see https://developers.google.com/recaptcha/docs/faq
  • Dependencies:
    • SLF4J API Module
    • Apache HttpClient
    • Jackson Databind

      *
  • Use ReCaptchaV2 inside an {@link UIScope} / {@link VaadinSession} scope.

    *
  • Copyright epicsoft.de @author Alexander Schwarz * * @version 1.5 */ @com.vaadin.annotations.JavaScript("https://www.google.com/recaptcha/api.js") public class ReCaptchaV2 extends CustomLayout { private static final long serialVersionUID = 4402703000633787627L; private static transient final Logger log = LoggerFactory.getLogger(ReCaptchaV2.class); // test public and private keys see https://developers.google.com/recaptcha/docs/faq public static transient final String TEST_SITE_KEY = "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI"; public static transient final String TEST_SECRET_KEY = "6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe"; // placeholder for reCAPTCHA private static transient final String DIV = "
 
"; // URL for verification private static transient final String VERIFY_URL = "https://www.google.com/recaptcha/api/siteverify?secret={key}&response={res}"; // optional part of verify URL private static transient final String VERIFY_URL_IP = "&remoteip={ip}"; // public and private key. testing as default private transient String siteKey = TEST_SITE_KEY; private transient String secretKey = TEST_SECRET_KEY; // response private transient String response; private transient String widgetId = ""; // Disabled Component for example login button private transient final Set disabledComponents = new HashSet<>(); /** * default constructor. Use only for Test !!! */ public ReCaptchaV2() { this(TEST_SITE_KEY, TEST_SECRET_KEY); this.setImmediate(true); } /** * Constructor for productive mode, use your own public and private key. see https://www.google.com/recaptcha * * @param keySite * @param keySecret */ public ReCaptchaV2(final String keySite, final String keySecret) { super(); this.siteKey = keySite; this.secretKey = keySecret; } /** * Init the Layout. Insert custom HTML (Placeholder) and create JavaScript-Callback for response. */ public void init() { // custom HTML final String html = DIV.replace("{SITE_KEY}", this.siteKey); this.setTemplateContents(html); // JavaScript callbacks final JavaScript currentJS = JavaScript.getCurrent(); currentJS.addFunction("reCaptchaSuccessCallback", arguments -> { this.response = arguments.asString(); this.enableComponents(); }); currentJS.addFunction("reCaptchaSetCurrentWidgetId", arguments -> { this.widgetId = arguments.asString(); }); } /** * Resets the reCAPTCHA widget. */ public void reset() { this.response = null; final JavaScript currentJS = JavaScript.getCurrent(); if (currentJS != null) { currentJS.execute("grecaptcha.reset(" + this.widgetId + ")"); } } /** * Renders the container as a reCAPTCHA widget.
* Use this method if reCAPTCHA must be reloaded (rerender), for example in a window which comes up again.
* Example 1:
* final Window window = new Window(); window.addFocusListener(event -> reCaptcha.render());}
* Example 2:
* @Override public void enter(final ViewChangeEvent event){ this.reCaptcha.render(); } */ public void render() { final String parameters = "{ 'sitekey' : '" + this.siteKey + "', 'callback' : reCaptchaSuccessCallback }"; JavaScript.getCurrent().execute("reCaptchaSetCurrentWidgetId(grecaptcha.render(document.getElementById('g-recaptcha'), " + parameters + "))"); } /** * check if reCAPTCHA answer is valid * * @return {@link Boolean} */ public boolean isValid() { boolean success = false; // Check only, when responce available if (this.response != null) { final String url = this.buildVerifyUrl(); success = this.externalHttpCall(url); } else { log.error("response is empty"); } return success; } /** * build the Verify URL for external Call * * @return {@link String} */ private String buildVerifyUrl() { String url = VERIFY_URL.replace("{key}", this.secretKey).replace("{res}", this.response); // Current client IP-Address final String ipAddress = Page.getCurrent().getWebBrowser().getAddress(); if (ipAddress != null && ipAddress.length() > 0) { url += VERIFY_URL_IP.replace("{ip}", ipAddress); } return url; } /** * Do the external HTTP Call to reCAPTCHA Service * * @param url * @return {@link Boolean} */ private boolean externalHttpCall(final String url) { boolean success = false; try (final CloseableHttpClient httpClient = HttpClients.createDefault()) { final HttpPost post = new HttpPost(url); final String body = httpClient.execute(post, response -> { final int status = response.getStatusLine().getStatusCode(); if (status >= 200 && status < 300) { final HttpEntity entity = response.getEntity(); return entity != null ? EntityUtils.toString(entity) : null; } throw new ClientProtocolException("Unexpected response status: " + status); }); @SuppressWarnings("unchecked") final HashMap result = new ObjectMapper().readValue(body, HashMap.class); success = (boolean) result.get("success"); } catch (final IOException e) { log.error(e.getMessage(), e); } return success; } /** * Enable disabled Compoenent for example Login-Button */ private void enableComponents() { for (final Component component : this.disabledComponents) { if (component != null && !component.isEnabled()) { component.setEnabled(true); } } } // ------------------------------------------------------------------------------- // GETTER / SETTER --------------------------------------------------------------- // ------------------------------------------------------------------------------- public void setSiteKey(final String siteKey) { this.siteKey = siteKey; } public void setSecretKey(final String secretKey) { this.secretKey = secretKey; } public void addDisabledComponent(final Component disabledComponent) { this.disabledComponents.add(disabledComponent); } public void removeDisabledComponent(final Component disabledComponent) { this.disabledComponents.remove(disabledComponent); } } Source:[Epicsoft] (https://www.epicsoft.de/recaptcha-v2-und-vaadin-7/)