验证 Google OpenID Connect JWT ID 令牌

Validating Google OpenID Connect JWT ID Token(验证 Google OpenID Connect JWT ID 令牌)
本文介绍了验证 Google OpenID Connect JWT ID 令牌的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着跟版网的小编来一起学习吧!

问题描述

我正在尝试升级我的 MVC 网站以使用新的 OpenID Connect 标准.OWIN 中间件看起来很健壮,但不幸的是只支持form_post"响应类型.这意味着 Google 不兼容,因为它在#"之后返回 url 中的所有令牌,因此它们永远不会到达服务器并且永远不会触发中间件.

I'm trying to upgrade my MVC website to use the new OpenID Connect standard. The OWIN middleware seems to be pretty robust, but unfortunately only supports the "form_post" response type. This means that Google isn't compatible, as it returns all the tokens in a the url after a "#", so they never reach the server and never trigger the middleware.

我自己尝试触发中间件中的响应处理程序,但这似乎根本不起作用,所以我有一个简单的 javascript 文件,它解析返回的声明并将它们发布到控制器操作进行处理.

I've tried to trigger the response handlers in the middleware myself, but that doesn't seem to work at all, so I've got a simply javascript file that parses out the returned claims and POSTs them to a controller action for processing.

问题是,即使我在服务器端获取它们,我也无法正确解析它们.我得到的错误如下所示:

Problem is, even when I get them on the server side I can't parse them correctly. The error I get looks like this:

IDX10500: Signature validation failed. Unable to resolve     
SecurityKeyIdentifier: 'SecurityKeyIdentifier
(
   IsReadOnly = False,
   Count = 1,
   Clause[0] = System.IdentityModel.Tokens.NamedKeySecurityKeyIdentifierClause
),
token: '{
    "alg":"RS256",
    "kid":"073a3204ec09d050f5fd26460d7ddaf4b4ec7561"
}.
{
    "iss":"accounts.google.com",
    "sub":"100330116539301590598",
    "azp":"1061880999501-b47blhmmeprkvhcsnqmhfc7t20gvlgfl.apps.googleusercontent.com",
    "nonce":"7c8c3656118e4273a397c7d58e108eb1",
    "email_verified":true,
    "aud":"1061880999501-b47blhmmeprkvhcsnqmhfc7t20gvlgfl.apps.googleusercontent.com",
    "iat":1429556543,"exp":1429560143
    }'."
}

我的令牌验证码遵循开发 IdentityServer 的好人概述的示例

My token verification code follows the example outlined by the good people developing IdentityServer

    private async Task<IEnumerable<Claim>> ValidateIdentityTokenAsync(string idToken, string state)
    {
        // New Stuff
        var token = new JwtSecurityToken(idToken);
        var jwtHandler = new JwtSecurityTokenHandler();
        byte[][] certBytes = getGoogleCertBytes();

        for (int i = 0; i < certBytes.Length; i++)
        {
            var certificate = new X509Certificate2(certBytes[i]);
            var certToken = new X509SecurityToken(certificate);

            // Set up token validation
            var tokenValidationParameters = new TokenValidationParameters();
            tokenValidationParameters.ValidAudience = googleClientId;
            tokenValidationParameters.IssuerSigningToken = certToken;
            tokenValidationParameters.ValidIssuer = "accounts.google.com";

            try
            {
                // Validate
                SecurityToken jwt;
                var claimsPrincipal = jwtHandler.ValidateToken(idToken, tokenValidationParameters, out jwt);
                if (claimsPrincipal != null)
                {
                    // Valid
                    idTokenStatus = "Valid";
                }
            }
            catch (Exception e)
            {
                if (idTokenStatus != "Valid")
                {
                    // Invalid?

                }
            }
        }

        return token.Claims;
    }

    private byte[][] getGoogleCertBytes()
    {
        // The request will be made to the authentication server.
        WebRequest request = WebRequest.Create(
            "https://www.googleapis.com/oauth2/v1/certs"
        );

        StreamReader reader = new StreamReader(request.GetResponse().GetResponseStream());

        string responseFromServer = reader.ReadToEnd();

        String[] split = responseFromServer.Split(':');

        // There are two certificates returned from Google
        byte[][] certBytes = new byte[2][];
        int index = 0;
        UTF8Encoding utf8 = new UTF8Encoding();
        for (int i = 0; i < split.Length; i++)
        {
            if (split[i].IndexOf(beginCert) > 0)
            {
                int startSub = split[i].IndexOf(beginCert);
                int endSub = split[i].IndexOf(endCert) + endCert.Length;
                certBytes[index] = utf8.GetBytes(split[i].Substring(startSub, endSub).Replace("\n", "
"));
                index++;
            }
        }
        return certBytes;
    }

我知道签名验证对于 JWT 来说并不是完全必要的,但我一点也不知道如何关闭它.有什么想法吗?

I know that Signature validation isn't completely necessary for JWTs but I haven't the slightest idea how to turn it off. Any ideas?

推荐答案

问题是JWT中的kid,其值为key的key标识符,用于签署JWT.由于您从 JWKs URI 手动构造了一组证书,因此您丢失了密钥标识符信息.然而,验证过程需要它.

The problem is the kid in the JWT whose value is the key identifier of the key was used to sign the JWT. Since you construct an array of certificates manually from the JWKs URI, you lose the key identifier information. The validation procedure however requires it.

您需要将 tokenValidationParameters.IssuerSigningKeyResolver 设置为一个函数,该函数将返回您上面在 tokenValidationParameters.IssuerSigningToken 中设置的相同密钥.此委托的目的是指示运行时忽略任何匹配"语义并尝试密钥.

You'll need to set tokenValidationParameters.IssuerSigningKeyResolver to a function that will return the same key that you set above in tokenValidationParameters.IssuerSigningToken. The purpose of this delegate is to instruct the runtime to ignore any 'matching' semantics and just try the key.

有关更多信息,请参阅本文:JwtSecurityTokenHandler 4.0.0 重大更改?

See this article for more information: JwtSecurityTokenHandler 4.0.0 Breaking Changes?

代码:

tokenValidationParameters.IssuerSigningKeyResolver = (token, securityToken, kid, validationParameters) => { return new X509SecurityKey(certificate); };

这篇关于验证 Google OpenID Connect JWT ID 令牌的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!

本站部分内容来源互联网,如果有图片或者内容侵犯了您的权益,请联系我们,我们会在确认后第一时间进行删除!

相关文档推荐

Custom Error Queue Name when using EasyNetQ for RabbitMQ?(使用 EasyNetQ for RabbitMQ 时自定义错误队列名称?)
How to generate password_hash for RabbitMQ Management HTTP API(如何为 RabbitMQ 管理 HTTP API 生成密码哈希)
Rabbitmq Ack or Nack, leaving messages on the queue(Rabbitmq Ack 或 Nack,将消息留在队列中)
Wait for a single RabbitMQ message with a timeout(等待一条带有超时的 RabbitMQ 消息)
Setup RabbitMQ consumer in ASP.NET Core application(在 ASP.NET Core 应用程序中设置 RabbitMQ 消费者)
Specify Publish timeouts in mass transit(指定公共交通中的发布超时)