c++ 验证jwt jwk转pem

C++ 验证jwt(json web token)

tip: 本文主要以apple 登录验证举例

一:jwt

jwt 分为 3 部分组成 base64(header) + "." + base64(payload) + "." + signature

例:

"eyJraWQiOiJlWGF1bm1MIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmh1aXNob3ViYW8uQ29sYUdvb2RzIiwiZXhwIjoxNTg0MDAxMjM3LCJpYXQiOjE1ODQwMDA2MzcsInN1YiI6IjAwMTQ3Mi5kYWIwNGY5YmE5ZDM0ZjAzYWQ2NDFmYTgyZDFjNTk4Yi4wOTQ1Iiwibm9uY2UiOiJFb0l3WmV2bUZJIiwiY19oYXNoIjoiZVkzUzF3ZVhzQzAxTFAzSmVZdTh1dyIsImVtYWlsIjoieWY3M3BzOGhtM0Bwcml2YXRlcmVsYXkuYXBwbGVpZC5jb20iLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJpc19wcml2YXRlX2VtYWlsIjoidHJ1ZSIsImF1dGhfdGltZSI6MTU4NDAwMDYzNywibm9uY2Vfc3VwcG9ydGVkIjp0cnVlfQ.wIZKE8N5xcHqql2ZqMs_bza-nakfCKqhh332ZRqPbjIc4vYstfUoIo4xWG0ALSBvcM3nNPwOA-tM3oQaD6VObOEYFlATSF_9z6WR0MTlryFkJS_DEvy_VMvcXBzEun9ccdx-21WxXmkQy74TxEM4Wl_kZMsjNVdto0Pw0oMGi7Zio52JthO7Q5pKnOpYoGiTJRi0L2egh6BXm72he9XYu6PbZBnwr_Gf-S05bA5EMFXD896DciKtGeWrw91f7DBqqmXTUqlBUuSvFgbVq6UftkmVqjrIIAb9r5I42QayToOTHQAzcxk8ena7OnY16EKK4FLZyHx1DW87_7zLDg6-SQ"

1.将header base64.decode

{
  "kid": "eXaunmL",
  "alg": "RS256"
}

2.将 payload base64.decode

{
  "iss": "https://appleid.apple.com",
  "aud": "com.huishoubao.ColaGoods",
  "exp": 1584001237,
  "iat": 1584000637,
  "sub": "001472.dab04f9ba9d34f03ad641fa82d1c598b.0945",
  "nonce": "EoIwZevmFI",
  "c_hash": "eY3S1weXsC01LP3JeYu8uw",
  "email": "[email protected]",
  "email_verified": "true",
  "is_private_email": "true",
  "auth_time": 1584000637,
  "nonce_supported": true
}

4.最后一部分就是剩下的signature

​ signature的验证是 base64UrlEncode(header) + "." + base64UrlEncode(payload)

二.JWK (json web key ) To Pem

c++ 通过JWK 对JWT 进行验证,首先需要将JWK 转换成 PEM 格式的

从苹果接口获取JWK :https://appleid.apple.com/auth/keys

{
  "keys": [
    {
      "kty": "RSA",
      "kid": "86D88Kf",
      "use": "sig",
      "alg": "RS256",
      "n": "iGaLqP6y-SJCCBq5Hv6pGDbG_SQ11MNjH7rWHcCFYz4hGwHC4lcSurTlV8u3avoVNM8jXevG1Iu1SY11qInqUvjJur--hghr1b56OPJu6H1iKulSxGjEIyDP6c5BdE1uwprYyr4IO9th8fOwCPygjLFrh44XEGbDIFeImwvBAGOhmMB2AD1n1KviyNsH0bEB7phQtiLk-ILjv1bORSRl8AK677-1T8isGfHKXGZ_ZGtStDe7Lu0Ihp8zoUt59kx2o9uWpROkzF56ypresiIl4WprClRCjz8x6cPZXU2qNWhu71TQvUFwvIvbkE1oYaJMb0jcOTmBRZA2QuYw-zHLwQ",
      "e": "AQAB"
    },
    {
      "kty": "RSA",
      "kid": "eXaunmL",
      "use": "sig",
      "alg": "RS256",
      "n": "4dGQ7bQK8LgILOdLsYzfZjkEAoQeVC_aqyc8GC6RX7dq_KvRAQAWPvkam8VQv4GK5T4ogklEKEvj5ISBamdDNq1n52TpxQwI2EqxSk7I9fKPKhRt4F8-2yETlYvye-2s6NeWJim0KBtOVrk0gWvEDgd6WOqJl_yt5WBISvILNyVg1qAAM8JeX6dRPosahRVDjA52G2X-Tip84wqwyRpUlq2ybzcLh3zyhCitBOebiRWDQfG26EH9lTlJhll-p_Dg8vAXxJLIJ4SNLcqgFeZe4OfHLgdzMvxXZJnPp_VgmkcpUdRotazKZumj6dBPcXI_XID4Z4Z3OM1KrZPJNdUhxw",
      "e": "AQAB"
    }
  ]
}

该JWK 中 kid 表示 jwt 使用的加密串, n 为 rsa 的模数,e 为rsa 的指数,alg 为加密的算法

n,e 两个字符串特别重要,需要用它来生成我们的rsa 公钥

C++ 没有找到比较好的现成的转换的库,所以使用openssl 进行生成

#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/ssl.h>
#include <openssl/bio.h>
#include <openssl/md5.h>
#include <openssl/des.h>
#include <openssl/pkcs12.h>
#include "cppcodec/base64_url_unpadded.hpp"

bool ConvertJwkToPem(const std::string &strnn, const std::string& strnn, std::string& strPubKey){
    auto nn = cppcodec::base64_url_unpadded::decode(strnn);
    auto ee = cppcodec::base64_url_unpadded::decode(stree);

    BIGNUM * modul = BN_bin2bn(nn.data(), nn.size(), NULL);
    BIGNUM * expon = BN_bin2bn(ee.data(), ee..size(), NULL);

    RSA * rr = RSA_new();
    EVP_PKEY * pRsaKey = EVP_PKEY_new();

    rr->n = modul;
    rr->e = expon;
    EVP_PKEY_assign_RSA(pRsaKey, rr);
    unsigned char * ss = new unsigned char[1024];
    unsigned char *desc = new unsigned char[1024];
    memset(desc, 0, 1024);

    BIO* bio = BIO_new(BIO_s_mem());
    PEM_write_bio_RSA_PUBKEY(bio, rr);
    BIO_read(bio, desc, 1024);
    strPubKey = (char*)desc;
    BIO_free(bio);
    RSA_free(rr);
    if (strPubKey.empty()) {
        return false;
    }
    return true;
}

在这里推荐一个cpp 好用的工具库 cppcodec ,只需要包含头文件就可以了,不用编译,不用链接第三方动态库

三:验证JWT

在上面拿到了publickey 之后后面的就简单了,规则为 RS256(base64UrlEncode(header) + "." + base64UrlEncode(payload) + publickey)加密后生成的密文 与 signature 进行比较,相等的话则验证通过。下面贴出一段验证代码示例:同样的,为了方便,也是用到了一个开源库

bool verifyJwt(const std::string &strJwtToken, const std::string& strPubKey)
try
    {
        auto decoded = jwt::decode(strJwtToken);
        auto verifie = jwt::verify().allow_algorithm(jwt::algorithm::rs256(strPubkey, "", "", "")).with_issuer("auth0");
        bool bok = verifie.verify(decoded);
    }
    catch (jwt::signature_verification_exception *e)
    {
        std::string strException = e->what();
        INFO("verification_exception:" + strException);
        m_strRet = "1";
        m_strRetCode = tools::CStringTools::Int2String(error_num::PARAMETER_ERROR);
        m_strRetInfo = error_string::ErrorStr(error_num::PARAMETER_ERROR);
        return false;
    }
    catch (...)
    {
        INFO("identityToken error" );
        m_strRet = "1";
        m_strRetCode = tools::CStringTools::Int2String(error_num::PARAMETER_ERROR);
        m_strRetInfo = error_string::ErrorStr(error_num::PARAMETER_ERROR);
        return false;
    }

以上代码我将开源库部分代码改了方便使用

四.参考文章

https://jwt.io/

https://blog.csdn.net/xiaoting451292510/article/details/46729377

https://blog.csdn.net/xunmeng2002/article/details/82385004?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

https://mkjwk.org/

https://www.cnblogs.com/tml839720759/p/3926006.html

https://stackoverflow.com/questions/57217529/how-to-convert-jwk-public-key-to-pem-format-in-c

五:用到的开源库

openssl :

cppcodec:https://www.ctolib.com/article/goGitHub/cppcodec.html

jwt-cpp:https://github.com/Thalhammer/jwt-cpp/issues

猜你喜欢

转载自www.cnblogs.com/bohat/p/12482357.html
今日推荐