首页 > 解决方案 > 谷歌 JWT 无效签名

问题描述

我正在尝试使用过滤器验证后端中的 Bearer jwt 令牌(从 jwt 检索的用户未注册,我创建一个新的,如果已注册,则允许使用 API)。为了实现这一点,我正在使用 Google OAuth2 服务(并计划使用 Facebook 和 Twitter 以及“社交登录”),但我无法验证令牌。

为了验证令牌,我首先尝试使用 Google 的 GoogleIdTokenVerifier (如此所述),但据报道我使用的令牌具有无效签名(当我使用 google OAuth2 服务获取它们时我发现这很奇怪),所以我尝试验证他们自己访问谷歌使用的公共签名并使用Auth0 JWT 库。我再次发现自己遇到了上一期(签名无效)。

所以 2 次尝试和 2 次失败,我认为令牌是问题,我使用在线 google api 服务来验证它(https://www.googleapis.com/oauth2/v1/tokeninfo?id_token=)。事实证明,令牌不是问题,所以我又回到零。

最后,我解码了令牌并将令牌上使用的公钥(由 jwt.io报告)与我使用谷歌签名生成的公钥进行比较,发现它们不匹配。

令牌公钥:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkoT6VAUmOwqXQoyMQkk0
F1JJd81H7ksx7FHfKtqXvdrDt9LLr1IDJ+CFpbbn4SuJeYQcUo+lHA1/vbbtgCBS
yd/H86WGGARlPrsqFF+hbBBauhUQuXMxFxJKAiQS0WIbFvDkwRaNiGIMYQvzwDKb
ms5tCqZ04T5Qez9v64i3RlU8+upJxG9duzeXQjrnC5uJeeG+9fwE06RRZ1Y8Uul6
3Lpxicw5Alyd5HQIzF5vSSwjqdVrBUJAToxuTIqIHR24omrz1f7Jf97wy2U7KUoS
nytauyaZtph7RmsUPVMFr9fGMxNVU8tKEVeOYJv+piOc0gfPvIahbv2en0DhOax6
OQIDAQAB
-----END PUBLIC KEY-----

生成的公钥(使用谷歌提供的签名)

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqWaspOi9jHSmTMBq8EOP
XJDIkqXdSfb+VeEczodUetDIuA0/q5muW1YTcbmwzJwu9WxSi3ZOuRe1zjmP1HyT
z6ffnMFvwOrtN/pBahn++QYOIXlgw3SU4WtWn+VIRbuAVya+XxE0jfekwuuvtt3f
75WejmUbEWch6546YSu/57qM6IkOkJjxmu7rwcBuVruwPxL5IrGkZarEmv3wuYf5
trqZrjYXcd0O3/dzikFmUsel5DVR/y86i89lkl2ofL+netLNKC3edsMiKzXJExiC
0fu2lyLuf6IfM0MKcdwQS2m4JPmKtPh+s+wV9OtvWslHf+aVHCc+YRdhlARfN8o0
mwIDAQAB
-----END PUBLIC KEY-----

我真的怀疑谷歌是否更改了他们的 OAuth2 签名公钥并忘记将它们更新给公众。

我可能做错了什么吗?

下面我提供了用于检索密钥并使用它们来验证 JWT 令牌的代码。

公共证书检索和公钥生成:

final Mono<GoogleJWTDto> googleJWTDtoMono = webClient
        .get()
        .retrieve()
        .bodyToMono(GoogleJWTDto.class);

googleJWTDtoMono.subscribe((googleJWTDto) -> {
    for(final GoogleJWT googleJWT : googleJWTDto.keys) {
        try {
            final RSAKey rsaKey = new RSAKey.Builder(Base64URL.from(googleJWT.n), Base64URL.from(googleJWT.e))
                    .keyID(googleJWT.kid)
                    .keyUse(KeyUse.parse(googleJWT.use))
                    .build();

            rsaKeys.put(googleJWT.kid, rsaKey);
        } catch (ParseException e) {
            LOGGER.error("the key (kid: {} from: {}) could not be loaded", googleJWT.kid, "google");
        }
    }
});

验证密钥选择:

final DecodedJWT decodedJWT = JWT.decode(token);
final RSAKey rsaKey = getRSAKey(decodedJWT.getKeyId());
final boolean isValid = JWTUtils.verifyToken(decodedJWT, rsaKey);

JWTUtils 类:

public class JWTUtils {

    public static boolean verifyToken(final DecodedJWT decodedJWT, final RSAKey key) {
        try {
            final Algorithm algorithm = getTokenAlgorithm(decodedJWT, key);
            final JWTVerifier verification = JWT.require(algorithm)
                    .withIssuer(decodedJWT.getIssuer())
                    .build();

            verification.verify(decodedJWT);
            return true;
        } catch (JWTVerificationException | JOSEException e) {
            return false;
        }
    }

    private static Algorithm getTokenAlgorithm(final DecodedJWT decodedJWT, final RSAKey key) throws JOSEException {
        switch (decodedJWT.getAlgorithm()) {
            case "RS256":
                return Algorithm.RSA256(key.toRSAPublicKey(), null);

            default:
                throw new IllegalArgumentException("the algorithm '" + decodedJWT.getAlgorithm() + "' is not supported");
        }
    }

}

PS:我知道我可以使用在线服务来验证 jwt,但由于 jwt 的全部目的是能够离线验证它们(使用公钥),我想避免这种方法。

标签: javajwt

解决方案


推荐阅读