如何在JMeter请求中包含PKCE Code_Verifier和Code_Challenge


介绍

已经建立了称为PKCE(RFC 7636)的规范,作为针对使用OAuth进行授权代码窃取攻击的对策,该规范是在客户端生成称为Code_Verifier和Code_Challenge的代码并将其嵌入请求参数中的规范。

由于已决定在开发与PKCE兼容的API服务时使用JMeter进行负载测试,并将客户端的代码生成过程并入JMeter,因此将说明当时合并的方法和过程。

生成Code_Verifier和Code_Challenge

为了使用JMeter生成要嵌入到请求中的代码(Code_Verifier,Code_Challenge),请生成一个包含代码生成类的jar。
由于代码生成算法在RFC 7636中的定义如下,因此请实现一个根据规范生成代码的Java程序。

code_verifier =使用的高熵密码随机STRING
保留字符[A-Z] / [a-z] / [0-9] /"-" /"。" /" _" /"?"
[RFC3986]第2.3节中的内容,最小长度为43个字符。
且最大长度为128个字符。

省略了

code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))

省略了

"代码挑战"的ABNF如下。

代码挑战= 43 * 128未保留
未保留= ALPHA / DIGIT /"-" /"。" /" _" /"?"
ALPHA =%x41-5A /%x61-7A
数字=%x30-39

commons-codec用于Base64编码,将commons-lang3用于Base64编码,以将SHA256Hashing算法用于Code_Challenge生成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.sample.pkce;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.RandomStringUtils;
import java.util.Base64;

public class PKCECode {

    private final String CODE_VERFIER_STRING = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-._~";

    public String getCodeVerifier(final int codeVerifierLength) {
        return RandomStringUtils.random(codeVerifierLength, CODE_VERFIER_STRING);
    }

    public String getCodeChallenge(final String codeVerifier) {
        final byte[] hashedCodeVerifier = DigestUtils.sha256(codeVerifier);
        final String base64EncodedCodeVerifier = Base64.getEncoder().encodeToString(hashedCodeVerifier);
        final String base64URLEncodedCodeVerifier = base64EncodedCodeVerifier
                .replaceAll("=","")
                .replaceAll("\\+","-")
                .replaceAll("/","_");
        return base64URLEncodedCodeVerifier;
    }
}

为了确认所实现的程序是否可以正确生成Code_Challenge,我们使用与RFC 7636附录转换示例相同的CodeVerifier进行了测试,并确认转换成功。

1
2
3
4
5
6
@Test
public void getCodeChallengeTest() {
    final String codeVerifier = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk";
    final PKCECode pkceCode = new PKCECode();
    Assert.assertEquals("E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", pkceCode.getCodeChallenge(codeVerifier));
}

使用JMeter要求

启动JMeter之前,将使用的库commons-codeccommons-lang3和生成的jar文件存储在JMeter的外部lib文件夹(<JMeterインストール先>\lib\ext)中。

之后,启动JMeter,从"添加"和"预处理"中选择BeanShell PreProcessor到测试计划,然后输入以下脚本。

1
2
3
4
5
6
7
8
import com.sample.pkce.PKCECode

PKCECode pkceCode = new PKCECode();
String codeVerifier = pkceCode.getCodeVerifier(43); //Code_Verifierの文字長を指定する
String codeChallenge= pkceCode.getCodeChallenge(codeVerifier);

vars.put("codeVerifier",codeVerifier );
vars.put("codeChallenge",codeChallenge);

之后,使用HTTP请求的request参数中的${codeVerifier}${codeChallenge}设置脚本中嵌入的值。

补品376??9111

这次,我解释了如何将代码生成过程合并到BeanShell PreProcessor中,但是如果每次都执行代码生成,则处理负载将应用于客户端,并且可能无法将负载应用于预期的。因此,建议在执行性能负载测试时选择以下两种方法之一。

  • 如何预先将大量代码输出到CSV文件并使用JMeter的``
  • 在所有请求的JMeter参数中直接将Code_Verifier和Code_Challenge设置为固定值

    • 如果可以接受固定值,则也可以使用RFC 7636附录转换示例,而不必费心生成新代码。

参考

有关为什么它是43到128个字符的说明,我在这里详细说明。请参考。

  • https://techblog.yahoo.co.jp/entry/20191219790463/