“使用 Google 登录”JWT 发送到我的服务器时无效

Posted

技术标签:

【中文标题】“使用 Google 登录”JWT 发送到我的服务器时无效【英文标题】:"Log in with Google" JWT is invalid when sent to my server 【发布时间】:2020-11-25 04:23:08 【问题描述】:

设置:React 前端和 Golang 后端。

我的 React 前端成功从 Google 获取令牌:

<GoogleLogin
   clientId="<client-id>.apps.googleusercontent.com"
   onSuccess=response => responseGoogle(response)
>
</GoogleLogin>

我有一个突变可以发送我需要的信息:

initiateTestMutation(
 variables: 
    idToken: response.getAuthResponse().id_token,
    email: response.profileObj.email,
    givenName: response.profileObj.givenName,
    familyName: response.profileObj.familyName,
 

然后它发送一个我可以用 jwt.io 解码的令牌,但它显示“无效签名”。它包含我的正确信息,但同样无效。

在我的服务器端,我也尝试验证它,但失败了。

// This is the token as a string
unencodedToken := *input.IDToken
fmt.Println(unencodedToken)
token, err := jwt.Parse(unencodedToken, func(token *jwt.Token) (interface, error)
    return []byte("What goes here?"), nil
)
if err != nil 
    fmt.Println("Could not decode the token")
    fmt.Println(err)

if token.Valid 
    fmt.Println("Valid token")
 else if ve, ok := err.(*jwt.ValidationError); ok 
    if ve.Errors&jwt.ValidationErrorMalformed != 0 
        fmt.Println("That's not even a token")
     else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 
        // Token is either expired or not active yet
        fmt.Println("Expired token")
     else 
        fmt.Println("Couldn't handle this token:", err)
    
 else 
    fmt.Println("Couldn't handle this token:", err)

其他信息:

这一切都在本地完成。 app.localhost 是请求 JWT 的域,该域被添加为已批准的来源

【问题讨论】:

您必须使用令牌发布者的公钥来验证令牌。 “这里发生了什么”应该是那个公钥。 @BurakSerdar 很抱歉仍未得到它,但是我应该从googleapis.com/oauth2/v3/certs 传递什么来验证 JWT? 这能回答你的问题吗? How to verify a JWT Token from AWS Cognito in Go? @peko 是的,我在自己的答案中添加了以下内容。 【参考方案1】:

靠近:https://***.com/a/61718113/12563520

我们的朋友here 写了如何正确验证 JWT。


token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface, error) 
    token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface, error) 
    if _, ok := token.Method.(*jwt.SigningMethodRS256); !ok 
        return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
    
    kid, ok := token.Header["kid"].(string)
    if !ok 
        return nil, errors.New("kid header not found")
    
    keys := keySet.LookupKeyID(kid);
    if len(keys) == 0 
         return nil, fmt.Errorf("key %v not found", kid)
    
    // keys[0].Materialize() doesn't exist anymore
    var raw interface
    return raw, keys[0].Raw(&raw)
)

这是我的完整实现,以满足 Google 从此处提供的说明:https://developers.google.com/identity/sign-in/web/backend-auth

我可能比我需要的更频繁地验证事情,所以如果有人想编辑或评论,我会做出改变。

// Get the Key
    unencodedToken := *input.IDToken
    fetchedToken, err := jwk.FetchHTTP("https://www.googleapis.com/oauth2/v3/certs")

    // Parse the token with standard claims
    token, err := jwt.ParseWithClaims(unencodedToken, &jwt.StandardClaims, func(token *jwt.Token) (interface, error) 
        if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok 
            return nil, gqlerror.Errorf("Unexpected token signing method", token)
        
        kid, ok := token.Header["kid"].(string)
        if !ok 
            fmt.Println("Could not find Key ID")
            return nil, gqlerror.Errorf("Could not find key ID in token:", token)
        
        keys := fetchedToken.LookupKeyID(kid)
        if len(keys) == 0 
            fmt.Println("Could not find key in the signature")
            return nil, gqlerror.Errorf("Could not find key in the signature: ", token)
        
        var empty interface
        return empty, keys[0].Raw(&empty)
    )
    if err != nil 
        fmt.Println("Could not decode the token")
        fmt.Println(err)
        return nil, gqlerror.Errorf("Could not decode the token: ", token)
    
    // Check if the token is valid
    if token.Valid 
        fmt.Println("Valid token")
     else if ve, ok := err.(*jwt.ValidationError); ok 
        if ve.Errors&jwt.ValidationErrorMalformed != 0 
            fmt.Println("That's not even a token")
            return nil, gqlerror.Errorf("Invalid Token")
         else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 
            // Token is either expired or not active yet
            fmt.Println("Expired token")
            return nil, gqlerror.Errorf("Expired Token:", token)
         else 
            fmt.Println("Couldn't handle this token", token, err)
            return nil, gqlerror.Errorf(err.Error())
        
     else 
        fmt.Println("Couldn't handle this token", token, err)
        return nil, gqlerror.Errorf(err.Error())
    
    // Check if the claims are valid
    err = token.Claims.Valid()
    if err != nil 
        fmt.Println("Failed validity check", err)
        return nil, gqlerror.Errorf("Failed validity check on token", token, err.Error())
    
    // Check the custom claims
    if claims, ok := token.Claims.(*jwt.StandardClaims); ok && token.Valid 
        audienceVerified := claims.VerifyAudience("773117128619-kfrd500nf8bfaq7anl7ee1ae7ucg5kp5.apps.googleusercontent.com", true)
        if !audienceVerified 
            // TODO Handle failure
            fmt.Println("Audience unverified")
            return nil, gqlerror.Errorf("Audience unverified on token", token)
        
    

    if claims, ok := token.Claims.(*jwt.StandardClaims); ok && token.Valid 
        netloc := claims.VerifyIssuer("accounts.google.com", true)
        httpVersion := claims.VerifyIssuer("accounts.google.com", true)
        if !netloc && !httpVersion 
            // TODO Handle failure
            fmt.Println("Can't verify issuer")
            return nil, gqlerror.Errorf("Can't verify issuer on token", token)
        
    

【讨论】:

以上是关于“使用 Google 登录”JWT 发送到我的服务器时无效的主要内容,如果未能解决你的问题,请参考以下文章

在 Google OAuth2 授权流程之后如何将 JWT 从我的后端服务器发送到我的前端

使用 Google 登录管理浏览器会话

如何从 expressjs 发送 jwt 令牌以响应应用程序? [关闭]

Google 登录 GoogleIdToken 后端验证突然失败

PHP中的Auth0 JWT令牌验证

使用 php 进行 jwt 服务器端身份验证