如何使用PEM格式的RSA公钥解码JWT令牌?

6

我的Java应用程序正在接收JWT。我拥有PEM格式的公钥:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAixn0CGu8/M4txn4pdp8K
m8RQfVa+cHX25/a5sPmzP49u7YlQsRvtOexzgdwDcfUJm3hHMZcbZBtrHKsS8q4Q
QtGQioyVml8EaLuFNFYisaIEldVyRbXFG54FNp03vSU9ImS/cOiM9swo+1w5JgWO
F9efy7JO40LA9E7lv64COUYjFhrn+HRZuKoblL19+Sj49FyXexAUS29UM9PfIdY6
ar1FA8cxzPqW7EkXZ0Mua3IzNnYcjMvUL9TJwoLAAz9S1Tv4Is5jupy9UXkuJ4r8
Jx9DqI3Q3ur0VekYSd5tnTI4K+no9ABCFVv7+6Q45Ec2eB0xMwlqI+phcGhGMVCX
1QIDAQAB
-----END PUBLIC KEY-----

我知道可以使用 JwtConsumer 来验证和解码 JWT:

JwtConsumer jwtConsumer = new JwtConsumerBuilder()
    .setRequireExpirationTime()
    .setVerificationKey(publicKey) // what do I pass here?
    .build();

但是我如何将我的PEM文件转换为可以被 .setVerificationKey() 理解的格式呢?

2个回答

8

我需要先对公钥进行Base64解码,将其转化为字节,然后以X509格式编码这些字节。

以下是一个完整的示例,展示了如何从解码后的JWT中读取一些声明:

package jwt;

import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import sun.misc.BASE64Decoder;

import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Map;

public class JwtExample {

    public void decodeJwt(String jwt) throws Exception {

        // read public key from a file or config or something
        String publicKeyPEM =
            "-----BEGIN PUBLIC KEY-----\n" +
                "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAixn0CGu8/M4txn4pdp8K\n" +
                "m8RQfVa+cHX25/a5sPmzP49u7YlQsRvtOexzgdwDcfUJm3hHMZcbZBtrHKsS8q4Q\n" +
                "QtGQioyVml8EaLuFNFYisaIEldVyRbXFG54FNp03vSU9ImS/cOiM9swo+1w5JgWO\n" +
                "F9efy7JO40LA9E7lv64COUYjFhrn+HRZuKoblL19+Sj49FyXexAUS29UM9PfIdY6\n" +
                "ar1FA8cxzPqW7EkXZ0Mua3IzNnYcjMvUL9TJwoLAAz9S1Tv4Is5jupy9UXkuJ4r8\n" +
                "Jx9DqI3Q3ur0VekYSd5tnTI4K+no9ABCFVv7+6Q45Ec2eB0xMwlqI+phcGhGMVCX\n" +
                "1QIDAQAB\n" +
                "-----END PUBLIC KEY-----";

        // decode to its constituent bytes
        publicKeyPEM = publicKeyPEM.replace("-----BEGIN PUBLIC KEY-----\n", "");
        publicKeyPEM = publicKeyPEM.replace("-----END PUBLIC KEY-----", "");
        BASE64Decoder base64Decoder = new BASE64Decoder();
        byte[] publicKeyBytes = base64Decoder.decodeBuffer(publicKeyPEM);

        // create a key object from the bytes
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PublicKey publicKey = keyFactory.generatePublic(keySpec);

        // create a JWT consumer
        JwtConsumer jwtConsumer = new JwtConsumerBuilder()
            .setRequireExpirationTime()
            .setVerificationKey(publicKey)
            .build();

        // validate and decode the jwt
        // eg: jwt = "eyJhbGciOiJSUzI1NiJ9.eyJ1c2VybmFtZSI6Ik1DaGFtYmU0IiwiZXhwIjoxNDU2OTEwODgzLCJzY29wZSI6WyJvcGVuaWQiLCJwMnAiLCJociIsImRhcyIsIm1lIl0sImNsaWVudF9pZCI6Im1vYmlsZSIsImp0aSI6ImNZcHBMYXltVzlmNXFBZk4ifQ.QqZI9vV8IznTjN-GtUSCri9-6HH6Yl1Oae6K8-d2yjQ4fysF5d3wStdL2kMazl7xeqbtSIsw-F5Aol9eHdGAu54b9IyBEM_QIasy0lnT8xFk0Zi36NJ-7yhl_89f6SB6TGimM59xUvzXxuAw3FzWM6TbiptInrCL2TXkhS69Gng-ANPeiSITUX5A1TDInssds6ZoSb7IOUMtxPGfrbO9sBjx8aJlIu9igkqk4OX5xBmxLp3icoo98I5v9Wt_Huu7eWKBfOskMSEav4X_m5_phbAZJ_F8nWRmcxk6O7hCQdawzegnhMxP2IPIhwlWRNX_8WxkNErq2fJgdazDf8pS_Q";
        JwtClaims jwtDecoded = jwtConsumer.processToClaims(jwt);
        Map<String, Object> jwtClaims = jwtDecoded.getClaimsMap();
        String username = (String) jwtClaims.get("username"); // "MChambe4"

        // ensure the required scope is claimed
        String requiredScope = "das";
        ArrayList scopes = (ArrayList) jwtClaims.get("scope");
        // ensure claims contains the required scope
        if (!scopes.stream().anyMatch(scope -> scope == requiredScope)) {
            throw new Exception("Required scope is not claimed: " + requiredScope);
        }
    }
}

4

从 v0.5.0 版本开始,有一些实用工具可以处理 PEM 编码的公钥,RsaKeyUtil.fromPemEncoded(String pem),这可能会让你的事情变得简单一些。你还可以直接从 JwtClaims 对象中获取声明值,这也可能会更加简化。下面是经过稍微修改后的示例:

    String jwt = "eyJhbGciOiJSUzI1NiJ9.eyJ1c2VybmFtZSI6Ik1DaGFtYmU0IiwiZXhwIjoxNDU2OTEwODgzLCJzY29wZSI6WyJvcGVuaWQiLCJwMnAiLCJociIsImRhcyIsIm1lIl0sImNsaWVudF9pZCI6Im1vYmlsZSIsImp0aSI6ImNZcHBMYXltVzlmNXFBZk4ifQ.QqZI9vV8IznTjN-GtUSCri9-6HH6Yl1Oae6K8-d2yjQ4fysF5d3wStdL2kMazl7xeqbtSIsw-F5Aol9eHdGAu54b9IyBEM_QIasy0lnT8xFk0Zi36NJ-7yhl_89f6SB6TGimM59xUvzXxuAw3FzWM6TbiptInrCL2TXkhS69Gng-ANPeiSITUX5A1TDInssds6ZoSb7IOUMtxPGfrbO9sBjx8aJlIu9igkqk4OX5xBmxLp3icoo98I5v9Wt_Huu7eWKBfOskMSEav4X_m5_phbAZJ_F8nWRmcxk6O7hCQdawzegnhMxP2IPIhwlWRNX_8WxkNErq2fJgdazDf8pS_Q";

    // read public key from a file or config or something
    String publicKeyPEM =
            "-----BEGIN PUBLIC KEY-----\n" +
            "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAixn0CGu8/M4txn4pdp8K\n" +
            "m8RQfVa+cHX25/a5sPmzP49u7YlQsRvtOexzgdwDcfUJm3hHMZcbZBtrHKsS8q4Q\n" +
            "QtGQioyVml8EaLuFNFYisaIEldVyRbXFG54FNp03vSU9ImS/cOiM9swo+1w5JgWO\n" +
            "F9efy7JO40LA9E7lv64COUYjFhrn+HRZuKoblL19+Sj49FyXexAUS29UM9PfIdY6\n" +
            "ar1FA8cxzPqW7EkXZ0Mua3IzNnYcjMvUL9TJwoLAAz9S1Tv4Is5jupy9UXkuJ4r8\n" +
            "Jx9DqI3Q3ur0VekYSd5tnTI4K+no9ABCFVv7+6Q45Ec2eB0xMwlqI+phcGhGMVCX\n" +
            "1QIDAQAB\n" +
            "-----END PUBLIC KEY-----";

    RsaKeyUtil rsaKeyUtil = new RsaKeyUtil();
    PublicKey publicKey = rsaKeyUtil.fromPemEncoded(publicKeyPEM);

    // create a JWT consumer
    JwtConsumer jwtConsumer = new JwtConsumerBuilder()
            .setRequireExpirationTime()
            .setVerificationKey(publicKey)
            .build();

    // validate and decode the jwt
    JwtClaims jwtDecoded = jwtConsumer.processToClaims(jwt);
    String username = jwtDecoded.getStringClaimValue("username"); // "MChambe4"

    // ensure the required scope is claimed
    String requiredScope = "das";
    List<String> scopes = jwtDecoded.getStringListClaimValue("scope");
    if (!scopes.stream().anyMatch(scope -> scope.equals(requiredScope))) {
        throw new Exception("Required scope is not claimed: " + requiredScope);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接