Add Google reCaptcha to Java Spring

Preventing bots from submitting forms on a website or web app is an absolute must, especially when you're receiving hundreds of emails about highheel specials in Russia 🙂 For some reason I don't particularly like spelling out reCaptcha the whole time, so I've just gone with GoogleService etc. instead of RecaptchaService.

Step 1

The first step to adding reCaptcha is to create an object in which the response from Google will be kept.

package com.club100.org.service;

import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.stereotype.Service;

import java.io.Serializable;
import java.util.Collection;

@Service
public class GoogleResponse implements Serializable {
    
    @JsonProperty("success")
    private boolean success;

    @JsonProperty("error-codes")
    private Collection<String> errorCodes;

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public Collection<String> getErrorCodes() {
        return errorCodes;
    }

    public void setErrorCodes(Collection<String> errorCodes) {
        this.errorCodes = errorCodes;
    }
}

Step 2

The next step is to create the GoogleService interface and implementation which will house the method for actually validating whether the client entered the correct reCaptcha.

GoogleService

package com.club100.org.service;

public interface GoogleService {

    boolean validateReCaptcha(String remoteIp, String reCaptchaResponse);

}

GoogleServiceImpl.java

package com.club100.org.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;


@Service
public class GoogleServiceImpl implements GoogleService {

    private final String recaptchaUrl = "https://www.google.com/recaptcha/api/siteverify";
    private final String recaptchaSecretKey = "YOUR-SECRET-KEY-HERE";

    private RestTemplate restTemplate;

    @Autowired
    public RecaptchaServiceImpl(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @Override
    public boolean validateReCaptcha(String remoteIp, String reCaptchaResponse) {
        GoogleResponse googleResponse;
        try {
            googleResponse = restTemplate
                    .postForEntity(recaptchaUrl, createBody(recaptchaSecretKey, remoteIp, reCaptchaResponse), GoogleResponse.class)
                    .getBody();

        } catch (RestClientException e) {
            throw new RuntimeException();
        }

        return googleResponse.isSuccess();
    }

    private MultiValueMap<String, String> createBody(String secret, String remoteIp, String response) {

        MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
        form.add("secret", secret);
        form.add("remoteip", remoteIp);
        form.add("response", response);

        return form;
    }
}

Step 3

The next step is to pass the g-recaptcha-response from the controller to the service and perform the validation

Autowire the GoogleService

@Controller
@RequestMapping ("/misc")
public class SomeController {
    @Autowired
    private GoogleService googleService;
...    

Add HttpServletRequest as a parameter in the method handling the form submission. This method should also contain the logic determining whether or not the validation was successful.

SomeController.java

    @RequestMapping (value = "/referAFriend", method= RequestMethod.POST)
    public String referAFriend (ReferAFriend referAFriend, BindingResult result,RedirectAttributes attributes, HttpServletRequest request) throws MessagingException {

String reCaptchaResponse = request.getParameter("g-recaptcha-response");

        boolean isValid;

        try {
            isValid = recaptchaService.validateReCaptcha(getRemoteIp(request), reCaptchaResponse);
        } catch (Exception e) {
            attributes.addFlashAttribute("errorMessage","reCaptcha error, please try again");
            return "redirect:/";
        }

        if(!isValid) {
            attributes.addFlashAttribute("errorMessage", "The Captcha is missing or wrong");
            return "redirect:/";
        }
...

Step 4

The final step is to include the necessary HTML and Javascript on the frontend.

Add the following line before the closing head tag:

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

Add the following line in your HTML where you would like the reCaptcha box to be displayed:

<div class="g-recaptcha" data-sitekey="YOUR-SITE-KEY"></div>

Please note that this is your site key and NOT your secret key.

Add the following line in your HTML where you would like the reCaptcha error message to be displayed if any:

<div  th_if="${errorMessage}" th_text="${errorMessage}" class="alert alert-danger" role="alert"></div>

This is of course how information is obtained from the model using Thymeleaf.