java - side - Excepción de verificación del token JWT de Google OAuth2
token reader (6)
Estoy enfrentando la excepción de verificación de token JWT OAuth2 la última hora (para que nadie pueda acceder a mi aplicación):
java.security.SignatureException: la longitud de la firma no es correcta: 256, pero esperaba 128. Estoy usando google-http-client 1.20.0
y Java 1.7.0
. La misma configuración funcionó hasta ahora, ¿alguna idea?
Stacktrace
java.security.SignatureException: Signature length not correct: got 256 but was expecting 128
at sun.security.rsa.RSASignature.engineVerify(Unknown Source) ~[na:1.7.0_45]
at java.security.Signature$Delegate.engineVerify(Unknown Source) ~[na:1.7.0_45]
at java.security.Signature.verify(Unknown Source) ~[na:1.7.0_45]
at com.google.api.client.util.SecurityUtils.verify(SecurityUtils.java:164) ~[google-http-client-1.20.0.jar:1.20.0]
El mismo problema aquí, agregué el código fuente de GoogleIdTokenVerifier a mi proyecto y cambié el método:
public boolean verify(GoogleIdToken googleIdToken) throws GeneralSecurityException, IOException {
// check the payload
if (!super.verify(googleIdToken)) {
return false;
}
// verify signature
for (PublicKey publicKey : publicKeys.getPublicKeys()) {
try {
if (googleIdToken.verifySignature(publicKey)) {
return true;
}
} catch (Exception e) {
System.err.println("Verify Token:" + e);
}
}
return false;
}
solo maneja la excepción, el segundo certificado funciona bien.
Editar: puedes subclase como Erik-z sugirió si quieres hacerlo más limpio:
Edición 2: No puedo hacer que funcione con el código de abajo, me atendré al truco feo de arriba.
package com.my.project.package;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.PublicKey;
import com.google.api.client.auth.openidconnect.IdTokenVerifier;
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.json.JsonFactory;
// Remember to remove this class later by making it deprecated
@Deprecated
public class GoogleIdTokenVerifier2 extends GoogleIdTokenVerifier {
// Add constructors as needed
public GoogleIdTokenVerifier2(HttpTransport transport, JsonFactory jsonFactory) {
super(transport, jsonFactory);
}
@Override
public boolean verify(GoogleIdToken googleIdToken) throws GeneralSecurityException, IOException {
// check the payload
if (!((IdTokenVerifier)this).verify(googleIdToken)) {
return false;
}
// verify signature
for (PublicKey publicKey : getPublicKeysManager().getPublicKeys()) {
try {
if (googleIdToken.verifySignature(publicKey)) {
return true;
}
} catch (Exception e) {
System.err.println("Verify Token:" + e);
}
}
return false;
}
}
Esto se copia de mi respuesta here , pero es más relevante para aquellos que no usan Google Cloud Endpoint (que coinciden con esta pregunta). El problema es causado por esto:
- RSA tiene firmas de longitud variable, dependiendo del tamaño de la clave.
- Google actualizó los pares de claves que utiliza para firmar, y ahora uno de los pares de claves genera una firma de longitud diferente a la otra.
-
java.security.Signature.verify(byte[] signature)
lanza una excepción si se pasa una firma de la longitud incorrecta (en lugar de devolver false, que normalmente se realiza cuando una firma no coincide con la clave)
La solución más simple es envolver la llamada de verificación ( try...catch
) y devolver falsa si obtiene una excepción
Mirando el código de ejemplo en http://android-developers.blogspot.com/2013/01/verifying-back-end-calls-from-android.html , parece que puede cambiar esta línea:
GoogleIdToken token = GoogleIdToken.parse(mJFactory, tokenString);
a
JsonWebSignature jws = JsonWebSignature.parser(mJFactory).setPayloadClass(Payload.class).parse(tokenString);
GoogleIdToken token = new GoogleIdToken(jws.getHeader(), (Payload) jws.getPayload(), jws.getSignatureBytes(), jws.getSignedContentBytes()) {
public boolean verify(GoogleIdTokenVerifier verifier)
throws GeneralSecurityException, IOException {
try {
return verifier.verify(this);
} catch (java.security.SignatureException e) {
return false;
}
}
};
Desafortunadamente, no tengo una configuración exacta para probar esto, avíseme si esto funciona para usted.
La causa raíz está en el lado de Google, los certificados en el JSON están en mal orden:
https://www.googleapis.com/oauth2/v1/certs
Puedes ajustar el orden de ellos, así:
http://test.gacivs.info/frontend/certs.json
Después, puedes especificar tu URL personalizada (o usando la mía :) de JSON con el método GooglePublicKeysManager.setPublicCertsCodifiedUrl (...):
final GoogleIdToken idToken = GoogleIdToken.parse(JSON_FACTORY, token);
final GooglePublicKeysManager manager = new GooglePublicKeysManager.Builder(HTTP_TRANSPORT, JSON_FACTORY).setPublicCertsEncodedUrl(CUSTOM_CERTS_URL).build();
final GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(manager).setAudience(Arrays.asList(CLIENT_ID)).build();
verifier.verify(idToken);
...y funciona.
Espero que el Google solucione el problema pronto ... :)
Me parece que las bibliotecas pueden estar comportándose mal. Como alternativa a la verificación de token sin conexión, puede usar los puntos finales OAuth2 de Google para verificar tokens. Un ejemplo básico del explorador de API se puede ver aquí .
Puede verificar un token con un comando curl https://www.googleapis.com/oauth2/v2/tokeninfo?id_token=[id_token]
, por ejemplo:
curl https://www.googleapis.com/oauth2/v2/tokeninfo?id_token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjRlYjczOTg0MzBkNTNjZjZjNGZkMGU5YmM4NzkzZWViZWNkMWY1NWUifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTA3Mzc3MTkxNjgxODAyNjY5ODY2IiwiYXpwIjoiMTE2MjY4ODY3NTIuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdF9oYXNoIjoieGJSVGJOdFJYRnJzcUJHTkRtRTR6USIsImF1ZCI6IjExNjI2ODg2NzUyLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiY19oYXNoIjoiU3JFa25WczRUejhQSWJicExnNXF2QSIsImlhdCI6MTQzNDA0MTY5OSwiZXhwIjoxNDM0MDQ1Mjk5fQ.vqQXCTFfbDqpTYnfFrDD7m68oEuGqd8NWa4s9wstOrrcyuVKUsqFXM_2bH-un_4C8UBvqtQEyzU_-53DxgvhCHQ7S0W-wtQ9YMoJcy7iL1wDjcy1p7aFVoeGCoqxWv1vzlWTUDu_FnD9oIBSAawyDexvRwwGxN8O1D8nzyj__1DQ_ivxIMF-j1W89mc7adK4p5B8ioZA_PI-AGawX2Nm8t58yBMIYrTk0XExr9Pf4eHHRGbrQxcd0ERGHbRMYuG6k-MzdnVNHIScgZ3Cixx9v15PbQ5hXExNvleifG_Wk3Thnz0j6Uacr4fbi-mO93-h8c0r3BSvQ270_JqlpL5q5Q
No piense que es la solución final, pero una solución temporal que definitivamente funciona es cambiar la audiencia del verificador al tokenId.
GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory).setAudience(Arrays.asList(clientId)).build();
a
GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory)
.setAudience(Arrays.asList(tokenResponse.getIdToken())).build();
Si no desea (o no puede) cambiar la fuente de la biblioteca de Google, solo puede extender GoogleIdTokenVerifier. (tienes que duplicar otro método que acceda a algunas variables privadas; afortunadamente, todas son accesibles a través de get-members). Esto funciona para mí:
GoogleIdTokenVerifier myVerifier = new GoogleIdTokenVerifier(httpTransport, jsonFactory) {
public boolean superVerify(IdToken idToken) {
return (getIssuer()== null || idToken.verifyIssuer(getIssuer()))
&& (getAudience() == null || idToken.verifyAudience(getAudience()))
&& idToken.verifyTime(getClock().currentTimeMillis(), getAcceptableTimeSkewSeconds());
}
@Override
public boolean verify(GoogleIdToken googleIdToken) throws GeneralSecurityException, IOException {
// check the payload
if (!superVerify(googleIdToken)) {
log.info("superVerify returned false");
return false;
}
// verify signature
for (PublicKey publicKey : getPublicKeysManager().getPublicKeys()) {
try {
if (googleIdToken.verifySignature(publicKey)) {
log.info("verifySignature: success!");
return true;
}
} catch (Exception e) {
log.info("error verifying!", e);
}
}
return false;
}
};