Google OAuth2 JWT 令牌验证异常
Google OAuth2 JWT token verification exception
上一小时我遇到了 OAuth2 JWT 令牌验证异常(所以没有人可以访问我的应用程序):
java.security.SignatureException:签名长度不正确:得到 256,但预期为 128。我正在使用 google-http-client 1.20.0
和 Java 1.7.0
。到目前为止相同的配置有效 - 有什么想法吗?
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]
同样的问题,我将GoogleIdTokenVerifier的源代码添加到我的项目中并更改了方法:
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;
}
只是处理异常,第二个证书工作正常。
编辑:如果你想让它更干净,你可以按照 Erik-z 的建议进行子类化:
编辑 2:我无法使用下面的代码让它工作,我会坚持使用上面的丑陋 hack。
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;
}
}
不要认为这是最终解决方案,但绝对有效的临时解决方法是将验证者的受众更改为 tokenId。
GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory).setAudience(Arrays.asList(clientId)).build();
至
GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory)
.setAudience(Arrays.asList(tokenResponse.getIdToken())).build();
根本原因在 Google 方面,JSON 中的证书顺序不正确:
https://www.googleapis.com/oauth2/v1/certs
你可以调整它们的顺序,像这样:
http://test.gacivs.info/frontend/certs.json
之后,您可以使用 GooglePublicKeysManager.setPublicCertsEncodedUrl(...) 方法指定 JSON 的自定义 URL(或使用我的 :):
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);
...它有效。
希望 Google 尽快解决问题...:)
这是从我的回答 中复制的,但与那些不使用 Google Cloud Endpoint(匹配此问题)的人更相关。问题是这样引起的:
- RSA 具有可变长度的签名,具体取决于密钥大小。
- Google 更新了它用于签名的密钥对,现在其中一个密钥对生成与另一个密钥对不同长度的签名
如果传递了错误长度的签名,java.security.Signature.verify(byte[] signature)
会抛出异常(而不是 returning false,通常在签名与密钥不匹配时会这样做)
最简单的解决方案是包装验证调用 (try...catch
),如果遇到异常return false
查看 http://android-developers.blogspot.com/2013/01/verifying-back-end-calls-from-android.html 上的示例代码,您似乎可以更改此行:
GoogleIdToken token = GoogleIdToken.parse(mJFactory, tokenString);
到
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;
}
}
};
不幸的是,我没有准确的设置来测试这个,如果这对你有用,请告诉我。
在我看来,图书馆可能表现不佳。作为离线令牌验证的替代方法,您可以使用 Google 的 OAuth2 端点来验证令牌。来自 API 资源管理器 can be seen here.
的基本示例
您可以使用 curl 命令检查令牌 curl https://www.googleapis.com/oauth2/v2/tokeninfo?id_token=[id_token]
,例如:
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
如果您不想(或不能)更改 google 库的源,您可以只扩展 GoogleIdTokenVerifier。 (你必须复制另一个访问一些私有变量的方法 - 幸运的是,所有这些都可以通过 get-members 访问)。这对我有用:
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;
}
};
上一小时我遇到了 OAuth2 JWT 令牌验证异常(所以没有人可以访问我的应用程序):
java.security.SignatureException:签名长度不正确:得到 256,但预期为 128。我正在使用 google-http-client 1.20.0
和 Java 1.7.0
。到目前为止相同的配置有效 - 有什么想法吗?
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]
同样的问题,我将GoogleIdTokenVerifier的源代码添加到我的项目中并更改了方法:
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;
}
只是处理异常,第二个证书工作正常。
编辑:如果你想让它更干净,你可以按照 Erik-z 的建议进行子类化:
编辑 2:我无法使用下面的代码让它工作,我会坚持使用上面的丑陋 hack。
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;
}
}
不要认为这是最终解决方案,但绝对有效的临时解决方法是将验证者的受众更改为 tokenId。
GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory).setAudience(Arrays.asList(clientId)).build();
至
GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory)
.setAudience(Arrays.asList(tokenResponse.getIdToken())).build();
根本原因在 Google 方面,JSON 中的证书顺序不正确:
https://www.googleapis.com/oauth2/v1/certs
你可以调整它们的顺序,像这样:
http://test.gacivs.info/frontend/certs.json
之后,您可以使用 GooglePublicKeysManager.setPublicCertsEncodedUrl(...) 方法指定 JSON 的自定义 URL(或使用我的 :):
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);
...它有效。
希望 Google 尽快解决问题...:)
这是从我的回答
- RSA 具有可变长度的签名,具体取决于密钥大小。
- Google 更新了它用于签名的密钥对,现在其中一个密钥对生成与另一个密钥对不同长度的签名 如果传递了错误长度的签名,
java.security.Signature.verify(byte[] signature)
会抛出异常(而不是 returning false,通常在签名与密钥不匹配时会这样做)
最简单的解决方案是包装验证调用 (try...catch
),如果遇到异常return false
查看 http://android-developers.blogspot.com/2013/01/verifying-back-end-calls-from-android.html 上的示例代码,您似乎可以更改此行:
GoogleIdToken token = GoogleIdToken.parse(mJFactory, tokenString);
到
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;
}
}
};
不幸的是,我没有准确的设置来测试这个,如果这对你有用,请告诉我。
在我看来,图书馆可能表现不佳。作为离线令牌验证的替代方法,您可以使用 Google 的 OAuth2 端点来验证令牌。来自 API 资源管理器 can be seen here.
的基本示例您可以使用 curl 命令检查令牌 curl https://www.googleapis.com/oauth2/v2/tokeninfo?id_token=[id_token]
,例如:
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
如果您不想(或不能)更改 google 库的源,您可以只扩展 GoogleIdTokenVerifier。 (你必须复制另一个访问一些私有变量的方法 - 幸运的是,所有这些都可以通过 get-members 访问)。这对我有用:
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;
}
};