我可以为 Google OpenIDConnect id_token 获得一致的 'iss' 值吗?

Can I get a consistent 'iss' value for a Google OpenIDConnect id_token?

我正在使用 Google's OpenIDConnect authentication,我想验证从 Google 返回的 JWT id_token。但是,关于 ID 令牌中 iss(发行者)声明的值 Google returns,文档似乎不一致。

One page says, "iss: always accounts.google.com", but another page 表示 "The value of iss in the ID token is equal to accounts.google.com or https://accounts.google.com" 示例代码中的注释进一步解释:

// If you retrieved the token on Android using the Play Services 8.3 API or newer, set
// the issuer to "https://accounts.google.com". Otherwise, set the issuer to
// "accounts.google.com". If you need to verify tokens from multiple sources, build
// a GoogleIdTokenVerifier for each issuer and try them both.

我有一个服务器端应用程序,而不是 Android 应用程序,所以我没有使用 Play 服务。

为了进一步搅浑水,the OpenIDConnect specification itself 包含一条注释:

Implementers may want to be aware that, as of the time of this writing, Google's deployed OpenID Connect implementation issues ID Tokens that omit the required https:// scheme prefix from the iss (issuer) Claim Value. Relying Party implementations wishing to work with Google will therefore need to have code to work around this, until such time as their implementation is updated. Any such workaround code should be written in a manner that will not break at such point Google adds the missing prefix to their issuer values.

该文档的日期是 2014 年 11 月 8 日。从那时起,Google 是否标准化了 iss 值,或者我真的需要检查这两个值吗?上面的评论似乎表明只有 Play Services >=8.3 获得 isshttps://,而其他地方的值将只是 accounts.google.com。是真的吗?

首先,我绝对同意 Google 的文档是一个阴暗的行业。

您可以通过多种不同的方式在服务器端验证 ID 令牌的完整性(顺便说一下,this 是您要查找的页面):

  1. "Manually" - 不断下载 Google 的 public 密钥,验证签名,然后验证每个字段,包括 iss 一个;我在这里看到的主要优势(尽管在我看来很小)是您可以最大限度地减少发送到 Google) 的请求数量。
  2. "Automatically" - 在 Google 的端点上执行 GET 以验证此令牌 - 迄今为止最简单的: https://www.googleapis.com/oauth2/v3/tokeninfo?id_token={0}
  3. 使用 Google API 客户端库 - 开销可能不值得,C# 没有官方的等

我建议您选择第二个选项,让 Google 担心验证算法。

你必须检查这两种可能性。这对我有用...

解码令牌以获得发行者。如果发行人不等于 https://accounts.google.comaccounts.google.com 中的任何一个,您可以到此为止。这是一个无效的令牌。

如果发行者等于上述任一 Google 字符串,则将相同的解码发行者值传递给验证步骤。

以下是我在 JavaScript 中为某些 Node.js Express 中间件编写的实现:

function authorize(req, res, next) {
    try {
        var token       = req.headers.authorization;
        var decoded     = jwt.decode(token, { complete: true });
        var keyID       = decoded.header.kid;
        var algorithm   = decoded.header.alg;
        var pem         = getPem(keyID);
        var iss         = decoded.payload.iss;

        if (iss === 'accounts.google.com' || iss === 'https://accounts.google.com') {
            var options = {
                audience: CLIENT_ID,
                issuer: iss,
                algorithms: [algorithm]
            }

            jwt.verify(token, pem, options, function(err) {
                if (err) {
                    res.writeHead(401);
                    res.end();
                } else {
                    next();
                }
            });            

        } else {
            res.writeHead(401);
            res.end();
        }
    } catch (err) {
        res.writeHead(401);
        res.end();
    }
}

注意此函数使用 jsonwebtokenjwk-to-pem 节点模块。我省略了最终将 json 网络密钥转换为 pem 格式的 getPem 函数的细节。