如何在 Java 中验证 Azure B2C id 令牌的 JWT 签名?

Posted

技术标签:

【中文标题】如何在 Java 中验证 Azure B2C id 令牌的 JWT 签名?【英文标题】:How do I verify a JWT signature for an Azure B2C id token in Java? 【发布时间】:2016-07-06 17:39:19 【问题描述】:

我已使用 google open-id connect 成功验证了签名,但尚未成功验证 Microsoft Azure B2C jwt id 令牌的签名。我在这里使用了示例 B2C 游乐场应用程序 https://aadb2cplayground.azurewebsites.net/ 。注册并编辑我的个人资料后,我捕获了这个 id 令牌。

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSIsImtpZCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSJ9.eyJhdWQiOiJiYjJhMmUzYS1jNWU3LTRmMGEtODhlMC04ZTAxZmQzZmMxZjQiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC83NzU1MjdmZi05YTM3LTQzMDctOGIzZC1jYzMxMWY1OGQ5MjUvIiwiaWF0IjoxNDU4NDMzMDIzLCJuYmYiOjE0NTg0MzMwMjMsImV4cCI6MTQ1ODQzNjkyMywiYW1yIjpbInB3ZCJdLCJpcGFkZHIiOiI3NC4xMzkuMjEzLjE3NSIsIm5hbWUiOiJib2I1Iiwibm9uY2UiOiJMNlNvekpjeVVEc2lYQ2t0NVQwN1NBPT0iLCJvaWQiOiJjNjc0NDA1Yy05ZGIxLTRmN2EtYTIwMy1jNzZkNDk1Zjk5ZDAiLCJwdWlkIjoiMTAwM0JGRkQ5NkQ0NThCMSIsInN1YiI6InozTGRJOWUtSFlWeXpCSl9sOE9RTndWYnRQM3BnbHBwREo3NjJ2TmEycFkiLCJ0aWQiOiI3NzU1MjdmZi05YTM3LTQzMDctOGIzZC1jYzMxMWY1OGQ5MjUiLCJ1bmlxdWVfbmFtZSI6InJib290aF9jYWxsaWJyaXR5LmNvbSNFWFQjQGZhYnJpa2FtYjJjLm9ubWljcm9zb2Z0LmNvbSIsInVwbiI6InJib290aF9jYWxsaWJyaXR5LmNvbSNFWFQjQGZhYnJpa2FtYjJjLm9ubWljcm9zb2Z0LmNvbSIsInZlciI6IjEuMCJ9.MPPkvUc0bHuVyf8hr4JZ0hG0mLE2pT7maDR-10e3XR8m6FtrsmQlkgvhnzfao94jPzDzX_CnG_Asfnqv04JeIpvQXBlViO63AlfZaZVllLByeJti5Uat1WepMPz5MRydk6b2o5w_xRfl7QOI-L9Yt8r7-rQX1FMuIPfvvsUity-M-H8s0XInvihxiKEHU_wvz6U017Tgjs4qcrpILM5Ziaxfb7oSxgECl3EDWAoITDy5B-rYCH_o-7mhxHQauUYgH5dUV2MrM8iuaMPoRc3r9Xk38SyfgS1-4taK_bi_AIutyOBX4O3cWbrvGDshQbHBW4BmjctTBT-xUPWboydpuA

我指定我的 java 代码使用以下端点进行令牌验证。

https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/discovery/v2.0/keys?p=b2c_1_sign_in

在撰写本文时,这是该端点的 json。


  "keys": [
    "kid":"IdTokenSigningKeyContainer","use":"sig","kty":"RSA","e":"AQAB","n":"tLDZVZ2Eq_DFwNp24yeSq_Ha0MYbYOJs_WXIgVxQGabu5cZ9561OUtYWdB6xXXZLaZxFG02P5U2rC_CT1r0lPfC_KHYrviJ5Y_Ekif7iFV_1omLAiRksQziwA1i-hND32N5kxwEGNmZViVjWMBZ43wbIdWss4IMhrJy1WNQ07Fqp1Ee6o7QM1hTBve7bbkJkUAfjtC7mwIWqZdWoYIWBTZRXvhMgs_Aeb_pnDekosqDoWQ5aMklk3NvaaBBESqlRAJZUUf5WDFoJh7yRELOFF4lWJxtArTEiQPWVTX6PCs0klVPU6SRQqrtc4kKLCp1AC5EJqPYRGiEJpSz2nUhmAQ"
  ]

这是我使用的java代码

package com.example

import org.jose4j.jwk.HttpsJwks
import org.jose4j.jwt.JwtClaims
import org.jose4j.jwt.consumer.InvalidJwtException
import org.jose4j.jwt.consumer.JwtConsumer
import org.jose4j.jwt.consumer.JwtConsumerBuilder
import org.jose4j.keys.resolvers.HttpsJwksVerificationKeyResolver

class AzureB2CPOC7 

public static talk()

    String jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSIsImtpZCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSJ9.eyJhdWQiOiJiYjJhMmUzYS1jNWU3LTRmMGEtODhlMC04ZTAxZmQzZmMxZjQiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC83NzU1MjdmZi05YTM3LTQzMDctOGIzZC1jYzMxMWY1OGQ5MjUvIiwiaWF0IjoxNDU4NDMzMDIzLCJuYmYiOjE0NTg0MzMwMjMsImV4cCI6MTQ1ODQzNjkyMywiYW1yIjpbInB3ZCJdLCJpcGFkZHIiOiI3NC4xMzkuMjEzLjE3NSIsIm5hbWUiOiJib2I1Iiwibm9uY2UiOiJMNlNvekpjeVVEc2lYQ2t0NVQwN1NBPT0iLCJvaWQiOiJjNjc0NDA1Yy05ZGIxLTRmN2EtYTIwMy1jNzZkNDk1Zjk5ZDAiLCJwdWlkIjoiMTAwM0JGRkQ5NkQ0NThCMSIsInN1YiI6InozTGRJOWUtSFlWeXpCSl9sOE9RTndWYnRQM3BnbHBwREo3NjJ2TmEycFkiLCJ0aWQiOiI3NzU1MjdmZi05YTM3LTQzMDctOGIzZC1jYzMxMWY1OGQ5MjUiLCJ1bmlxdWVfbmFtZSI6InJib290aF9jYWxsaWJyaXR5LmNvbSNFWFQjQGZhYnJpa2FtYjJjLm9ubWljcm9zb2Z0LmNvbSIsInVwbiI6InJib290aF9jYWxsaWJyaXR5LmNvbSNFWFQjQGZhYnJpa2FtYjJjLm9ubWljcm9zb2Z0LmNvbSIsInZlciI6IjEuMCJ9.MPPkvUc0bHuVyf8hr4JZ0hG0mLE2pT7maDR-10e3XR8m6FtrsmQlkgvhnzfao94jPzDzX_CnG_Asfnqv04JeIpvQXBlViO63AlfZaZVllLByeJti5Uat1WepMPz5MRydk6b2o5w_xRfl7QOI-L9Yt8r7-rQX1FMuIPfvvsUity-M-H8s0XInvihxiKEHU_wvz6U017Tgjs4qcrpILM5Ziaxfb7oSxgECl3EDWAoITDy5B-rYCH_o-7mhxHQauUYgH5dUV2MrM8iuaMPoRc3r9Xk38SyfgS1-4taK_bi_AIutyOBX4O3cWbrvGDshQbHBW4BmjctTBT-xUPWboydpuA";
    HttpsJwks httpsJkws = new HttpsJwks("https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/discovery/v2.0/keys?p=b2c_1_sign_in");
    HttpsJwksVerificationKeyResolver httpsJwksKeyResolver = new HttpsJwksVerificationKeyResolver(httpsJkws);
    JwtConsumer jwtConsumer = new JwtConsumerBuilder()
            .setRequireExpirationTime() // the JWT must have an expiration time
            .setAllowedClockSkewInSeconds(3600) // allow some leeway in validating time based claims to account for clock skew
            .setRequireSubject() // the JWT must have a subject claim
            .setExpectedIssuer("https://sts.windows.net/775527ff-9a37-4307-8b3d-cc311f58d925/") // whom the JWT needs to have been issued by
            .setExpectedAudience("bb2a2e3a-c5e7-4f0a-88e0-8e01fd3fc1f4") // to whom the JWT is intended for
            .setVerificationKeyResolver(httpsJwksKeyResolver)
            .build();

    try
    
        //  Validate the JWT and process it to the Claims
        JwtClaims jwtClaims = jwtConsumer.processToClaims(jwt);
        System.out.println("JWT validation succeeded! " + jwtClaims);
    
    catch (InvalidJwtException e)
    
        // InvalidJwtException will be thrown, if the JWT failed processing or validation in anyway.
        // Hopefully with meaningful explanations(s) about what went wrong.
        System.out.println("Invalid JWT! " + e);
    


起初,我收到此错误消息“无法为带有标头的 JWS 找到合适的验证密钥”。因此,我创建了一个本地 Web 服务器并从 microsoft json 端点复制了 json,但我将“kid”:“IdTokenSigningKeyContainer”替换为“kid”:“MnC_VZcATfM5pOYiJHMba9goEKY”。

该更改修复了“无法为带有标头的 JWS 找到合适的验证密钥”错误消息,但我收到了以下错误,而不是“JWS 签名无效”。

我正在寻找一种 java 解决方案来验证上面列出的 jwt id 令牌的签名。提前致谢。

【问题讨论】:

【参考方案1】:

尝试将https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/discovery/v2.0/keys 用于 HTTPS JWKS 位置。那里有一个不同的密钥,它有一个与 JWT 中的内容相匹配的 child 和 x5t。

【讨论】:

【参考方案2】:

那个网址:

https://login.microsoftonline.com/fabrikamb2c.onmicrosoft.com/discovery/v2.0/keys?p=b2c_1_sign_in

只是为每个 b2c 租户提供的通用端点的示例

https://login.microsoftonline.com/您的租户名称/discovery/v2.0/keys?p=您的登录注册政策

,Azure b2c 令牌本身不提供密钥,而只提供密钥 ID“kid”,但是在自定义端点提供的 json 对象包含编码验证密钥所需的所有组件,您只需要“e”和“n”值

public class KeyUtilHandler 

// values "n" and "e" from json object at
// https://login.microsoftonline.com/your tenant name/discovery/v2.0/keys?p=your sign in sign up policy

private String eValue;
private String nValue;

public String stringPublicKey()

    byte[] modulusBytes = Base64.getUrlDecoder().decode(nValue);
    BigInteger modulusInt = new BigInteger(1, modulusBytes);

    byte[] exponentBytes = Base64.getUrlDecoder().decode(eValue);
    BigInteger exponentInt = new BigInteger(1, exponentBytes);

    KeyFactory keyFactory;

    RSAPublicKeySpec publicSpec = new RSAPublicKeySpec(modulusInt, exponentInt);

    String encodedStringKey = null;

    
        try 
            keyFactory = KeyFactory.getInstance("RSA");

            RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(publicSpec);

            byte [] encodedKey = publicKey.getEncoded();

            encodedStringKey = Base64.getEncoder().encodeToString(encodedKey);

         catch (NoSuchAlgorithmException | InvalidKeySpecException e) 
            e.printStackTrace();
        
    

    return String.format("-----BEGIN PUBLIC KEY-----%s-----END PUBLIC KEY-----"
            , encodedStringKey);

【讨论】:

以上是关于如何在 Java 中验证 Azure B2C id 令牌的 JWT 签名?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 .Core 应用程序中使用授权验证 Azure B2C 访问令牌

如何通过 Azure AD B2C 使用身份验证功能在 Azure Web App 上添加身份验证?

我们如何在 Xamarin 表单中删除/自定义 Azure AD B2C 身份验证 webview 页面标题栏?

如何在 Azure AD B2C 中将电子邮件验证码更改为验证电子邮件的链接

Azure AD B2C 使用 PHP 验证 JWT

Azure AD B2C 受众验证失败