10

This is an Q&A style post, which I'll post both the question and an answer. The main reason for this is that I spent quite a lot of time searching the easiest way to validate recaptcha V2. So I'm going to share my knowledge to avoid further time wastage of developers.

How to do a server side validation of Google reCAPTCHA V2 or Invisible reCAPTCHA with Java?

Roshana Pitigala
  • 8,437
  • 8
  • 49
  • 80

2 Answers2

17

I'm using org.json library for this. Get the jar file from here or read the docs. Add the jar file to your project and import the following classes.

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import org.json.JSONObject;

Use the following method for validation.

/**
 * Validates Google reCAPTCHA V2 or Invisible reCAPTCHA.
 *
 * @param secretKey Secret key (key given for communication between your
 * site and Google)
 * @param response reCAPTCHA response from client side.
 * (g-recaptcha-response)
 * @return true if validation successful, false otherwise.
 */
public synchronized boolean isCaptchaValid(String secretKey, String response) {
    try {
        String url = "https://www.google.com/recaptcha/api/siteverify",
                params = "secret=" + secretKey + "&response=" + response;

        HttpURLConnection http = (HttpURLConnection) new URL(url).openConnection();
        http.setDoOutput(true);
        http.setRequestMethod("POST");
        http.setRequestProperty("Content-Type",
                "application/x-www-form-urlencoded; charset=UTF-8");
        OutputStream out = http.getOutputStream();
        out.write(params.getBytes("UTF-8"));
        out.flush();
        out.close();

        InputStream res = http.getInputStream();
        BufferedReader rd = new BufferedReader(new InputStreamReader(res, "UTF-8"));

        StringBuilder sb = new StringBuilder();
        int cp;
        while ((cp = rd.read()) != -1) {
            sb.append((char) cp);
        }
        JSONObject json = new JSONObject(sb.toString());
        res.close();

        return json.getBoolean("success");
    } catch (Exception e) {
        //e.printStackTrace();
    }
    return false;
}

Call the above method as shown below,

if(isCaptchaValid("enter_your_key_here", request.getParameter("g-recaptcha-response"))){
    //valid
}

Hope this helps. Cheers!


EDIT: Using the POST method to verify information as recommended by Google, is way more safer, however if you need the GET method version please refer the edit history.

Don't encode the params variable. You will always get the below response by doing so.

{"error-codes":["missing-input-response","missing-input-secret"],"success":false}
Roshana Pitigala
  • 8,437
  • 8
  • 49
  • 80
  • 1
    This also might be posted on the [code review site](https://codereview.stackexchange.com/) if you're interested in receiving commentary on your code. – markspace Dec 03 '17 at 19:50
  • Don't you need to include the remote user's IP address, too? – Rick Apr 13 '18 at 22:35
  • Ah, nvm, the remote IP is optional. – Rick Apr 13 '18 at 22:44
  • 1
    @Rick change the method on your will. Just add another parameter to the method and combine it to the url. :) – Roshana Pitigala Apr 14 '18 at 02:30
  • I don't think you should send private secret key like that. Anybody can see that in JS code or what? – Paul Aug 29 '18 at 20:18
  • @Pavol This is a server side code, and the request is sent by the server not by the client's browser. – Roshana Pitigala Aug 30 '18 at 02:05
  • 1
    I tried your code, it works, thank you. But official site (https://developers.google.com/recaptcha/docs/verify) is suggesting using POST method. Maybe using POST is safer. :) – Hosi Golden Jul 12 '19 at 06:16
  • @HosiGolden Thanks for mentioning. I updated my answer. Using the `POST` method is way better and safer. :) – Roshana Pitigala Nov 16 '19 at 18:38
  • @RoshanaPitigala thank you for the code, I'd like to ask why did you add synchronised to the method? – Mohammed R. El-Khoudary Apr 29 '21 at 14:43
  • @MohammedR.El-Khoudary to make it thread-safe. You can remove it. But make sure that multiple threads can't access the method at the same time. Since there's an HTTP call happening in the process it might take some time for one thread to finish executing the task. – Roshana Pitigala Apr 30 '21 at 09:15
  • @RoshanaPitigala sorry again, but this is thread safe even if it is executed by multiple threads, if I'm validating captchas I don't want all my system users queue to do so, or else it would create great latency on load, I don't see any part of the method thread unsafe. – Mohammed R. El-Khoudary Apr 30 '21 at 12:04
  • @MohammedR.El-Khoudary Methods are not tread-safe by default. Multiple threads can cause unexpected behaviors even in methods with simple logics. Since this is an instance method, if you provide new objects to all your threads then you are fine. And yes I completely agree, keeping users on a queue to validate is not a good idea. I added `synchronized` just to give an idea to readers that its better to make it thread safe. – Roshana Pitigala Apr 30 '21 at 13:38
  • @RoshanaPitigala thread safety isn't about the method itself, it's about its contents, what logic it holds, thread safety is about accessing shared state, this method contains no shared state, of course synchronised guarantees thread safety.. but the hard way.. as it makes execution so much slower. – Mohammed R. El-Khoudary May 01 '21 at 05:50
0

Just to provide another variant:

import javax.inject.Inject;
import javax.validation.constraints.NotNull;
import javax.ws.rs.client.*;
import javax.ws.rs.core.*;

import org.springframework.stereotype.Component;

import com.fasterxml.jackson.databind.*;

@Component
public class ReCaptcha {

    private final WebTarget webTarget;

    public ReCaptcha() {
        webTarget = ClientBuilder.newClient()
                .target("https://www.google.com/recaptcha/api/siteverify")
                .queryParam("secret", "6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe");
    }

    public boolean isValid(@NotNull String token) throws IOException {
        Response response = this.webTarget.queryParam("response", token)
                .request(MediaType.APPLICATION_JSON)
                .accept("application/ld+json")
                .get();

        if (response.getStatus() != 200)
            return false;

        String stringResponse = response.readEntity(String.class);
        JsonNode jsonNode = new ObjectMapper().readTree(stringResponse);
        return jsonNode.get("success").asBoolean();
    }
}

In addition you could validate the returned hostname and action. Also you might want to log returned error codes.

You will have to replace the used API key with your own (this is a test API key and should always return that the token is valid: https://developers.google.com/recaptcha/docs/faq) It also might be a good idea to put the API key and API url into a extra property file.

You can inject this class everywhere you like.

I use it with special exceptions instead of returning true or false.

Spenhouet
  • 6,556
  • 12
  • 51
  • 76