【问题标题】:How to validate id_token without using any library如何在不使用任何库的情况下验证 id_token
【发布时间】:2017-11-29 10:33:38
【问题描述】:

我已经在 java 中成功实现了 OpenID Connect。仅剩余部分是经过验证的 id_token。我已经使用 base64 解码了 id_token 并获取了用户的电子邮件和姓名。现在我想验证 id_token 以便我可以使用该用户详细信息。谁能帮我验证 id_token?

我尝试了下面的代码来验证 id_token

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.RSAPublicKeySpec;
import org.apache.commons.codec.binary.Base64;

public class ValidateIdToken {
    public static final String id_token = "eyJhbGciOiJSUzI1NiIsImtpZCI6IjRlMjVjMTQ2Y2Y4NmU2MGM4ODYxNDdlYWQyNzBlYzAxYTllODU1YTEifQ.eyJhenAiOiIyODQzNDYxNzE1OTEtMGxtOGphamkwOW9iMzNuM2h1bXRrOXYyMzczcjgxcnEuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiIyODQzNDYxNzE1OTEtMGxtOGphamkwOW9iMzNuM2h1bXRrOXYyMzczcjgxcnEuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMDk0NjgzNjY4OTQ1NjQwMDU3MzgiLCJoZCI6ImluZm9vYmplY3RzLmNvbSIsImVtYWlsIjoia2lzaG9yZS5zdXRoYXJAaW5mb29iamVjdHMuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImF0X2hhc2giOiJod255SGc0dnlrOTZXVUxZYktyRTl3IiwiaXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tIiwiaWF0IjoxNDk4NDc5MDIyLCJleHAiOjE0OTg0ODI2MjIsIm5hbWUiOiJLaXNob3JlIFN1dGhhciIsInBpY3R1cmUiOiJodHRwczovL2xoNS5nb29nbGV1c2VyY29udGVudC5jb20vLXJNb3kzeEpfRHA4L0FBQUFBQUFBQUFJL0FBQUFBQUFBQUFBL0FJNnlHWHp5WXdoeXMtU0FwVnhodDBJbTFOQktlYXpZa0Evczk2LWMvcGhvdG8uanBnIiwiZ2l2ZW5fbmFtZSI6Iktpc2hvcmUiLCJmYW1pbHlfbmFtZSI6IlN1dGhhciIsImxvY2FsZSI6ImVuIn0.iWJquu48swut8denWospcnOu-0QpQjWrw7d_K7lDdtwJ6xo6zE1rnmoOHw1xxeWrE9e2Vy-AqJ4mLHYn3rGoRe026oWLvwQMAgcNmhBENwvd9kAbYZukg_J7Ypy2Srnsu97u0q-QTAs3MBmxO0Yp2bHwuR5gmwEvTaE6gTtSZOf_Tg5FyrqaZgdrBuXxxLvQqHsNXIp4MxthxBtAH1kGo_KBrVAoKH1dRkKzsjSlt73nB_uKODGe2FoUwrNybItp9t5xE653pnxp2L6rbLJ2U9TU3gGSQ5yDdWPMt-AIAGJ7d1WOIHHGpN9FcT3KYEueS0j0h6Kfktr5XCCYnuEwQg";
    public static final String[] id_token_parts = id_token.split("\\.");

    public static final String MODULUS = "5SGw1jcqyFYEZaf39RoxAhlq-hfRSOsneVtsT2k09yEQhwB2myvf3ckVAwFyBF6y0Hr1psvu1FlPzKQ9YfcQkfge4e7eeQ7uaez9mMQ8RpyAFZprq1iFCix4XQw-jKW47LAevr9w1ttZY932gFrGJ4gkf_uqutUny82vupVUETpQ6HDmIL958SxYb_-d436zi5LMlHnTxcR5TWIQGGxip-CrD7vOA3hrssYLhNGQdwVYtwI768EvwE8h4VJDgIrovoHPH1ofDQk8-oG20eEmZeWugI1K3z33fZJS-E_2p_OiDVr0EmgFMTvPTnQ75h_9vyF1qhzikJpN9P8KcEm8oGu7KJGIn8ggUY0ftqKG2KcWTaKiirFFYQ981PhLHryH18eOIxMpoh9pRXf2y7DfNTyid99ig0GUH-lzAlbKY0EV2sIuvEsIoo6G8YT2uI72xzl7sCcp41FS7oFwbUyHp_uHGiTZgN7g-18nm2TFmQ_wGB1xCwJMFzjIXq1PwEjmg3W5NBuMLSbG-aDwjeNrcD_4vfB6yg548GztQO2MpV_BuxtrZDJQm-xhJXdm4FfrJzWdwX_JN9qfsP0YU1_mxtSU_m6EKgmwFdE3Yh1WM0-kRRSk3gmNvXpiKeVduzm8I5_Jl7kwLgBw24QUVaLZn8jC2xWRk_jcBNFFLQgOf9U";
    public static final String EXPONENT = "AQAB";

    public static final String ID_TOKEN_HEADER = base64UrlDecode(id_token_parts[0]);
    public static final String ID_TOKEN_PAYLOAD = base64UrlDecode(id_token_parts[1]);
    public static final byte[] ID_TOKEN_SIGNATURE = base64UrlDecodeToBytes(id_token_parts[2]);

    public static String base64UrlDecode(String input) {
        byte[] decodedBytes = base64UrlDecodeToBytes(input);
        String result = new String(decodedBytes, StandardCharsets.UTF_8);
        return result;
    }

    public static byte[] base64UrlDecodeToBytes(String input) {
        Base64 decoder = new Base64(-1, null, true);
        byte[] decodedBytes = decoder.decode(input);

        return decodedBytes;
    }

    public static void main(String args[]) {
        validateToken();
    }

    public static void validateToken() {
        PublicKey publicKey = getPublicKey(MODULUS, EXPONENT);
        byte[] data = (id_token_parts[0] + "." + id_token_parts[1]).getBytes(StandardCharsets.UTF_8);

        try {
            boolean isSignatureValid = verifyUsingPublicKey(data, ID_TOKEN_SIGNATURE, publicKey);
            System.out.println("isSignatureValid: " + isSignatureValid);
        } catch (GeneralSecurityException e) {
            e.printStackTrace();
        }

    }

    public static PublicKey getPublicKey(String MODULUS, String EXPONENT) {
        byte[] nb = base64UrlDecodeToBytes(MODULUS);
        byte[] eb = base64UrlDecodeToBytes(EXPONENT);
        BigInteger n = new BigInteger(1, nb);
        BigInteger e = new BigInteger(1, eb);

        RSAPublicKeySpec rsaPublicKeySpec = new RSAPublicKeySpec(n, e);
        try {
            PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(rsaPublicKeySpec);

            return publicKey;
        } catch (Exception ex) {
            throw new RuntimeException("Cant create public key", ex);
        }
    }

    private static boolean verifyUsingPublicKey(byte[] data, byte[] signature, PublicKey pubKey)
            throws GeneralSecurityException {
        Signature sig = Signature.getInstance("SHA256withRSA");
        sig.initVerify(pubKey);
        sig.update(data);

        return sig.verify(signature);
    }
}

错误

java.security.SignatureException: Signature length not correct: got 256 but was expecting 512
    at sun.security.rsa.RSASignature.engineVerify(RSASignature.java:189)
    at java.security.Signature$Delegate.engineVerify(Signature.java:1219)
    at java.security.Signature.verify(Signature.java:652)
    at com.certain.sso.handler.ValidateIdToken.verifyUsingPublicKey(ValidateIdToken.java:75)
    at com.certain.sso.handler.ValidateIdToken.validateToken(ValidateIdToken.java:45)
    at com.certain.sso.handler.ValidateIdToken.main(ValidateIdToken.java:37)

【问题讨论】:

  • 请添加有关您使用的编程语言以及您在使用 Nimbus 和其他库时遇到的问题的信息。
  • @Ján Halaša 更新问题
  • 我试图从 MODULUS 和 EXPONENT 创建公钥,但 MODULUS 值似乎是无效的 Base64 字符串。我从java.util.Base64.getDecoder().decode(MODULUS) 得到Illegal base64 character 2d
  • 您还有其他方法可以验证 id_token 吗?

标签: java oauth-2.0 openid-connect


【解决方案1】:

Java 异常是由于您的签名未正确计算: ID 令牌标头中的“HS256”alg 参数不仅仅意味着您需要计算 SHA-256 哈希并使用 RSA 私钥对其进行“加密”,然后使用 base64 对其进行编码并将其填充到 ID 的末尾令牌。

HS256 意味着您需要使用 RFC-8017 完全指定的 PKCS#1 "SHA256withRSA" 算法。根据此 RFC,签名的八位字节长度必须与 RSA 模数相同。您的 RSA 模数为 512 字节长度,因此您的 ID 令牌的签名部分必须为 512 字节长度。即使哈希函数 (HMAC-SHA256) 返回 256 位长度的值。

因此,要从 256 位长度的 HMAC-SHA256 哈希计算 512 字节长度的签名,RFC-8017 要求您使用 I2OSP Integer-to-Octet-String 原语,该原语采用 2 个参数:您的 256 位哈希,和整数 512(你的 RSA 模数的长度)。 I2OSP 将输出一个 512 字节长度的八位字节字符串,您必须最终将其作为参数提供给 sig.verify()。

【讨论】:

    猜你喜欢
    • 2018-10-17
    • 2013-09-16
    • 2018-01-31
    • 2018-09-10
    • 1970-01-01
    • 1970-01-01
    • 2020-09-06
    • 1970-01-01
    • 2022-07-28
    相关资源
    最近更新 更多