├── .travis.yml ├── .gitignore ├── src ├── main │ └── java │ │ ├── com │ │ └── google │ │ │ └── common │ │ │ └── annotations │ │ │ └── VisibleForTesting.java │ │ └── de │ │ └── triology │ │ └── recaptchav2java │ │ ├── ReCaptchaException.java │ │ ├── ReCaptchaJson.java │ │ ├── ReCaptcha.java │ │ ├── Http.java │ │ └── ReCaptchaEndPoint.java └── test │ ├── java │ └── de │ │ └── triology │ │ └── recaptchav2java │ │ ├── HttpTest.java │ │ ├── ReCaptchaTests.java │ │ ├── ReCaptchaJsonTest.java │ │ └── ReCaptchaTest.java │ └── resources │ └── logback-test.xml ├── LICENSE ├── .editorconfig ├── README.md └── pom.xml /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Maven template 3 | target/ 4 | pom.xml.tag 5 | pom.xml.releaseBackup 6 | pom.xml.versionsBackup 7 | pom.xml.next 8 | release.properties 9 | dependency-reduced-pom.xml 10 | buildNumber.properties 11 | .mvn/timing.properties 12 | 13 | # Avoid ignoring Maven wrapper jar file (.jar files are usually ignored) 14 | !/.mvn/wrapper/maven-wrapper.jar 15 | 16 | ### JetBrains 17 | /TestDataLoader.iml 18 | *.iml 19 | /.idea/ 20 | -------------------------------------------------------------------------------- /src/main/java/com/google/common/annotations/VisibleForTesting.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2006 The Guava Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package com.google.common.annotations; 15 | 16 | /** 17 | * Annotates a program element that exists, or is more widely visible than otherwise necessary, only 18 | * for use in test code. 19 | * 20 | * @author Johannes Henkel 21 | */ 22 | public @interface VisibleForTesting { 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 TRIOLOGY GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # 2 | # MIT License 3 | # 4 | # Copyright (c) 2017 TRIOLOGY GmbH 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in all 14 | # copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | # 24 | 25 | root = true 26 | 27 | [*] 28 | end_of_line = lf 29 | insert_final_newline = true 30 | charset = utf-8 31 | indent_style = space 32 | indent_size = 4 33 | trim_trailing_whitespace = true 34 | tab_width = 4 35 | 36 | [Makefile] 37 | indent_style = tab 38 | tab_width = 8 39 | 40 | # java properties must be saved in latin1 41 | [*.properties] 42 | charset = latin1 43 | -------------------------------------------------------------------------------- /src/main/java/de/triology/recaptchav2java/ReCaptchaException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 TRIOLOGY GmbH 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package de.triology.recaptchav2java; 25 | 26 | /** 27 | * Indicates that something technically went wrong during captcha validation. 28 | */ 29 | @SuppressWarnings("WeakerAccess") // This is the public API! 30 | public class ReCaptchaException extends RuntimeException { 31 | 32 | public ReCaptchaException(String message, Throwable cause) { 33 | super(message, cause); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/de/triology/recaptchav2java/HttpTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 TRIOLOGY GmbH 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package de.triology.recaptchav2java; 25 | 26 | import org.junit.Rule; 27 | import org.junit.Test; 28 | import org.junit.rules.ExpectedException; 29 | 30 | public class HttpTest { 31 | 32 | @Rule 33 | public ExpectedException expectedException = ExpectedException.none(); 34 | 35 | @Test 36 | public void handlesInvalidUrl() throws Exception { 37 | expectedException.expect(ReCaptchaException.class); 38 | Http.post("not an URL!", ""); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 27 | 28 | 29 | 30 | 31 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/main/java/de/triology/recaptchav2java/ReCaptchaJson.java: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 TRIOLOGY GmbH 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package de.triology.recaptchav2java; 25 | 26 | 27 | import org.slf4j.Logger; 28 | import org.slf4j.LoggerFactory; 29 | 30 | import javax.json.Json; 31 | import javax.json.JsonObject; 32 | import javax.json.JsonReader; 33 | import javax.json.stream.JsonParsingException; 34 | import java.io.StringReader; 35 | 36 | /** 37 | * JSON-related logic for reCAPTCHA. 38 | */ 39 | class ReCaptchaJson { 40 | private static final Logger LOG = LoggerFactory.getLogger(ReCaptchaJson.class); 41 | 42 | private String json; 43 | 44 | ReCaptchaJson(String json) { 45 | this.json = json; 46 | } 47 | 48 | boolean isSuccess() { 49 | try { 50 | return readJsonObject().getBoolean("success"); 51 | } catch (JsonParsingException e) { 52 | LOG.warn("Error parsing JSON. Defensively returning false", e); 53 | return false; 54 | } 55 | } 56 | 57 | private JsonObject readJsonObject() { 58 | try (JsonReader reader = Json.createReader(new StringReader(json))) { 59 | return reader.readObject(); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/de/triology/recaptchav2java/ReCaptchaTests.java: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 TRIOLOGY GmbH 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package de.triology.recaptchav2java; 25 | 26 | class ReCaptchaTests { 27 | 28 | private static final String BODY_TEMPLATE = "{\n" 29 | + " \"success\": %s,\n" // true|false 30 | + " \"challenge_ts\": \"2017-06-26T09:31:42+0200\",\n" // timestamp of the challenge load (ISO format yyyy-MM-dd'T'HH:mm:ssZZ) 31 | + " \"hostname\": \"localhost\"" // the hostname of the site where the reCAPTCHA was solved 32 | + "%s" // optional error codes: ,"error-codes": [...] 33 | + "}"; 34 | 35 | private static final String BODY_ERROR_CODES_TEMPLATE = ",\n\"error-codes\": [%s]"; 36 | 37 | private ReCaptchaTests() { 38 | } 39 | 40 | static String createBodySuccess() { 41 | return String.format(BODY_TEMPLATE, "true", ""); 42 | } 43 | 44 | static String createBodyFailure() { 45 | return String.format(BODY_TEMPLATE, "false", 46 | String.format(BODY_ERROR_CODES_TEMPLATE, "\"invalid-input-secret\", \"invalid-input-response\"")); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/de/triology/recaptchav2java/ReCaptchaJsonTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 TRIOLOGY GmbH 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package de.triology.recaptchav2java; 25 | 26 | import org.junit.Test; 27 | 28 | import static com.google.common.truth.Truth.assertThat; 29 | import static de.triology.recaptchav2java.ReCaptchaTests.createBodyFailure; 30 | import static de.triology.recaptchav2java.ReCaptchaTests.createBodySuccess; 31 | 32 | public class ReCaptchaJsonTest { 33 | @Test 34 | public void isSuccessTrue() { 35 | assertThat(new ReCaptchaJson(createBodySuccess()).isSuccess()).isTrue(); 36 | } 37 | 38 | @Test 39 | public void isSuccessFalse() { 40 | assertThat(new ReCaptchaJson(createBodyFailure()).isSuccess()).isFalse(); 41 | } 42 | 43 | @Test 44 | public void canCopeWithWitespaces() { 45 | String jsonSuccessWithWithespaces = "{\n\n\"success\": \n true \n }"; 46 | assertThat(new ReCaptchaJson(jsonSuccessWithWithespaces).isSuccess()).isTrue(); 47 | } 48 | 49 | @Test 50 | public void handlesInvalidJson() { 51 | assertThat(new ReCaptchaJson("not json").isSuccess()).isFalse(); 52 | } 53 | 54 | @Test 55 | public void handlesEmptyJson() { 56 | assertThat(new ReCaptchaJson("").isSuccess()).isFalse(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/de/triology/recaptchav2java/ReCaptcha.java: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 TRIOLOGY GmbH 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package de.triology.recaptchav2java; 25 | 26 | import com.google.common.annotations.VisibleForTesting; 27 | 28 | /** 29 | * Java abstraction for verifying a response token generate by Google's reCAPTCHA V2 client widget on server side. 30 | */ 31 | @SuppressWarnings("WeakerAccess") // This is the public API! 32 | public class ReCaptcha { 33 | private final String secret; 34 | 35 | /** 36 | * Creates a new ReCaptcha service using a specific secret key. 37 | * 38 | * @param secret the server-side secret. Most not be {@code null}! 39 | */ 40 | public ReCaptcha(String secret) { 41 | this.secret = secret; 42 | } 43 | 44 | /** 45 | * Validates a response token generate by Google's reCAPTCHA V2 client widget. 46 | * 47 | * @param captchaResponseToken the result of a captcha challenge. 48 | * @return {@code true} if the challenge was successful, otherwise {@code false} 49 | * @throws ReCaptchaException when something technically went wrong during captcha validation. 50 | */ 51 | public boolean isValid(String captchaResponseToken) { 52 | return createReCaptchaEndPoint().verify(captchaResponseToken); 53 | } 54 | 55 | @VisibleForTesting 56 | protected ReCaptchaEndPoint createReCaptchaEndPoint() { 57 | return new ReCaptchaEndPoint(secret); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # reCAPTCHA-V2-jee7 2 | [![Build Status](https://travis-ci.org/schnatterer/reCAPTCHA-V2-jee7.svg?branch=master)](https://travis-ci.org/schnatterer/reCAPTCHA-V2-jee7) 3 | [![JitPack](https://jitpack.io/v/schnatterer/reCAPTCHA-V2-jee7.svg)](https://jitpack.io/#schnatterer/reCAPTCHA-V2-jee7) 4 | 5 | Java Bindings for reCAPTCHA V2. See [Verifying the user's response | reCAPTCHA | Google Developers](https://developers.google.com/recaptcha/docs/verify). Basing on JEE7's JAX-RS 2.0 Client API and JSON P 1.0. 6 | Same API as [triologygmbh/reCAPTCHA-V2-java](https://github.com/triologygmbh/reCAPTCHA-V2-java) 7 | 8 | ## Prerequisites 9 | 10 | * Get a public and a secret API key [from Google](http://www.google.com/recaptcha/admin). 11 | * Set up your client side to display the reCAPTCHA V2 widget, **containing the public API key**, as described [here](https://developers.google.com/recaptcha/docs/display). 12 | * Send the response token to your server. 13 | 14 | ## Usage 15 | 16 | Get it via [JitPack](https://jitpack.io/#triologygmbh/reCAPTCHA-V2-java), for example using maven. 17 | 18 | Add the following maven repository to your POM.xml 19 | 20 | ```xml 21 | 22 | 23 | jitpack.io 24 | https://jitpack.io 25 | 26 | 27 | ``` 28 | 29 | Then add the actual dependency 30 | 31 | ```xml 32 | 33 | com.github.schnatterer 34 | reCAPTCHA-V2-jee7 35 | 1.0.0 36 | 37 | ``` 38 | 39 | On your server: 40 | * Create a new instance of `ReCaptcha`, 41 | * passing the reCAPTCHA **private API key** (the one beloging to the public key used in your client code to generate the response). 42 | * Then validate the token sent by the client, you'll receive a boolean response. 43 | 44 | It's as simple as that. 45 | 46 | ````java 47 | String response = "03AEHxwuyM-dll21GpJuJ65tGT6SVEvQEO3tvLfyxbbgBCaSdOLRQBT4Py-jMjGxplhE1wo7nn7Y6zRNgqUufFTnYDdqzYDTupfZkgx0LppSC3_eBKkODMopBaSBeeGMlt_wzkqWes5tAo34t2LmS0fGdwsE_feGJ_NsrB29NsUNAO78FGyL5DpL7f8K5dnh9Q_6QiN5Qg0MapUEu2w30r-GOI7MfVDMF7qk7wDwbM8uZmoIMn8AenNVKsZY0yEP6ghGVTBhtFvBVaD6jiHXeKztnAX1oLAvPa0jh9sJe20Dwk4jtmuemWKLI"; 48 | String secret = "sMSd8L8jlFrKGHdtbXePOphPfhJO_oA4A0sfvw0i"; 49 | 50 | new ReCaptcha(secret).isValid(response); 51 | ```` 52 | 53 | ### Error Handling 54 | In case there are not technical problems `Recaptcha.isValid()` always returns a boolean. Otherwise a `ReCaptchaException` is thrown. 55 | If you need insight into the underlying HTTP traffic you best set the log level of all loggers `de.triology.recaptchav2java` to `TRACE` using your favorite [SLF4J](https://www.slf4j.org/) implementation. 56 | 57 | -------------------------------------------------------------------------------- /src/main/java/de/triology/recaptchav2java/Http.java: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 TRIOLOGY GmbH 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package de.triology.recaptchav2java; 25 | 26 | import org.slf4j.Logger; 27 | import org.slf4j.LoggerFactory; 28 | 29 | import javax.ws.rs.ProcessingException; 30 | import javax.ws.rs.client.Client; 31 | import javax.ws.rs.client.ClientBuilder; 32 | import javax.ws.rs.client.Entity; 33 | import java.util.function.Function; 34 | 35 | /** 36 | * HTTP-related logic for reCAPTCHA. 37 | */ 38 | class Http { 39 | private static final Logger LOG = LoggerFactory.getLogger(Http.class); 40 | private Http() {} 41 | 42 | static String post(String url, String urlParameters) { 43 | try { 44 | return postWithExceptions(url, urlParameters); 45 | } catch (ProcessingException e){ 46 | throw new ReCaptchaException("I/O error sending or receiving the response ", e); 47 | } 48 | } 49 | 50 | private static String postWithExceptions(String url, String urlParameters) { 51 | return withClient(url, client -> 52 | client.target(url) 53 | .request() 54 | .post(Entity.json(urlParameters)) 55 | .readEntity(String.class)); 56 | } 57 | 58 | private static String withClient(String url, Function runnable) { 59 | Client client = null; 60 | try { 61 | LOG.trace("Creating client {}", url); 62 | client = ClientBuilder.newClient(); 63 | return runnable.apply(client); 64 | } finally { 65 | if (client != null) { 66 | LOG.trace("Closing client for url {}", url); 67 | client.close(); 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/de/triology/recaptchav2java/ReCaptchaEndPoint.java: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 TRIOLOGY GmbH 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package de.triology.recaptchav2java; 25 | 26 | import com.google.common.annotations.VisibleForTesting; 27 | import org.slf4j.Logger; 28 | import org.slf4j.LoggerFactory; 29 | 30 | /** 31 | * Implements the connection to the reCAPTCHA V2 endpoint. 32 | * See https://developers.google.com/recaptcha/docs/verify 33 | */ 34 | class ReCaptchaEndPoint { 35 | private static final Logger LOG = LoggerFactory.getLogger(ReCaptchaEndPoint.class); 36 | 37 | @VisibleForTesting 38 | static final String URL_PROTOCOL_HOST = "https://www.google.com"; 39 | @VisibleForTesting 40 | static final String URL_QUERY_PART = "/recaptcha/api/siteverify"; 41 | private static final String URL_ABSOLUTE = URL_PROTOCOL_HOST + URL_QUERY_PART; 42 | 43 | private static final String POST_PARAM_SECRET = "secret"; 44 | private static final String POST_PARAM_TOKEN = "response"; 45 | private final String secret; 46 | 47 | ReCaptchaEndPoint(String secret) { 48 | this.secret = secret; 49 | } 50 | 51 | boolean verify(String token) { 52 | LOG.debug("Verifying token {}", token); 53 | String response = Http.post(getAbsoluteUrl(), createUrlParameters(token)); 54 | boolean success = new ReCaptchaJson(response).isSuccess(); 55 | LOG.debug("Received response for token {}: success={}", token, success); 56 | return success; 57 | } 58 | 59 | private String createUrlParameters(String token) { 60 | return POST_PARAM_SECRET + "=" + secret + "&" + 61 | POST_PARAM_TOKEN + "=" + token; 62 | } 63 | 64 | @VisibleForTesting 65 | protected String getAbsoluteUrl() { 66 | return URL_ABSOLUTE; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/de/triology/recaptchav2java/ReCaptchaTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2017 TRIOLOGY GmbH 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package de.triology.recaptchav2java; 25 | 26 | 27 | import com.github.tomakehurst.wiremock.junit.WireMockRule; 28 | import org.junit.Ignore; 29 | import org.junit.Rule; 30 | import org.junit.Test; 31 | import org.junit.rules.ExpectedException; 32 | 33 | import static com.github.tomakehurst.wiremock.client.WireMock.*; 34 | import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; 35 | import static com.google.common.truth.Truth.assertThat; 36 | 37 | import static de.triology.recaptchav2java.ReCaptchaTests.*; 38 | import static de.triology.recaptchav2java.ReCaptchaEndPoint.*; 39 | 40 | public class ReCaptchaTest { 41 | 42 | private static final String EXPECTED_RESPONSE_TOKEN = "SomeToken"; 43 | private static final String EXPECTED_SECRET = "topSecret"; 44 | private static final String IRRELEVANT_SECRET = "not relvant for this test"; 45 | 46 | @Rule 47 | public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort()); 48 | 49 | @Rule 50 | public ExpectedException expectedException = ExpectedException.none(); 51 | 52 | 53 | @Test 54 | public void isValid() throws Exception { 55 | ReCaptcha recaptcha = mockResponseSuccess(); 56 | 57 | boolean reCaptchaResponse = recaptcha.isValid(EXPECTED_RESPONSE_TOKEN); 58 | 59 | assertThat(reCaptchaResponse).isTrue(); 60 | verifyParameters(); 61 | } 62 | 63 | @Test 64 | public void isValidFalse() throws Exception { 65 | ReCaptcha recaptcha = mockResponseFailure(); 66 | 67 | boolean reCaptchaResponse = recaptcha.isValid(EXPECTED_RESPONSE_TOKEN); 68 | 69 | assertThat(reCaptchaResponse).isFalse(); 70 | verifyParameters(); 71 | } 72 | 73 | @Test 74 | @Ignore // Results in 404, not in error receiving?! 75 | public void isValidTransportErrorReceiving() throws Exception { 76 | ReCaptcha recaptcha = mockErrorReceiving(); 77 | expectedException.expect(ReCaptchaException.class); 78 | expectedException.expectMessage("receiving"); 79 | 80 | recaptcha.isValid(EXPECTED_RESPONSE_TOKEN); 81 | } 82 | 83 | @Test 84 | public void isValidTransportErrorSending() throws Exception { 85 | expectedException.expect(ReCaptchaException.class); 86 | expectedException.expectMessage("sending"); 87 | 88 | ReCaptcha reCaptcha = mockErrorSending(); 89 | 90 | reCaptcha.isValid(EXPECTED_RESPONSE_TOKEN); 91 | } 92 | 93 | private void verifyParameters() { 94 | verify(postRequestedFor(urlEqualTo(URL_QUERY_PART)) 95 | .withRequestBody(containing("secret="+ EXPECTED_SECRET)) 96 | .withRequestBody(containing("response="+EXPECTED_RESPONSE_TOKEN)) 97 | ); 98 | } 99 | 100 | private ReCaptcha mockErrorReceiving() { 101 | wireMockRule.setGlobalFixedDelay(Integer.MAX_VALUE); 102 | return new ReCaptchaWireMock(); 103 | } 104 | 105 | private ReCaptcha mockErrorSending() { 106 | return new ReCaptchaFailingToConnect(); 107 | } 108 | 109 | private ReCaptcha mockResponseSuccess() { 110 | mockResponse(createBodySuccess()); 111 | return new ReCaptchaWireMock(); 112 | } 113 | 114 | private ReCaptchaWireMock mockResponseFailure() { 115 | mockResponse(createBodyFailure()); 116 | return new ReCaptchaWireMock(); 117 | } 118 | 119 | private void mockResponse(String body) { 120 | stubFor(post(URL_QUERY_PART) 121 | .willReturn(aResponse() 122 | .withHeader("Content-Type", "application/json") 123 | .withBody(body) 124 | .withStatus(200))); 125 | } 126 | 127 | private class ReCaptchaWireMock extends ReCaptcha { 128 | ReCaptchaWireMock() { 129 | // Overwritten in createReCaptchaEndPoint() 130 | super(IRRELEVANT_SECRET); 131 | } 132 | 133 | @Override 134 | protected ReCaptchaEndPoint createReCaptchaEndPoint() { 135 | return new ReCaptchaEndPoint(EXPECTED_SECRET) { 136 | @Override 137 | protected String getAbsoluteUrl() { 138 | return super.getAbsoluteUrl().replace( 139 | URL_PROTOCOL_HOST, 140 | String.format("http://localhost:%s", wireMockRule.port())); 141 | } 142 | }; 143 | } 144 | } 145 | 146 | private static class ReCaptchaFailingToConnect extends ReCaptcha { 147 | ReCaptchaFailingToConnect() { 148 | // Overwritten in createReCaptchaEndPoint() 149 | super(IRRELEVANT_SECRET); 150 | } 151 | 152 | @Override 153 | protected ReCaptchaEndPoint createReCaptchaEndPoint() { 154 | // Secret is not needed, because request fails anyway 155 | return new ReCaptchaEndPoint(IRRELEVANT_SECRET) { 156 | @Override 157 | protected String getAbsoluteUrl() { 158 | return super.getAbsoluteUrl().replace(URL_PROTOCOL_HOST, "http://localhost:0"); 159 | } 160 | }; 161 | } 162 | } 163 | 164 | } 165 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 27 | 29 | 4.0.0 30 | 31 | de.triology.recaptchav2-java 32 | recaptchav2-java 33 | 1.0.1-SNAPSHOT 34 | jar 35 | 36 | recaptchav2-java 37 | https://github.com/triologygmbh/recaptchav2-java 38 | 39 | 40 | scm:git:https://github.com/triologygmbh/recaptchav2-java 41 | scm:git:https://github.com/triologygmbh/recaptchav2-java 42 | https://github.com/triologygmbh/recaptchav2-java.git 43 | HEAD 44 | 45 | 46 | 47 | 48 | 49 | maven-compiler-plugin 50 | 3.6.1 51 | 52 | 1.8 53 | 1.8 54 | 55 | 56 | 57 | 58 | org.apache.maven.plugins 59 | maven-release-plugin 60 | 2.5.3 61 | 62 | 63 | @{project.version} 64 | 65 | 66 | 67 | 68 | maven-deploy-plugin 69 | 2.8.2 70 | 71 | 72 | internal.repo::default::file://${project.build.directory}/release 73 | 74 | 75 | 76 | 77 | 78 | org.apache.maven.plugins 79 | maven-source-plugin 80 | 3.0.1 81 | 82 | 83 | attach-sources 84 | 85 | jar 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | com.mycila 94 | license-maven-plugin 95 | 3.0 96 | 97 |
LICENSE
98 | 99 | **/VisibleForTesting.java 100 | 101 |
102 | 103 | 104 | 105 | 106 | check 107 | 108 | 109 | 110 |
111 |
112 |
113 | 114 | 115 | 116 | 117 | org.slf4j 118 | slf4j-api 119 | 1.7.25 120 | 121 | 122 | 123 | javax.json 124 | javax.json-api 125 | 1.0 126 | provided 127 | 128 | 129 | 130 | javax.ws.rs 131 | javax.ws.rs-api 132 | 2.0 133 | provided 134 | 135 | 136 | 137 | 138 | junit 139 | junit 140 | 4.12 141 | test 142 | 143 | 144 | org.mockito 145 | mockito-core 146 | 2.8.47 147 | 148 | 149 | com.google.truth 150 | truth 151 | 0.34 152 | test 153 | 154 | 155 | 156 | com.github.tomakehurst 157 | wiremock 158 | 2.6.0 159 | 160 | 161 | ch.qos.logback 162 | logback-classic 163 | 1.2.3 164 | test 165 | 166 | 167 | org.glassfish.jersey.core 168 | jersey-client 169 | 2.5 170 | test 171 | 172 | 173 | org.glassfish 174 | javax.json 175 | 1.0.4 176 | test 177 | 178 | 179 |
180 | --------------------------------------------------------------------------------