現(xiàn)在我們有這樣一個(gè)接口做為壓測對(duì)象:
其使用HTTP協(xié)議進(jìn)行交互,使用RSA加密算法進(jìn)行加密傳輸,然后進(jìn)行密文報(bào)文反饋。
其請(qǐng)求報(bào)文體基礎(chǔ)格式為JSON,如下所示:

{
"format":"json",
"message":{
"head":{
"branchCode":"2110",
"channel":"BESK",
"timeStamp":"20180827105901487",
"transCode":"billQuery",
"transFlag":"01",
"transSeqNum":"BP180827105846210047"
},
"info":{
"epayCode":"VC-PAY2018080265602",
"input1":"123456",
"merchantId":"103881104410001",
"traceNo":"VC180827105846813462",
"userId":"1637206339848118"
}
}
}

我們無法用Jmeter的其它組件來實(shí)現(xiàn)報(bào)文體加密,因此引入Beanshell前置處理器將報(bào)文進(jìn)行加密,接收到返回后再引入Beanshell斷言解密返回報(bào)文并斷言測試結(jié)果,具體拆分步驟如下:
在線程組下加入HTTP取樣器
在取樣器下加入HTTP信息頭管理
在取樣器下加入BeanShell預(yù)處理器,并完成JSON格式報(bào)文組裝
在BeanShell處理器中完成報(bào)文體RSA加密驗(yàn)簽,使用Jmeter變量保存
配置HTTP取樣器使其使用上一步變量值發(fā)送加密報(bào)文
在取樣器下加入BeanShell斷言,解密返回報(bào)文并斷言結(jié)果
配置其它監(jiān)控器如查看結(jié)果樹和聚合報(bào)告等監(jiān)控測試結(jié)果
3、BeanShell實(shí)現(xiàn)
3.1、原始單元測試的java代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package?com.xxx.test.mock;
import?cn.hutool.core.codec.Base64;
import?cn.hutool.core.date.DatePattern;
import?cn.hutool.core.date.DateUtil;
import?cn.hutool.http.HttpResponse;
import?cn.hutool.http.HttpUtil;
import?cn.hutool.json.JSONObject;
import?com.cwl.gw.xxx.utils;
import?org.apache.commons.lang3.StringUtils;
public?void?querySiteInfo() {
????try{
????????JSONObject paramJson =?new?JSONObject();
????????JSONObject messageJson =?new?JSONObject();
????????JSONObject headJson =?new?JSONObject();
????????headJson.put("branchCode",?"2110");
????????headJson.put("channel",?"BESK");
????????headJson.put("timeStamp", DateUtil.format(new?Date(), DatePattern.PURE_DATETIME_MS_PATTERN));
????????headJson.put("transCode",?"billQuery");
????????headJson.put("transFlag",?"01");
????????headJson.put("transSeqNum",?"BP"?+ DateUtil.format(new?Date(), DatePattern.PURE_DATETIME_MS_PATTERN));
????????JSONObject infoJson =?new?JSONObject();
????????infoJson.put("epayCode",?"VC-PAY"?+ DateUtil.format(new?Date(), DatePattern.PURE_DATETIME_MS_PATTERN));
????????infoJson.put("merchantId",?"103881399990002");
????????infoJson.put("userId",?"103881399990002");
????????infoJson.put("input1",?"13000007");
????????infoJson.put("traceNo",?"VC"?+ DateUtil.format(new?Date(), DatePattern.PURE_DATETIME_MS_PATTERN));
????????messageJson.put("head", headJson);
????????messageJson.put("info", infoJson);
????????paramJson.put("format",?"json");
????????paramJson.put("message", messageJson);
????????String req = paramJson.toString().replaceAll(" ",?"");
????????System.out.println("請(qǐng)求參數(shù):"?+ req);
????????String base64 =?new?String(Base64.encode(req.getBytes("utf-8")));
????????String sign = RSAUtil.sign(req, keyFilePath, keyStorePass);
????????System.out.println("簽名:"?+ sign);
????????System.out.println("base64:"?+ base64);
????????System.out.println("reqContent:"?+ sign +?"||"?+ base64);
????????System.out.println("請(qǐng)求地址:"?+ querySiteInfoUrl);
????????HttpResponse resp = HttpUtil.createPost(querySiteInfoUrl).body(sign +?"||"?+ base64,?"text/plain").execute();
????????System.out.println("response:"?+ resp);
????????String context = resp.body().substring(resp.body().indexOf("||") +?2);
????????System.out.println("context:"?+ context);
????????System.out.println("body:"?+ StringUtils.toString(Base64.decode(context),?"utf-8"));
????????System.out.println("驗(yàn)簽:"?+ RSAUtil.verifySign(base64, sign,? certFilePath));
????}?catch(Exception e) {
????????e.printStackTrace();
????}
}
3.2、調(diào)用的RSAUtil原始方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package?com.xxx.gw.xxxx.utils;
import?cn.hutool.core.codec.Base64;
import?org.apache.commons.lang3.StringUtils;
import?javax.servlet.http.HttpServletRequest;
import?javax.servlet.http.HttpServletResponse;
import?java.io.File;
import?java.io.FileInputStream;
import?java.io.IOException;
import?java.io.UnsupportedEncodingException;
import?java.security.KeyStore;
import?java.security.PrivateKey;
import?java.security.PublicKey;
import?java.security.Signature;
import?java.security.cert.CertificateFactory;
import?java.security.cert.X509Certificate;
import?java.util.Enumeration;
/**
?* 驗(yàn)簽和加簽工具類
?* @author VC
?*
?*/
public?class?RSAUtil {
????/**
?????* 加簽
?????* @param dataString 數(shù)據(jù)串
?????* @param keyFilePath 秘鑰文件路徑
?????* @param keyStorePass 秘鑰庫密碼
?????* @return
?????*/
????public?static?String sign(String dataString, String keyFilePath, String keyStorePass) {
????????String signatureString =?null;
????????try?{
????????????KeyStore ks = KeyStore.getInstance("PKCS12");
????????????FileInputStream fis =?new?FileInputStream(keyFilePath);
????????????char[] nPassword =?null;
????????????if?((keyStorePass ==?null) || keyStorePass.trim().equals("")) {
????????????????nPassword =?null;
????????????}?else?{
????????????????nPassword = keyStorePass.toCharArray();
????????????}
????????????ks.load(fis, nPassword);
????????????fis.close();
????????????System.out.println("【返回?cái)?shù)據(jù)加簽】keystore type:"?+ ks.getType());
????????????Enumeration<String> enums = ks.aliases();
????????????String keyAlias =?null;
????????????if?(enums.hasMoreElements()) {
????????????????keyAlias = (String) enums.nextElement();
????????????????System.out.println("【返回?cái)?shù)據(jù)加簽】keyAlias="?+ keyAlias);
????????????}
????????????System.out.println("【返回?cái)?shù)據(jù)加簽】is key entry:"?+ ks.isKeyEntry(keyAlias));
????????????PrivateKey prikey = (PrivateKey) ks.getKey(keyAlias, nPassword);
????????????java.security.cert.Certificate cert = ks.getCertificate(keyAlias);
????????????PublicKey pubkey = cert.getPublicKey();
????????????System.out.println("【返回?cái)?shù)據(jù)加簽】cert class = "?+ cert.getClass().getName());
????????????//System.out.println("【返回?cái)?shù)據(jù)加簽】cert = " + cert);
????????????System.out.println("【返回?cái)?shù)據(jù)加簽】public key = "?+ pubkey);
????????????System.out.println("【返回?cái)?shù)據(jù)加簽】private key = "?+ prikey);
????????????// SHA1withRSA算法進(jìn)行簽名
????????????Signature sign = Signature.getInstance("SHA1withRSA");
????????????sign.initSign(prikey);
????????????byte[] data = dataString.getBytes("utf-8");
????????????byte[] dataBase= Base64.encode(data).getBytes();
????????????// 更新用于簽名的數(shù)據(jù)
????????????sign.update(dataBase);
????????????byte[] signature = sign.sign();
????????????signatureString =?new?String(Base64.encode(signature));
????????????System.out.println("【返回?cái)?shù)據(jù)加簽】signature is : "?+ signatureString);
????????}?catch?(Exception e) {
????????????System.out.println("返回?cái)?shù)據(jù)加簽】失敗??!"?+ e);
????????}
????????return?signatureString;
????}
????/**
?????* 驗(yàn)簽
?????* @param base64body
?????* @param sign
?????* @param certFilePath
?????* @return
?????*/
????public?static?boolean?verifySign(String base64body, String sign, String certFilePath) {
????????X509Certificate cert =?null;
//????? certFilePath = resourcesPath + certFilePath;
????????try?{
????????????CertificateFactory cf = CertificateFactory.getInstance("X.509");
????????????cert = (X509Certificate) cf.generateCertificate(new?FileInputStream(new?File(certFilePath)));
????????????PublicKey publicKey = cert.getPublicKey();
????????????String publicKeyString =?new?String(Base64.encode(publicKey.getEncoded()));
????????????System.out.println("【請(qǐng)求數(shù)據(jù)驗(yàn)簽】證書公鑰:"?+ publicKeyString);
????????????Signature verifySign = Signature.getInstance("SHA1withRSA");
????????????verifySign.initVerify(publicKey);
????????????// 用于驗(yàn)簽的數(shù)據(jù)
????????????verifySign.update(base64body.getBytes("utf-8"));
????????????boolean?flag = verifySign.verify(Base64.decode(sign));
????????????System.out.println("【請(qǐng)求數(shù)據(jù)驗(yàn)簽】驗(yàn)簽結(jié)果:"?+ flag);
????????????return?flag;
????????}?catch?(Exception e) {
????????????System.out.println("【請(qǐng)求數(shù)據(jù)驗(yàn)簽】驗(yàn)簽出異常:"?+ e);
????????}
????????return?false;
????}
3.3、使用BeanShell預(yù)處理器實(shí)現(xiàn)報(bào)文加密:
庫導(dǎo)入部分:
1
2
3
4
import?cn.hutool.json.JSONObject;
import?cn.hutool.core.date.DatePattern;
import?cn.hutool.core.codec.Base64;
import?java.io.FileInputStream;<br>import?java.security.*;
其中:
hutool是java常用的工具類庫,在BeanShell中也可以同樣導(dǎo)入引用;
而java.io和java.security是JDK中提供的基礎(chǔ)類庫,直接導(dǎo)入即可。
JSON報(bào)文組裝:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
JSONObject paramJson =?new?JSONObject();
JSONObject messageJson =?new?JSONObject();
JSONObject headJson =?new?JSONObject();
headJson.put("branchCode",?"2110");
headJson.put("channel",?"BESK");
headJson.put("timeStamp",?new?Date().getTime());
headJson.put("transCode",?"billQuery");
headJson.put("transFlag",?"01");
headJson.put("transSeqNum",?"VC"?+?new?Date().getTime());
JSONObject infoJson =?new?JSONObject();
infoJson.put("epayCode",?"VC-PAY"?+?new?Date().getTime());
infoJson.put("merchantId",?"103881399990002");
infoJson.put("userId",?"103881399990002");
infoJson.put("input1",?"13000007");
infoJson.put("traceNo",?"VC"?+?new?Date().getTime());
messageJson.put("head", headJson);//將headJson放入整體報(bào)文json中
messageJson.put("info", infoJson);//將infoJson放入整體報(bào)文json中
paramJson.put("format",?"json");
paramJson.put("message", messageJson);
String req = paramJson.toString().replaceAll(" ",?"");//去掉報(bào)文中多余空格
log.info("請(qǐng)求參數(shù):"?+ req);
可以看到,基本與原demo代碼保持一致,只是用Date().getTime()方法代替了原DatePattern.PURE_DATETIME_MS_PATTERN。主要是因?yàn)楹笳咴贐eanShell使用過程中出現(xiàn)了找不到方法的問題,簡單起見,由于原代碼只是獲取時(shí)間戳以實(shí)現(xiàn)訂單編號(hào)等的唯一的效果,所以直接用我熟知的可用方法替代掉了,對(duì)整體沒有太大影響。
RSA加密:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
String base64 =?new?String(cn.hutool.core.codec.Base64.encode(req.getBytes("utf-8")));
String signatureString =?null;
File file =?new?File(".//user-rsa.pfx");//文件路徑
try?{
KeyStore ks = KeyStore.getInstance("PKCS12");
FileInputStream fis =?new?FileInputStream(file);
char[] nPassword =?null;
if?((keyStorePass ==?null) || keyStorePass.trim().equals("")) {
nPassword =?null;
}?else?{
nPassword = keyStorePass.toCharArray();
}
ks.load(fis, nPassword);
fis.close();
log.info("【返回?cái)?shù)據(jù)加簽】keystore type:"?+ ks.getType());
Enumeration enums = ks.aliases();
String keyAlias =?null;
if?(enums.hasMoreElements()) {
keyAlias = (String) enums.nextElement();
log.info("【返回?cái)?shù)據(jù)加簽】keyAlias="?+ keyAlias);
}
log.info("【返回?cái)?shù)據(jù)加簽】is key entry:"?+ ks.isKeyEntry(keyAlias));
PrivateKey prikey = (PrivateKey) ks.getKey(keyAlias, nPassword);
java.security.cert.Certificate cert = ks.getCertificate(keyAlias);
PublicKey pubkey = cert.getPublicKey();
log.info("【返回?cái)?shù)據(jù)加簽】cert class = "?+ cert.getClass().getName());
log.info("【返回?cái)?shù)據(jù)加簽】public key = "?+ pubkey);
log.info("【返回?cái)?shù)據(jù)加簽】private key = "?+ prikey);
Signature sign = Signature.getInstance("SHA1withRSA");
sign.initSign(prikey);
byte[] data = req.getBytes("utf-8");
byte[] dataBase= Base64.encode(data).getBytes();// 更新用于簽名的數(shù)據(jù)
sign.update(dataBase);
byte[] signature = sign.sign();
signatureString =?new?String(cn.hutool.core.codec.Base64.encode(signature));
log.info("【返回?cái)?shù)據(jù)加簽】signature is : "?+ signatureString);
}?catch?(Exception e) {
log.info("返回?cái)?shù)據(jù)加簽】失敗??!"?+ e);
}
log.info("簽名:"?+ signatureString);
log.info("base64:"?+ base64);
log.info("reqContent:"?+ sign +?"||"?+ base64);
log.info("請(qǐng)求地址:"?+ querySiteInfoUrl);
String params = signatureString +?"||"?+ base64;<br><br>//將加密后的字符串存入jmeter變量
vars.put("nhzdParams",params);
vars.put("base64",base64);
vars.put("sign",signatureString);
可以看到在BeanShell中,將原有的方法進(jìn)行了一些改寫。由于BeanShell語法和java很大程度上是通用的,因此改寫幅度較小。
此處我們是直接將原RSAUtil類中的邏輯直接寫入了BeanShell而非采用外部依賴的方式,實(shí)際也可以采取將原工具類打成外部包進(jìn)行引用。
使用log.info輸出Jmeter日志,可以使用log view實(shí)時(shí)查看,也可以在jmeter.log中查看到。
完成了對(duì)原始Json的加密后,使用vars.put將加密字串存入jmeter變量以便后續(xù)使用。
3.4、取樣器中發(fā)送請(qǐng)求:

3.4、使用BeanShell斷言器實(shí)現(xiàn)報(bào)文驗(yàn)簽和斷言:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import?cn.hutool.json.JSONObject;
import?cn.hutool.core.date.DatePattern;
import?cn.hutool.core.codec.Base64;
import?java.io.FileInputStream;
import?java.security.*;
import?org.apache.commons.lang3.StringUtils;
X509Certificate cert =?null;
String base64 = vars.get("base64");
String sign = vars.get("sign");
try?{
????CertificateFactory cf = CertificateFactory.getInstance("X.509");
????cert = (X509Certificate) cf.generateCertificate(new?FileInputStream(new?File(".//public-rsa.cer")));
????PublicKey publicKey = cert.getPublicKey();
????String publicKeyString =?new?String(Base64.encode(publicKey.getEncoded()));
????log.info("【請(qǐng)求數(shù)據(jù)驗(yàn)簽】證書公鑰:"?+ publicKeyString);
????Signature verifySign = Signature.getInstance("SHA1withRSA");
????erifySign.initVerify(publicKey);
?// 用于驗(yàn)簽的數(shù)據(jù)
????verifySign.update(base64.getBytes("utf-8"));
????boolean flag = verifySign.verify(Base64.decode(sign));
????log.info("【請(qǐng)求數(shù)據(jù)驗(yàn)簽】驗(yàn)簽結(jié)果:"?+ flag);
}?catch?(Exception e) {
????log.info("【請(qǐng)求數(shù)據(jù)驗(yàn)簽】驗(yàn)簽出異常:"?+ e);
}
1
2
3
4
5
String resp=new?String(prev.getResponseData());
String context = resp.substring(resp.indexOf("||") + 2);
String result = StringUtils.toString(Base64.decode(context),"utf-8");
log.info("系統(tǒng)返回的結(jié)果是:"?+ result);
1
2
3
4
5
6
7
8
9
<br>
if(result.contains("查詢成功")){
Failure=false;
FailureMessage="斷言成功";
log.info("斷言成功");
}else{
????Failure=true;
????FailureMessage="斷言失敗";
????log.info("斷言失敗");<br> }
外部引用和代碼格式部分不再贅述,此組件中主要的邏輯是:
取出上一節(jié)預(yù)處理組件中存入的base64和sign兩個(gè)字符串變量,使用預(yù)設(shè)邏輯進(jìn)行驗(yàn)簽。
通過prev.getResponseData()方法獲得返回報(bào)文,進(jìn)行解密,然后根據(jù)返回中是否存在“查詢成功”關(guān)鍵字進(jìn)行斷言。
到這里BeanShell的應(yīng)用就基本結(jié)束了,我們已經(jīng)實(shí)現(xiàn)了 組裝報(bào)文-加密-發(fā)送報(bào)文-接收返回-解密-斷言 這一系列的工作。
其它常用組件的搭配使用暫略。
深圳網(wǎng)站建設(shè)www.sz886.com