【问题标题】:How to generate a RS256 JWT Token in Karate API testing如何在空手道 API 测试中生成 RS256 JWT 令牌
【发布时间】:2022-07-28 00:28:44
【问题描述】:

如何在空手道 (https://github.com/karatelabs/karate) 功能文件中生成 sha256-RSA 签名的 JWT 令牌?

https://github.com/karatelabs/karate/issues/1138#issuecomment-629453412 有一个很好的方法来处理 HMAC-SHA256(或 Java 术语中的“HmacSHA256”)令牌,即使用对称/共享秘密加密。

但我们需要非对称加密和 RS256 算法(背景见 RS256 vs HS256: What's the difference?)...

【问题讨论】:

    标签: jwt karate mtls rs256


    【解决方案1】:

    好的,我想我明白了:-)。

    非常感谢慷慨的灵魂在这里提供了所有必要的信息:

    所以下面是一个示例空手道特征文件使用

    • 一个 RS256 JWT 令牌(放在 x-jwt 标头中)
    • mTLS(即使用客户端证书进行双向 TLS)

    要做到这一点,需要利用空手道的 JavaScript 和 Java 互操作功能。

    这是我们让它工作的设置:

    0 $ tree
    .
    ├── karate-config.js
    ├── karate.jar
    ├── secrets
    │   ├── client-cert-keystore.p12
    │   ├── client-cert.pem
    │   ├── client-cert_private-key.pem
    │   ├── rsa-4096-cert.pem
    │   ├── rsa-4096-private.pem
    │   └── rsa-4096-public.pem
    └── test.feature
    
    1 directory, 9 files
    

    我们将使用 rsa-4096-* 文件的私钥 rsa-4096-private.pem(保密!)来创建签名令牌。

    所以 JWT 部分的基本文件是

    • rsa-4096-private.pem 用于创建 JWT
    • rsa-4096-public.pem 用于验证令牌/签名,这就是 api/service/server 对您的 JWT 令牌所做的事情(即我们的功能文件中不需要/使用此文件)。您可以尝试使用例如验证生成的令牌https://jwt.io/

    旁注:公钥/私钥对可以使用例如生成openssl.

    作为奖励,此示例包含使用客户端证书和 mTLS(httpbin 可能会优雅地忽略)。如果您不需要这个,您可以简单地从空手道配置文件和命令行中删除 configure ssl... 行和 client_cert_keystore_pass 内容。

    空手道功能文件:

    # test.feature
    Feature: Simple test
    
      Background: 
        # Several helper functions for creating a RS256 signed JWT token.
        # Graciously adapted from:
        # JWT generation in Karate, but with HmacSHA256:
        # https://github.com/karatelabs/karate/issues/1138#issuecomment-629453412
        # Signing with sha256 RSA signature in Java:
        # https://www.quickprogrammingtips.com/java/how-to-create-sha256-rsa-signature-using-java.html
        * def b64encode_bytes = 
          """
          function(bytes) {
              // Base64-encode `bytes`.
              // Returns bytes.
              
              var encoder = Java.type('java.util.Base64')
                  .getUrlEncoder()
                  .withoutPadding()
              return new java.lang.String(encoder.encode(bytes))
          }
          """
    
        # Base64-encode `str`, encodes str to UTF-8 and base64-encodes it.
        # Returns bytes.
        * def b64encode_str = function(str) {return b64encode_bytes(str.getBytes("UTF-8"))}
        
        * def strip_key_header_footer_ws =
          """
          function(key_text) {
              // Strip -----BEGIN ... header + footer and all newline characters.
              // Returns UTF-8-encoded bytes.
    
              // Need string object for replaceAll method.
              var key_text_str = new java.lang.String(key_text)
              var key_str = key_text_str
                .replaceAll("-----BEGIN PRIVATE KEY-----", "")
                .replaceAll("-----END PRIVATE KEY-----", "")
                .replaceAll("\r", "")
                .replaceAll("\n", "")
              return key_str.getBytes('UTF-8')
          }
          """
    
        * def sha256rsa_sign =
          """
          function(bytes, privateKey) { 
              var decoder = Java.type('java.util.Base64')
                  .getDecoder()
              var PKCS8EncodedKeySpec = Java.type(
                  'java.security.spec.PKCS8EncodedKeySpec')
              var spec = new PKCS8EncodedKeySpec(decoder.decode(privateKey))
              var kf = Java.type('java.security.KeyFactory').getInstance("RSA")
              var signature = Java.type('java.security.Signature')
                  .getInstance("SHA256withRSA")
              signature.initSign(kf.generatePrivate(spec))
              signature.update(bytes)
              var signed = signature.sign()
              return signed
          }
          """
    
        * def generate_jwt_sha256rsa = 
          """
          function(payload) {
              // Generate JWT from given `payload` object (dict).
              // Returns SHA256withRSA-signed JWT token (bytes).
    
              var header_encoded = b64encode_str(
                  JSON.stringify({alg: "RS256", typ: "JWT"}))
              var payload_encoded = b64encode_str(JSON.stringify(payload))
              var data_to_sign = header_encoded + '.' + payload_encoded
              var signature = b64encode_bytes(
                  sha256rsa_sign(data_to_sign.getBytes("UTF-8"), privateKey)
                  )
              var token = data_to_sign + '.' + signature
              return token
          }
          """
    
        # enable X509 client certificate authentication with PKCS12 file
        * configure ssl = { keyStore: 'secrets/client-cert-keystore.p12', keyStoreType: 'pkcs12', keyStorePassword: '#(client_cert_keystore_pass)' }
    
        # get private key for JWT generation and API key
        * def privateKeyContent = read('secrets/rsa-4096-private.pem')
        * def privateKey = strip_key_header_footer_ws(privateKeyContent)
    
        # generate JWT
        * def jwt = generate_jwt_sha256rsa({iss: "ExampleApp", exp: "1924902000"})
    
        # put all needed API access credential in the header
        * headers { x-jwt: '#(jwt)'}
    
        * url 'https://httpbin.org'
    
      Scenario Outline: get anything
        Given path '/anything/<anything_id>'
        When method get
        Then status 200
    
        Examples:
          | anything_id |
          | 1           |
    

    空手道配置文件:

    // karate-config.js
    function fn() {
        //var http_proxy = java.lang.System.getenv('http_proxy');
        var client_cert_keystore_pass = java.lang.System.getenv(
            'CLIENT_CERT_KEYSTORE_PASS');
    
        // setup connection
        karate.configure('connectTimeout', 5000);
        karate.configure('readTimeout', 5000);
        //karate.configure('proxy', http_proxy);
    
        var config = {
            client_cert_keystore_pass: client_cert_keystore_pass  
        };
        return config;
    }
    

    如前所述,除非您需要 mTLS,否则您不需要 client_cert_keystore_pass 的东西。此外,您可能不需要超时配置。我已经在代理后面进行了测试,因此它还包含对http_proxy 的一些额外配置支持(已评论,出于教育目的而保留)。适应你的口味。

    运行它:

    0 $ CLIENT_CERT_KEYSTORE_PASS="$PASSWORD" java -jar karate.jar -o /tmp/karate-out test.feature 
    17:34:41.614 [main]  INFO  com.intuit.karate - Karate version: 1.2.1.RC1
    17:34:42.076 [main]  DEBUG com.intuit.karate.Suite - [config] karate-config.js
    17:34:43.942 [main]  DEBUG com.intuit.karate - key store key count for secrets/client-cert-keystore.p12: 1
    17:34:44.535 [main]  DEBUG com.intuit.karate - request:
    1 > GET https://httpbin.org/anything/1
    1 > x-jwt: eyJhbGciO...
    1 > Host: httpbin.org
    1 > Connection: Keep-Alive
    ...
    
    
    ---------------------------------------------------------
    feature: test.feature
    scenarios:  1 | passed:  1 | failed:  0 | time: 1.7300
    ---------------------------------------------------------
    
    17:34:46.577 [main]  INFO  com.intuit.karate.Suite - <<pass>> feature 1 of 1 (0 remaining) test.feature
    Karate version: 1.2.1.RC1
    ======================================================
    elapsed:   4.74 | threads:    1 | thread time: 1.73 
    features:     1 | skipped:    0 | efficiency: 0.36
    scenarios:    1 | passed:     1 | failed: 0
    ======================================================
    
    HTML report: (paste into browser to view) | Karate version: 1.2.1.RC1
    file:///tmp/karate-out/karate-reports/karate-summary.html
    ===================================================================
    
    0 $ 
    

    请注意,我绝不是空手道专家,也不是 JavaScript 或 Java 程序员。所以这可能不是你惯用的空手道/JS/Java 代码。 ;-)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-06-09
      • 2020-05-15
      • 2019-10-08
      • 2019-10-13
      • 2018-11-02
      • 2018-07-30
      • 1970-01-01
      相关资源
      最近更新 更多