[英]Java Web GoogleSignin - GoogleIdTokenVerifier verify token string returns null
I am adding google register/signin to my web app and i have encountered a problem.我正在将谷歌注册/登录添加到我的 web 应用程序中,但遇到了问题。
This is my code:这是我的代码:
private static final HttpTransport transport = new NetHttpTransport();
private static final JsonFactory jsonFactory = new JacksonFactory();
private static final String MY_APP_GOOGLE_CLIENT_ID = "wouldntyouliketoknow";
public UsernamePasswordAuthenticationToken verify(final String idTokenString){
GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory)
.setAudience(Collections.singletonList(MY_APP_GOOGLE_CLIENT_ID))
.build();
try {
GoogleIdToken idToken = verifier.verify(idTokenString);// <-- verifier.verify returns null !!!
if (idToken != null) {
Payload payload = idToken.getPayload();
String email = payload.getEmail();
if(Boolean.valueOf(payload.getEmailVerified())){
UserJPA jpa = userRepository.findByEmail(email);
if(jpa==null){
throw new UsernameNotFoundException("Cannot find user with email = "+email);
}
if(!jpa.isRegisterredWithGoogle()){
throw new UsernameNotFoundException("This user did not use the 'Register with google' option.");
}
bokiAuthenticationProvider.checkUserActiveAndUnlocked(jpa);
return new UsernamePasswordAuthenticationToken(jpa.getUsername(), jpa.getPasswordHesh(),
bokiAuthenticationProvider.getAuthorities(jpa.getUserHasRoleSecurityList()));
}
}else{
System.out.println("The *idToken* object is null !!!");
}
} catch (GeneralSecurityException | IOException e) {
e.printStackTrace();
}
throw new MyCustomException("Google token is invalid or has expired");
}
To create my CLIENT_ID I followed instructions here:要创建我的 CLIENT_ID,我按照此处的说明进行操作:
https://developers.google.com/identity/sign-in/web/devconsole-project https://developers.google.com/identity/sign-in/web/devconsole-project
The problem is that verifier.verify keeps returning null .问题是verifier.verify不断返回null 。
I have checked:我检查过:
my user did register with google and the database fields are properly filled我的用户确实在谷歌注册,并且数据库字段已正确填写
i am getting different string tokens from google each time i try google_sign_in每次尝试 google_sign_in 时,我都会从 google 获得不同的字符串令牌
my CLIENT_ID is valid and active in the google console.我的CLIENT_ID在 Google 控制台中有效且处于活动状态。
To add to the confusion, this whole thing worked fine just a month ago.更令人困惑的是,整件事在一个月前还运行良好。 I went away on sick leave and when i came back, my boss welcomed me with this issue.
我请了病假,回来时,我的老板用这个问题欢迎我。
Anyone have any idea what might have happened?有人知道可能发生了什么吗?
I finally figured it out.我终于弄明白了。
Since no one knew how to help me, i dropped the designated google libraries and went and made my own token verification from scratch.由于没有人知道如何帮助我,我放弃了指定的 google 库,并从头开始进行自己的令牌验证。
I went and used the google-token-verifier-url-tool here :我去这里使用了 google-token-verifier-url-tool :
https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=XYZ123 https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=XYZ123
At the bottom of the page here在页面底部这里
https://developers.google.com/identity/protocols/OpenIDConnect https://developers.google.com/identity/protocols/OpenIDConnect
, i found how to decipher the json. ,我找到了如何破译json。
What i do is, I contact their online tool in code, get the json response and verify it manually.我所做的是,我用代码联系他们的在线工具,获取 json 响应并手动验证。 This is my code :
这是我的代码:
private Map<String,String> getMapFromGoogleTokenString(final String idTokenString){
BufferedReader in = null;
try {
// get information from token by contacting the google_token_verify_tool url :
in = new BufferedReader(new InputStreamReader(
((HttpURLConnection) (new URL("https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=" + idTokenString.trim()))
.openConnection()).getInputStream(), Charset.forName("UTF-8")));
// read information into a string buffer :
StringBuffer b = new StringBuffer();
String inputLine;
while ((inputLine = in.readLine()) != null){
b.append(inputLine + "\n");
}
// transforming json string into Map<String,String> :
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(b.toString(), objectMapper.getTypeFactory().constructMapType(Map.class, String.class, String.class));
// exception handling :
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch(Exception e){
System.out.println("\n\n\tFailed to transform json to string\n");
e.printStackTrace();
} finally{
if(in!=null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
// chack the "email_verified" and "email" values in token payload
private boolean verifyEmail(final Map<String,String> tokenPayload){
if(tokenPayload.get("email_verified")!=null && tokenPayload.get("email")!=null){
try{
return Boolean.valueOf(tokenPayload.get("email_verified")) && tokenPayload.get("email").contains("@gmail.");
}catch(Exception e){
System.out.println("\n\n\tCheck emailVerified failed - cannot parse "+tokenPayload.get("email_verified")+" to boolean\n");
}
}else{
System.out.println("\n\n\tCheck emailVerified failed - required information missing in the token");
}
return false;
}
// check token expiration is after now :
private boolean checkExpirationTime(final Map<String,String> tokenPayload){
try{
if(tokenPayload.get("exp")!=null){
// the "exp" value is in seconds and Date().getTime is in mili seconds
return Long.parseLong(tokenPayload.get("exp")+"000") > new java.util.Date().getTime();
}else{
System.out.println("\n\n\tCheck expiration failed - required information missing in the token\n");
}
}catch(Exception e){
System.out.println("\n\n\tCheck expiration failed - cannot parse "+tokenPayload.get("exp")+" into long\n");
}
return false;
}
// check that at least one CLIENT_ID matches with token values
private boolean checkAudience(final Map<String,String> tokenPayload){
if(tokenPayload.get("aud")!=null && tokenPayload.get("azp")!=null){
List<String> pom = Arrays.asList("MY_CLIENT_ID_1",
"MY_CLIENT_ID_2",
"MY_CLIENT_ID_3");
if(pom.contains(tokenPayload.get("aud")) || pom.contains(tokenPayload.get("azp"))){
return true;
}else{
System.out.println("\n\n\tCheck audience failed - audiences differ\n");
return false;
}
}
System.out.println("\n\n\tCheck audience failed - required information missing in the token\n");
return false;
}
// verify google token payload :
private boolean doTokenVerification(final Map<String,String> tokenPayload){
if(tokenPayload!=null){
return verifyEmail(tokenPayload) // check that email address is verifies
&& checkExpirationTime(tokenPayload) // check that token is not expired
&& checkAudience(tokenPayload) // check audience
;
}
return false;
}
Once i have had this detailed verification, i was able to see exactly where the mistake was ;一旦我进行了详细的验证,我就能够准确地看到错误在哪里; The front end was sending me invalid CLIENT_ID value.
前端向我发送了无效的 CLIENT_ID 值。 [ grumble, grumble ] I have asked them about it about a hundred times and they told me that those values match.
[抱怨,抱怨] 我已经问过他们大约一百次了,他们告诉我这些价值观是一致的。 BAH !
呸!
So the mistake was not somewhere in my original code for token verification, it was an error in communication in my office.所以这个错误不是在我的原始令牌验证代码中的某个地方,而是我办公室的通信错误。
I will revert back to the original code, with the proper CLIENT_ID this time for the sake of security.为了安全起见,这次我将恢复到原始代码,并使用正确的 CLIENT_ID。 Still, i must make one complaint about the google library - it never told me why token verification was failing.
不过,我必须对谷歌图书馆提出一个抱怨——它从来没有告诉我为什么令牌验证失败。 It took me DAYS of pain staking effort to finally figure it out.
我花了几天的努力才最终弄明白。 I know they did it out of security, but still, the lack of support is galling.
我知道他们这样做是出于安全考虑,但仍然缺乏支持令人痛苦。
Had similar issue.有类似的问题。 It was so stupid, but I just have wrong time on my server.
这太愚蠢了,但我只是在我的服务器上有错误的时间。 I think verification always fail because expiration time of token is end in my server time universe.
我认为验证总是失败,因为令牌的到期时间在我的服务器时间范围内结束。
For those who still having this issue:对于那些仍然有这个问题的人:
Try python example( https://developers.google.com/identity/sign-in/web/backend-auth#python ) to ensure that you are using right CLIENT_ID, if no, you will see exception message with right CLIENT_ID尝试 python 示例( https://developers.google.com/identity/sign-in/web/backend-auth#python )以确保您使用正确的 CLIENT_ID,如果没有,您将看到带有正确 CLIENT_ID 的异常消息
Use com.google.api-client
of version 1.31.3
and com.google.oauth-client
of version 1.31.4
使用
com.google.api-client
的版本1.31.3
和com.google.oauth-client
的版本1.31.4
Use com.google.api.client.json.gson.GsonFactory
as JsonFactory
使用
com.google.api.client.json.gson.GsonFactory
作为JsonFactory
我通过从客户端发送idToken
而不是authToken
解决了我的问题
Not sure if anyone is still looking at this, but the code from google GitHub actually solved this for me.不确定是否有人还在看这个,但来自谷歌 GitHub 的代码实际上为我解决了这个问题。 https://github.com/googlearchive/gplus-verifytoken-java/tree/master/src/com/google/plus/samples/verifytoken
https://github.com/googlearchive/gplus-verifytoken-java/tree/master/src/com/google/plus/samples/verifytoken
They mention that the project is no longer actively maintained, and remains there as an archive of this work though.他们提到该项目不再得到积极维护,但仍作为这项工作的存档保留在那里。
I found two reasons of why it can happen.我发现了为什么它会发生的两个原因。
Solution to refresh the auth token:刷新授权令牌的解决方案:
We get a reloadAuthResponse method on the response object upon successful sign in. Also, we get the expires_in property which gives us the number of milliseconds after which the token will expire.成功登录后,我们在响应 object 上获得了 reloadAuthResponse 方法。此外,我们还获得了 expires_in 属性,它为我们提供了令牌过期的毫秒数。 Following is the code snippet to use it.
以下是使用它的代码片段。
const handleSuccessfulGoogleResponse = (response) => {
console.log(response)
const loginDetails = {
name: response.profileObj.name,
email: response.profileObj.email,
tokenId: response.tokenId
}
//Do what is needed with the data
refreshTokenGenerator(response);
}
const refreshTokenGenerator = res => {
let refreshTime = (res.tokenObj.expires_in || 3600 - 50 * 60) * 1000;
const refreshToken = async () => {
console.log('running at ',new Date(Date.now()).toLocaleTimeString());
const refreshedResponse = await res.reloadAuthResponse();
refreshTime = (refreshedResponse.expires_in || 3600 - 50 * 60) *
1000;
const newTokenId = refreshedResponse.id_token;
//store the newTokenId and use it for the network calls
setTimeout(refreshToken, refreshTime);
}
setTimeout(refreshToken, refreshTime);
}
Here, the handleSuccessfulGoogleResponse() is executed upon a successful sign in. We are calling the refreshTokenGenerator() inside it which uses the expires_in property(if it exists otherwise 3600ms by default).在这里,handleSuccessfulGoogleResponse() 在成功登录后执行。我们在其中调用 refreshTokenGenerator() ,它使用 expires_in 属性(如果存在,则默认为 3600 毫秒)。 We are then using setTimeout() to execute the function to generate the new token before the current one expires.
然后我们使用 setTimeout() 执行 function 以在当前令牌到期之前生成新令牌。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.