diff options
-rw-r--r-- | pom.xml | 6 | ||||
-rw-r--r-- | src/main/java/com/github/scribejava/apis/GoogleTokenVerifier.java | 66 | ||||
-rw-r--r-- | src/main/java/com/juick/www/api/ApiSocialLogin.java | 22 |
3 files changed, 74 insertions, 20 deletions
@@ -172,9 +172,9 @@ <version>1.7</version> </dependency> <dependency> - <groupId>com.google.api-client</groupId> - <artifactId>google-api-client</artifactId> - <version>1.31.1</version> + <groupId>com.google.code.findbugs</groupId> + <artifactId>jsr305</artifactId> + <version>3.0.2</version> </dependency> <dependency> <groupId>com.kotcrab.remark</groupId> diff --git a/src/main/java/com/github/scribejava/apis/GoogleTokenVerifier.java b/src/main/java/com/github/scribejava/apis/GoogleTokenVerifier.java new file mode 100644 index 00000000..35a9d832 --- /dev/null +++ b/src/main/java/com/github/scribejava/apis/GoogleTokenVerifier.java @@ -0,0 +1,66 @@ +package com.github.scribejava.apis; + +import java.net.MalformedURLException; +import java.net.URL; +import java.text.ParseException; +import java.util.Map; +import java.util.Optional; + +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.jwk.source.JWKSource; +import com.nimbusds.jose.jwk.source.RemoteJWKSet; +import com.nimbusds.jose.proc.BadJOSEException; +import com.nimbusds.jose.proc.JWSKeySelector; +import com.nimbusds.jose.proc.JWSVerificationKeySelector; +import com.nimbusds.jose.proc.SecurityContext; +import com.nimbusds.jwt.proc.ConfigurableJWTProcessor; +import com.nimbusds.jwt.proc.DefaultJWTClaimsVerifier; +import com.nimbusds.jwt.proc.DefaultJWTProcessor; + +public class GoogleTokenVerifier { + + public static Optional<String> validateToken(String idToken) { + + // Create a JWT processor for the access tokens + ConfigurableJWTProcessor<SecurityContext> jwtProcessor = + new DefaultJWTProcessor<>(); + + // The public RSA keys to validate the signatures will be sourced from the + // OAuth 2.0 server's JWK set, published at a well-known URL. The RemoteJWKSet + // object caches the retrieved keys to speed up subsequent look-ups and can + // also handle key-rollover + JWKSource<SecurityContext> keySource = + null; + try { + keySource = new RemoteJWKSet<>(new URL("https://www.googleapis.com/oauth2/v3/certs")); + } catch (MalformedURLException e) { + return Optional.empty(); + } + + // The expected JWS algorithm of the access tokens (agreed out-of-band) + JWSAlgorithm expectedJWSAlg = JWSAlgorithm.RS256; + + // Configure the JWT processor with a key selector to feed matching public + // RSA keys sourced from the JWK set URL + JWSKeySelector<SecurityContext> keySelector = + new JWSVerificationKeySelector<>(expectedJWSAlg, keySource); + + jwtProcessor.setJWSKeySelector(keySelector); + + // Set the required JWT claims for access tokens issued by the server + jwtProcessor.setJWTClaimsSetVerifier(new DefaultJWTClaimsVerifier<>()); + + // Process the token + Map<String, Object> claimsSet; + try { + claimsSet = jwtProcessor.process(idToken, null).toJSONObject(); + } catch (BadJOSEException | JOSEException | ParseException e) { + return Optional.empty(); + } + + String email = (String)claimsSet.get("email"); + boolean verified = claimsSet.get("email_verified").equals(true); + return verified ? Optional.of(email) : Optional.empty(); + } +} diff --git a/src/main/java/com/juick/www/api/ApiSocialLogin.java b/src/main/java/com/juick/www/api/ApiSocialLogin.java index 43de04bb..e6116173 100644 --- a/src/main/java/com/juick/www/api/ApiSocialLogin.java +++ b/src/main/java/com/juick/www/api/ApiSocialLogin.java @@ -20,18 +20,13 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.github.scribejava.apis.AppleClientSecretGenerator; import com.github.scribejava.apis.AppleSignInApi; import com.github.scribejava.apis.FacebookApi; +import com.github.scribejava.apis.GoogleTokenVerifier; import com.github.scribejava.apis.VkontakteApi; import com.github.scribejava.core.builder.ServiceBuilder; import com.github.scribejava.core.model.OAuth2AccessToken; import com.github.scribejava.core.model.OAuthRequest; import com.github.scribejava.core.model.Verb; import com.github.scribejava.core.oauth.OAuth20Service; -import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken; -import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier; -import com.google.api.client.http.HttpTransport; -import com.google.api.client.http.javanet.NetHttpTransport; -import com.google.api.client.json.JsonFactory; -import com.google.api.client.json.jackson2.JacksonFactory; import com.juick.model.AuthResponse; import com.juick.model.ext.facebook.User; import com.juick.model.ext.vk.UsersResponse; @@ -58,8 +53,8 @@ import javax.annotation.PostConstruct; import javax.inject.Inject; import java.io.IOException; import java.security.GeneralSecurityException; -import java.util.Collections; import java.util.Map; +import java.util.Optional; import java.util.UUID; import java.util.concurrent.ExecutionException; @@ -109,18 +104,11 @@ public class ApiSocialLogin { @Inject private Users users; - private final HttpTransport transport = new NetHttpTransport(); - private final JsonFactory jsonFactory = new JacksonFactory(); - private GoogleIdTokenVerifier verifier; - @PostConstruct public void init() { ServiceBuilder facebookBuilder = new ServiceBuilder(FACEBOOK_APPID); ServiceBuilder twitterBuilder = new ServiceBuilder(twitterConsumerKey); ServiceBuilder vkBuilder = new ServiceBuilder(VK_APPID); - verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory) - .setAudience(Collections.singletonList(googleClientId)) - .build(); facebookAuthService = facebookBuilder .apiSecret(FACEBOOK_SECRET) .callback(FACEBOOK_REDIRECT) @@ -246,9 +234,9 @@ public class ApiSocialLogin { throws GeneralSecurityException, IOException { logger.info("Token: {}", idTokenString); logger.info("Client: {}", googleClientId); - GoogleIdToken idToken = verifier.verify(idTokenString); - if (idToken != null) { - String email = idToken.getPayload().getEmail(); + Optional<String> verifiedEmail = GoogleTokenVerifier.validateToken(idTokenString); + if (verifiedEmail.isPresent()) { + String email = verifiedEmail.get(); com.juick.model.User visitor = userService.getUserByEmail(email); if (visitor.isAnonymous()) { String verificationCode = RandomStringUtils.randomAlphanumeric(8).toUpperCase(); |