fix(接口测试): 修复大批量场景执行偶发证书认证失败问题

Signed-off-by: fit2-zhao <yong.zhao@fit2cloud.com>
This commit is contained in:
fit2-zhao 2023-11-17 15:40:02 +08:00 committed by Craftsman
parent 488ed0fa90
commit 4712d98627
5 changed files with 110 additions and 41 deletions

View File

@ -620,9 +620,11 @@ public class ElementUtil {
return processor; return processor;
} }
public static void setBaseParams(TestElement sampler, MsTestElement parent, ParameterConfig config, String id, String indexPath) { public static String setBaseParams(TestElement sampler, MsTestElement parent, ParameterConfig config, String id, String indexPath) {
sampler.setProperty("MS-ID", id); sampler.setProperty("MS-ID", id);
sampler.setProperty("MS-RESOURCE-ID", ElementUtil.getResourceId(id, config, parent, indexPath)); String resourceId = ElementUtil.getResourceId(id, config, parent, indexPath);
sampler.setProperty("MS-RESOURCE-ID", resourceId);
return resourceId;
} }
private static final List<String> preOperates = new ArrayList<String>() {{ private static final List<String> preOperates = new ArrayList<String>() {{

View File

@ -129,7 +129,8 @@ public class MsHTTPSamplerProxy extends MsTestElement {
} }
sampler.setProperty(TestElement.TEST_CLASS, HTTPSamplerProxy.class.getName()); sampler.setProperty(TestElement.TEST_CLASS, HTTPSamplerProxy.class.getName());
sampler.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("HttpTestSampleGui")); sampler.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("HttpTestSampleGui"));
ElementUtil.setBaseParams(sampler, this.getParent(), config, this.getId(), this.getIndex()); String resourceId = ElementUtil.setBaseParams(sampler, this.getParent(), config, this.getId(), this.getIndex());
sampler.setMethod(this.getMethod()); sampler.setMethod(this.getMethod());
sampler.setContentEncoding(StandardCharsets.UTF_8.name()); sampler.setContentEncoding(StandardCharsets.UTF_8.name());
sampler.setFollowRedirects(this.isFollowRedirects()); sampler.setFollowRedirects(this.isFollowRedirects());
@ -227,7 +228,7 @@ public class MsHTTPSamplerProxy extends MsTestElement {
if (this.authManager != null && MsAuthManager.mechanismMap.containsKey(this.authManager.getVerification())) { if (this.authManager != null && MsAuthManager.mechanismMap.containsKey(this.authManager.getVerification())) {
this.authManager.setAuth(httpSamplerTree, this.authManager, sampler); this.authManager.setAuth(httpSamplerTree, this.authManager, sampler);
} }
addCertificate(config, httpSamplerTree); addCertificate(config, httpSamplerTree, resourceId);
if (httpConfig != null) { if (httpConfig != null) {
//根据配置增加全局前后至脚本 //根据配置增加全局前后至脚本
JMeterScriptUtil.setScriptByHttpConfig(httpConfig, httpSamplerTree, config, useEnvironment, this.getEnvironmentId(), false); JMeterScriptUtil.setScriptByHttpConfig(httpConfig, httpSamplerTree, config, useEnvironment, this.getEnvironmentId(), false);
@ -500,7 +501,7 @@ public class MsHTTPSamplerProxy extends MsTestElement {
/** /**
* 加载SSL认证 * 加载SSL认证
*/ */
private void addCertificate(ParameterConfig config, HashTree httpSamplerTree) { private void addCertificate(ParameterConfig config, HashTree httpSamplerTree, String resourceId) {
if (config != null && config.isEffective(this.getProjectId()) && getEnvironmentConfig(config).getSslConfig() != null) { if (config != null && config.isEffective(this.getProjectId()) && getEnvironmentConfig(config).getSslConfig() != null) {
KeyStoreConfig sslConfig = getEnvironmentConfig(config).getSslConfig(); KeyStoreConfig sslConfig = getEnvironmentConfig(config).getSslConfig();
List<KeyStoreFile> files = sslConfig.getFiles(); List<KeyStoreFile> files = sslConfig.getFiles();
@ -542,6 +543,7 @@ public class MsHTTPSamplerProxy extends MsTestElement {
keystoreConfig.setProperty("startIndex", 0); keystoreConfig.setProperty("startIndex", 0);
keystoreConfig.setProperty(ElementConstants.MS_KEYSTORE_FILE_PATH, msKeyStore.getPath()); keystoreConfig.setProperty(ElementConstants.MS_KEYSTORE_FILE_PATH, msKeyStore.getPath());
keystoreConfig.setProperty(ElementConstants.MS_KEYSTORE_FILE_PASSWORD, msKeyStore.getPassword()); keystoreConfig.setProperty(ElementConstants.MS_KEYSTORE_FILE_PASSWORD, msKeyStore.getPassword());
keystoreConfig.setProperty("MS-RESOURCE-ID", resourceId);
httpSamplerTree.add(keystoreConfig); httpSamplerTree.add(keystoreConfig);
config.getKeyStoreMap().put(this.getProjectId(), new MsKeyStore(msKeyStore.getPath(), msKeyStore.getPassword())); config.getKeyStoreMap().put(this.getProjectId(), new MsKeyStore(msKeyStore.getPath(), msKeyStore.getPassword()));
} }

View File

@ -61,7 +61,8 @@ public class KeystoreConfig extends ConfigTestElement implements TestBean, TestS
@Override @Override
public void testEnded(String host) { public void testEnded(String host) {
log.info("Destroying Keystore"); log.info("Destroying Keystore");
SSLManager.getInstance().destroyKeystore(); String resourceId = this.getPropertyAsString("MS-RESOURCE-ID");
SSLManager.getInstance().destroyKeystore(resourceId);
} }
@Override @Override
@ -110,10 +111,23 @@ public class KeystoreConfig extends ConfigTestElement implements TestBean, TestS
} catch (IOException e) { } catch (IOException e) {
log.error(e.getMessage()); log.error(e.getMessage());
} }
// 获取请求上的资源ID
String resourceId = this.getPropertyAsString("MS-RESOURCE-ID");
if (StringUtils.isNotBlank(resourceId)) {
KeystoreDTO dto = new KeystoreDTO();
dto.setStartIndex(startIndexAsInt);
dto.setEndIndex(endIndexAsInt);
dto.setPreload(this.preload);
dto.setClientCertAliasVarName(this.clientCertAliasVarName);
dto.setPwd(password);
dto.setPath(path);
SSLManager.keyMap.put(resourceId, dto);
}
SSLManager.getInstance().configureKeystore(Boolean.parseBoolean(preload), SSLManager.getInstance().configureKeystore(Boolean.parseBoolean(preload),
startIndexAsInt, startIndexAsInt,
endIndexAsInt, endIndexAsInt,
clientCertAliasVarName, in, password); clientCertAliasVarName, in, password);
} }
/** /**

View File

@ -0,0 +1,13 @@
package org.apache.jmeter.config;
import lombok.Data;
@Data
public class KeystoreDTO {
private int startIndex;
private int endIndex;
private String preload;
private String clientCertAliasVarName;
private String pwd;
private String path;
}

View File

@ -17,8 +17,12 @@
package org.apache.jmeter.util; package org.apache.jmeter.util;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.apache.jmeter.config.KeystoreDTO;
import org.apache.jmeter.gui.GuiPackage; import org.apache.jmeter.gui.GuiPackage;
import org.apache.jmeter.threads.JMeterContext;
import org.apache.jmeter.threads.JMeterContextService;
import org.apache.jmeter.util.keystore.JmeterKeyStore; import org.apache.jmeter.util.keystore.JmeterKeyStore;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -30,7 +34,9 @@ import java.io.InputStream;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.security.*; import java.security.*;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
/** /**
@ -42,7 +48,6 @@ import java.util.Locale;
* more information. * more information.
* <p> * <p>
* TODO? - N.B. does not currently allow the selection of a client certificate. * TODO? - N.B. does not currently allow the selection of a client certificate.
*
*/ */
public abstract class SSLManager { public abstract class SSLManager {
private static final Logger log = LoggerFactory.getLogger(SSLManager.class); private static final Logger log = LoggerFactory.getLogger(SSLManager.class);
@ -57,20 +62,30 @@ public abstract class SSLManager {
private static final String PKCS12 = "pkcs12"; // $NON-NLS-1$ private static final String PKCS12 = "pkcs12"; // $NON-NLS-1$
/** Singleton instance of the manager */ public static final Map<String, KeystoreDTO> keyMap = new HashMap<>();
/**
* Singleton instance of the manager
*/
private static SSLManager manager; private static SSLManager manager;
private static final boolean IS_SSL_SUPPORTED = true; private static final boolean IS_SSL_SUPPORTED = true;
/** Cache the KeyStore instance */ /**
* Cache the KeyStore instance
*/
private JmeterKeyStore keyStore; private JmeterKeyStore keyStore;
/** Cache the TrustStore instance - null if no truststore name was provided */ /**
* Cache the TrustStore instance - null if no truststore name was provided
*/
private KeyStore trustStore = null; private KeyStore trustStore = null;
// Have we yet tried to load the truststore? // Have we yet tried to load the truststore?
private volatile boolean truststoreLoaded=false; private volatile boolean truststoreLoaded = false;
/** Have the password available */ /**
* Have the password available
*/
protected volatile String defaultpw = System.getProperty(KEY_STORE_PASSWORD); protected volatile String defaultpw = System.getProperty(KEY_STORE_PASSWORD);
private int keystoreAliasStartIndex; private int keystoreAliasStartIndex;
@ -91,8 +106,7 @@ public abstract class SSLManager {
/** /**
* Default implementation of setting the Provider * Default implementation of setting the Provider
* *
* @param provider * @param provider the provider to use
* the provider to use
*/ */
protected void setProvider(Provider provider) { protected void setProvider(Provider provider) {
if (null != provider) { if (null != provider) {
@ -102,7 +116,7 @@ public abstract class SSLManager {
protected synchronized JmeterKeyStore getKeyStore() { protected synchronized JmeterKeyStore getKeyStore() {
if (null == this.keyStore) { if (null == this.keyStore) {
String fileName = System.getProperty(JAVAX_NET_SSL_KEY_STORE,""); // empty if not provided String fileName = System.getProperty(JAVAX_NET_SSL_KEY_STORE, ""); // empty if not provided
String fileType = System.getProperty(JAVAX_NET_SSL_KEY_STORE_TYPE, // use the system property to determine the type String fileType = System.getProperty(JAVAX_NET_SSL_KEY_STORE_TYPE, // use the system property to determine the type
fileName.toLowerCase(Locale.ENGLISH).endsWith(".p12") ? PKCS12 : "JKS"); // otherwise use the name fileName.toLowerCase(Locale.ENGLISH).endsWith(".p12") ? PKCS12 : "JKS"); // otherwise use the name
log.info("JmeterKeyStore Location: {} type {}", fileName, fileType); log.info("JmeterKeyStore Location: {} type {}", fileName, fileType);
@ -111,7 +125,7 @@ public abstract class SSLManager {
log.info("KeyStore created OK"); log.info("KeyStore created OK");
} catch (Exception e) { } catch (Exception e) {
this.keyStore = null; this.keyStore = null;
throw new IllegalArgumentException("Could not create keystore: "+e.getMessage(), e); throw new IllegalArgumentException("Could not create keystore: " + e.getMessage(), e);
} }
try { try {
@ -154,9 +168,9 @@ public abstract class SSLManager {
* *
* @return the configured {@link JmeterKeyStore} * @return the configured {@link JmeterKeyStore}
*/ */
protected synchronized JmeterKeyStore getKeyStore(InputStream is,String password) { protected synchronized JmeterKeyStore getKeyStore(InputStream is, String password) {
if (null == this.keyStore) { if (null == this.keyStore) {
String fileName = System.getProperty(JAVAX_NET_SSL_KEY_STORE,""); // empty if not provided String fileName = System.getProperty(JAVAX_NET_SSL_KEY_STORE, ""); // empty if not provided
String fileType = System.getProperty(JAVAX_NET_SSL_KEY_STORE_TYPE, // use the system property to determine the type String fileType = System.getProperty(JAVAX_NET_SSL_KEY_STORE_TYPE, // use the system property to determine the type
fileName.toLowerCase(Locale.ENGLISH).endsWith(".p12") ? PKCS12 : "JKS"); // otherwise use the name fileName.toLowerCase(Locale.ENGLISH).endsWith(".p12") ? PKCS12 : "JKS"); // otherwise use the name
log.info("JmeterKeyStore Location: {} type {}", fileName, fileType); log.info("JmeterKeyStore Location: {} type {}", fileName, fileType);
@ -165,7 +179,7 @@ public abstract class SSLManager {
log.info("KeyStore created OK"); log.info("KeyStore created OK");
} catch (Exception e) { } catch (Exception e) {
this.keyStore = null; this.keyStore = null;
throw new IllegalArgumentException("Could not create keystore: "+e.getMessage(), e); throw new IllegalArgumentException("Could not create keystore: " + e.getMessage(), e);
} }
try { try {
@ -267,7 +281,7 @@ public abstract class SSLManager {
/** /**
* Opens and initializes the TrustStore. * Opens and initializes the TrustStore.
* * <p>
* There are 3 possibilities: * There are 3 possibilities:
* <ul> * <ul>
* <li>no truststore name provided, in which case the default Java truststore * <li>no truststore name provided, in which case the default Java truststore
@ -280,16 +294,14 @@ public abstract class SSLManager {
* If the KeyStore object cannot be created, then this is currently treated the * If the KeyStore object cannot be created, then this is currently treated the
* same as if no truststore name was provided. * same as if no truststore name was provided.
* *
* @return * @return {@code null} when Java truststore should be used.
* {@code null} when Java truststore should be used.
* Otherwise the truststore, which may be empty if the file could not be * Otherwise the truststore, which may be empty if the file could not be
* loaded. * loaded.
*
*/ */
protected KeyStore getTrustStore() { protected KeyStore getTrustStore() {
if (!truststoreLoaded) { if (!truststoreLoaded) {
truststoreLoaded=true;// we've tried ... truststoreLoaded = true;// we've tried ...
String fileName = System.getProperty(SSL_TRUST_STORE); String fileName = System.getProperty(SSL_TRUST_STORE);
if (fileName == null) { if (fileName == null) {
@ -302,7 +314,7 @@ public abstract class SSLManager {
log.info("TrustStore created OK, Type: JKS"); log.info("TrustStore created OK, Type: JKS");
} catch (Exception e) { } catch (Exception e) {
this.trustStore = null; this.trustStore = null;
throw new RuntimeException("Problem creating truststore: "+e.getMessage(), e); throw new RuntimeException("Problem creating truststore: " + e.getMessage(), e);
} }
try { try {
@ -342,6 +354,26 @@ public abstract class SSLManager {
if (null == SSLManager.manager) { if (null == SSLManager.manager) {
SSLManager.manager = new JsseSSLManager(null); SSLManager.manager = new JsseSSLManager(null);
} }
try {
// 重新加载认证文件
JMeterContext threadContext = JMeterContextService.getContext();
if (SSLManager.manager.keyStore == null && threadContext != null && threadContext.getCurrentSampler() != null) {
String resourceId = threadContext.getCurrentSampler().getPropertyAsString("MS-RESOURCE-ID");
log.info("重新加载认证文件{}", resourceId);
if (StringUtils.isNotBlank(resourceId) && keyMap.containsKey(resourceId)) {
KeystoreDTO dto = keyMap.get(resourceId);
// 加载认证文件
InputStream in = new FileInputStream(new File(dto.getPath()));
SSLManager.manager.configureKeystore(Boolean.parseBoolean(dto.getPreload()), dto.getStartIndex(),
dto.getEndIndex(), dto.getClientCertAliasVarName(), in, dto.getPwd());
keyMap.remove(resourceId);
log.info("移除认证文件{}", resourceId);
}
}
} catch (Exception e) {
log.error("证书处理失败{}", e.getMessage());
}
return SSLManager.manager; return SSLManager.manager;
} }
@ -358,23 +390,19 @@ public abstract class SSLManager {
/** /**
* Configure Keystore * Configure Keystore
* *
* @param preload * @param preload flag whether the keystore should be opened within this method,
* flag whether the keystore should be opened within this method,
* or the opening should be delayed * or the opening should be delayed
* @param startIndex * @param startIndex first index to consider for a key
* first index to consider for a key * @param endIndex last index to consider for a key
* @param endIndex * @param clientCertAliasVarName name of the default key, if empty the first key will be used
* last index to consider for a key
* @param clientCertAliasVarName
* name of the default key, if empty the first key will be used
* as default key * as default key
*/ */
public synchronized void configureKeystore(boolean preload, int startIndex, int endIndex, String clientCertAliasVarName,InputStream is,String password) { public synchronized void configureKeystore(boolean preload, int startIndex, int endIndex, String clientCertAliasVarName, InputStream is, String password) {
this.keystoreAliasStartIndex = startIndex; this.keystoreAliasStartIndex = startIndex;
this.keystoreAliasEndIndex = endIndex; this.keystoreAliasEndIndex = endIndex;
this.clientCertAliasVarName = clientCertAliasVarName; this.clientCertAliasVarName = clientCertAliasVarName;
if(preload) { if (preload) {
keyStore = getKeyStore(is,password); keyStore = getKeyStore(is, password);
} }
} }
@ -382,6 +410,16 @@ public abstract class SSLManager {
* Destroy Keystore * Destroy Keystore
*/ */
public synchronized void destroyKeystore() { public synchronized void destroyKeystore() {
keyStore=null; keyStore = null;
}
/**
* Destroy Keystore
*/
public synchronized void destroyKeystore(String resourceId) {
if (StringUtils.isNotBlank(resourceId)) {
keyMap.remove(resourceId);
}
keyStore = null;
} }
} }