В этой статье я собираюсь показать, как выполнить модульное тестирование конечной точки HTTPS с помощью Apache HttpClient . Кроме того, я покажу, как настроить конечную точку HTTPS, используя Mule ESB в качестве фиктивного HTTPS-сервера.
Пример в этой статье ограничен показом того, как клиент будет утверждать идентичность сервера с помощью склада доверенных сертификатов. Сервер не будет утверждать личность клиента (взаимная аутентификация).
Создать пример проекта
Я собираюсь разработать пример проекта в Spring Tool Suite с установленным плагином Anypoint Studio . Основная причина этого заключается в том, чтобы иметь возможность удобно и быстро создать проект с соответствующими зависимостями Mule. Если вы можете собрать необходимые зависимости каким-либо другим способом, не стесняйтесь использовать IDE по вашему выбору.
Если вы используете плагин Anypoint Studio, вам нужно установить хотя бы одну среду исполнения Mule. Я использовал среду выполнения 3.5.0. Версия 3.5.0 Mule CE содержит версию 4.2 библиотеки Apache HttpClient, это версия HttpClient, которую я выбрал.
Давайте начнем с создания примера проекта:
- В меню File выберите New и затем Other.
В появившемся диалоговом окне выберите мастер проекта Mule.
- В диалоговом окне «Параметры проекта» введите имя проекта и выберите среду выполнения Mule, которую будет использовать проект. Я назову свой проект «HTTPSUnitTestWithHTTPClient» и, как и раньше, использую среду выполнения Mule 3.5.0 CE.
Теперь мы создали пример проекта. Прежде чем мы реализуем что-либо в этом проекте, мы собираемся создать хранилище ключей сервера и хранилище доверенных сертификатов клиента.
Создайте хранилище ключей сервера
Хранилище ключей сервера — это файл, который содержит закрытый ключ сервера. Этот ключ используется, когда клиенты договариваются о соединении с сервером, чтобы клиенты могли проверить, что сервер, к которому он подключается, действительно является сервером, к которому клиент хочет подключиться, а не обманщиком. Кроме того, согласование HTTPS приводит к зашифрованному соединению, обеспечивающему обмен информацией между клиентом и сервером, который защищен от перехвата.
Чтобы создать хранилище ключей сервера, я использовал следующие команды из терминала или командного окна:
keytool-keypass secret-storepass secret-genkey-aliashttpskey-keyalg RSA-keystore server_keystore.jks
После ввода команды вам будет задан ряд вопросов. Ответьте на них, как вы хотите. Ниже я ответил на вопросы при создании хранилища ключей на моем сервере.
What is your first and last name? [Unknown]: Ivan Krizsan What is the name of your organizational unit? [Unknown]: Home Unit What is the name of your organization? [Unknown]: Home Organization What is the name of your City or Locality? [Unknown]: Home City What is the name of your State or Province? [Unknown]: Home State What is the two-letter country code for this unit? [Unknown]: SE Is CN=Ivan Krizsan, OU=Home Unit, O=Home Organization, L=Home City, ST=Home State, C=SE correct? [no]: yes
Результатом должен быть файл с именем «server_keystore.jks».
Создайте клиентское хранилище доверенных сертификатов
Склад доверенных сертификатов клиента содержит одну или несколько записей для сертификатов, которым клиент должен доверять. Когда клиент пытается подключиться к серверу, удостоверение сервера проверяется по записям в хранилище доверенных сертификатов.
Чтобы создать клиентское хранилище доверенных сертификатов, я использовал следующие две команды:
keytool -export -alias httpskey -keystore server_keystore.jks -storepass secret -file server.cert keytool -import -v -trustcacerts -alias httpskey -keystore client_truststore.jks -storepass secret -file server.cert
После ввода второй команды вас спросят, доверять ли сертификату в файле server.cert. Ответьте да на этот вопрос.
Теперь у вас должно быть всего три файла: «server_keystore.jks», «client_truststore.jks» и «server.cert». Скопируйте первые два файла в «src / main / resources» примера проекта, который мы создали ранее.
Создать макет конфигурации сервера
В примере проекта должен быть файл с именем «httpsunittestwithhttpclient.xml» в каталоге «src / main / app». Вставьте следующую конфигурацию Mule в этот файл:
<?xml version="1.0" encoding="UTF-8"?>
<mule
xmlns:https="http://www.mulesoft.org/schema/mule/https"
xmlns="http://www.mulesoft.org/schema/mule/core"
xmlns:doc="http://www.mulesoft.org/schema/mule/documentation"
xmlns:spring="http://www.springframework.org/schema/beans"
xmlns:test="http://www.mulesoft.org/schema/mule/test"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-current.xsd
http://www.mulesoft.org/schema/mule/core
http://www.mulesoft.org/schema/mule/core/current/mule.xsd
http://www.mulesoft.org/schema/mule/https
http://www.mulesoft.org/schema/mule/https/current/mule-https.xsd
http://www.mulesoft.org/schema/mule/test
http://www.mulesoft.org/schema/mule/test/current/mule-test.xsd">
<https:connector
name="HTTPSNoClientCert"
clientSoTimeout="10000"
serverSoTimeout="10000"
socketSoLinger="0">
<!-- Removes the Mule session information from messages sent over this connector. -->
<service-overrides sessionHandler="org.mule.session.NullSessionHandler"/>
<!-- Sets the keystore and passwords used by this connector. -->
<https:tls-key-store path="server_keystore.jks"
keyPassword="secret" storePassword="secret" />
</https:connector>
<flow name="https-noclientcert-flow">
<https:inbound-endpoint
exchange-pattern="request-response"
host="localhost" port="8082"
connector-ref="HTTPSNoClientCert"/>
Если вы знакомы с файлами конфигурации Mule, не стесняйтесь пропустить следующее объяснение.
Приведенный выше файл конфигурации Mule содержит:
- Разъем HTTPS с именем «HTTPSNoClientCert».
Разъем Mule содержит конфигурацию для средства связи. В этом случае для связи используется базовый протокол HTTPS с некоторыми параметрами времени ожидания и хранилище ключей.
Кроме того, на соединителе настроен нулевой обработчик сеанса, который предотвратит отправку Mule информации сеанса Mule клиентам. - Поток с именем «https-noclientcert-flow».
Поток Мула описывает потенциальный путь, по которому может пройти сообщение. - Поток в свою очередь содержит входящую конечную точку.
Входящая конечная точка является получателем сообщений, отправляемых потоку.
Как можно видеть, поток ссылается на упомянутый ранее HTTPS-коннектор с использованием атрибута connector-ref. Атрибуты хоста и порта указывают адрес и порт, на котором входящая конечная точка будет прослушивать входящие сообщения. - После входящей конечной точки поток содержит тестовый компонент.
Его цель — записывать полученные сообщения на консоль. - Наконец, поток содержит процессор, который устанавливает полезную нагрузку (исходящего) сообщения.
Мы можем протестировать фиктивный HTTPS-сервер, щелкнув правой кнопкой мыши на файле конфигурации Mule, который мы только что создали, и выбрав Run As → Mule Application. Затем откройте браузер, например Firefox, и введите URL-адрес «https: // localhost: 8082 /».
Может быть предупреждение о ненадежном сертификате, но просто подтвердите, что вы доверяете сертификату. Сделав это, вы должны увидеть такую строку в окне браузера, если все работает как положено:
Hello, the time is now 2014-10-05T21:28:19.948+02:00
Добавьте файл конфигурации Log4J
Чтобы увидеть, что регистрирует Mule, добавьте следующий файл с именем «log4j.xml» в каталог «src / test / resources» в примере проекта:
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration>
<appender name="stdout" class="org.apache.log4j.ConsoleAppender">
<param name="Target" value="System.out" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d %-5p %-30C - %m%n" />
</layout>
</appender>
<logger name="org.mule">
<level value="INFO"/>
</logger>
<logger name="org.mule.transport">
<level value="WARN"/>
</logger>
<logger name="org.mule.tck.functional">
<level value="DEBUG"/>
</logger>
<root>
<priority value="WARN" />
<appender-ref ref="stdout" />
</root>
</log4j:configuration>
Выполнить тест
Пока что мы только готовились к реальной цели этой статьи, а именно к тесту, показывающему, как подключиться к серверу по HTTPS. Причина, по которой я предпочитаю подключение Apache HttpClient к серверу, например, через клиентский API-интерфейс Mule, заключается в том, что он дает мне больший контроль над запросом, отправляемым на сервер, и я также знаю, что ответ, полученный в моем тестовом коде, имеет не были изменены в любом случае.
Тест, который подключается к серверу-макету, выглядит следующим образом:
package se.ivankrizsan.mule.https;
import java.security.KeyStore;
import java.util.HashMap;
import java.util.Map;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.HeaderIterator;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mule.tck.junit4.FunctionalTestCase;
/**
* Tests connecting to a HTTPS server that does not require a client certificate.
*
* @author Ivan Krizsan
*/
public class HttpsNoClientCertificateTest extends FunctionalTestCase {
/* Constant(s): */
/** Mule configuration file(s) loaded by the test-case. */
private final static String[] TEST_CONFIGURATION_FILES =
{ "httpsunittestwithhttpclient.xml" };
/** URL which to send test-request to. */
private final static String TEST_ENDPOINT_URL = "https://localhost:8082/";
/** Client truststore. */
private static final String CLIENT_TRUSTSTORE = "client_truststore.jks";
/** Client truststore password. */
private static final String CLIENT_TRUSTSTORE_PASSWORD = "secret";
/* Instance variable(s): */
private DefaultHttpClient mHttpClient;
@Override
protected String[] getConfigFiles() {
return TEST_CONFIGURATION_FILES;
}
/**
* Prepares for tests by performing creating and initializing the HTTP
* client used to send test-request.
*
* @throws Exception If error occurred.
*/
@Before
public void setUp() throws Exception {
mHttpClient = new DefaultHttpClient();
/* Load client truststore. */
final KeyStore theClientTruststore = KeyStore.getInstance("JKS");
theClientTruststore.load(loadResource(CLIENT_TRUSTSTORE),
CLIENT_TRUSTSTORE_PASSWORD.toCharArray());
/* Create a trust manager factory using the client truststore. */
final TrustManagerFactory theTrustManagerFactory =
TrustManagerFactory.getInstance(TrustManagerFactory
.getDefaultAlgorithm());
theTrustManagerFactory.init(theClientTruststore);
/*
* Create a SSL context with a trust manager that uses the
* client truststore.
*/
final SSLContext theSslContext = SSLContext.getInstance("TLS");
theSslContext.init(null, theTrustManagerFactory.getTrustManagers(),
null);
/*
* Create a SSL socket factory that uses the client truststore SSL
* context and that does not perform any kind of hostname verification.
* IMPORTANT: Hostname verification should be performed in a
* production environment!
* To turn on hostname verification, change the
* ALLOW_ALL_HOSTNAME_VERIFIER below to STRICT_HOSTNAME_VERIFIER.
*/
final SSLSocketFactory theSslSocketFactory =
new SSLSocketFactory(theSslContext,
SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
/*
* Register the SSL socket factory to be used with HTTPS connections
* with the HTTP client.
* A {@code Scheme} object is used to associate the protocol scheme,
* such as HTTPS in this case, and a socket factory.
*/
final Scheme theHttpsScheme =
new Scheme("https", 443, theSslSocketFactory);
mHttpClient.getConnectionManager().getSchemeRegistry().register(
theHttpsScheme);
}
/**
* Tests a successful HTTPS connection.
*
* @throws Exception If error occurs. Indicates test failure.
*/
@Test
public void testSuccessfulConnection() throws Exception {
/*
* Create a HTTP GET to the HTTPS endpoint and set a HTTP header
* on the request.
*/
final HttpGet theHttpGet = new HttpGet(TEST_ENDPOINT_URL);
theHttpGet.addHeader("test-header-name", "test-header-value");
/* Send the HTTPS request. */
final HttpResponse theHttpResponse = mHttpClient.execute(theHttpGet);
Assert.assertNotNull("A response message should have been received",
theHttpResponse);
/* Insert headers from the response in a map. */
final Map<String, String> theResponseHeadersMap =
new HashMap<String, String>();
final HeaderIterator theHeaderIterator =
Код довольно хорошо прокомментирован, но я, несмотря на это, приведу краткий анализ:
- Тест-класс наследуется от класса FunctionalTestCase .
FunctionalTestCase — это базовый класс для тестов, который хочет запустить экземпляр Mule как часть тестов. - Метод getConfigFiles возвращает файлы конфигурации Mule, которые должны быть загружены в экземпляр Mule, запущенный как часть тестов.
В данном случае это файл конфигурации Mule, содержащий фиктивный сервер. - Метод setUp создает и настраивает экземпляр Apache HttpClient, который использует определенное хранилище доверенных сертификатов для проверки подлинности серверов, с которыми он связывается.
- Тест-метод testSuccessfulConnection проверяет, что ожидается успешной попыткой подключения к серверу.
Выполнение теста
Если мы теперь щелкнем правой кнопкой мыши по классу test в IDE и выберем Run As → JUnit Test, тест должен пройти, и вы увидите, среди прочего, вывод, похожий на этот в консоли:
********************************************************************************
* Message Received in service: https-noclientcert-flow. Content is: / *
********************************************************************************
2014-10-05 22:00:11,845 INFO org.mule.module.logging.DispatchingLogger - Full Message payload:
/
Message properties:
INVOCATION scoped properties:
INBOUND scoped properties:
Connection=true
Host=localhost:8082
Keep-Alive=true
MULE_ORIGINATING_ENDPOINT=endpoint.https.localhost.8082
MULE_REMOTE_CLIENT_ADDRESS=/127.0.0.1:50474
User-Agent=Apache-HttpClient/4.2 (java 1.5)
http.context.path=/
http.context.uri=https://localhost:8082
http.headers={Host=localhost:8082, User-Agent=Apache-HttpClient/4.2 (java 1.5), Keep-Alive=true, test-header-name=test-header-value, Connection=true}
http.method=GET
http.query.params={}
http.query.string=
http.relative.path=
http.request=/
http.request.path=/
http.version=HTTP/1.1
test-header-name=test-header-value
OUTBOUND scoped properties:
LOCAL_CERTIFICATES=[Ljava.security.cert.X509Certificate;@76c5ccf
MULE_ENCODING=UTF-8
SESSION scoped properties:
Response headers: {LOCAL_CERTIFICATES=[Ljava.security.cert.X509Certificate;@76c5ccf, X-MULE_ENCODING=UTF-8, Date=Sun, 05 Oct 2014 22:00:11 +0200, Content-Length=52, Connection=close, Content-Type=tex
t/plain, Server=Mule EE Core Extensions/3.5.0}
Response body: Hello, the time is now 2014-10-05T22:00:11.859+02:00
Из вышесказанного видно, что запрос с HTTP-заголовком «test-header-name» со значением «test-header-value» был получен фиктивным сервером. Мы также можем видеть заголовки ответа и полезную нагрузку ответа, которая содержит строку приветствия того же типа, которую мы видели в браузере при тестировании фиктивного сервера.
Обратите внимание, что мы также видим, что библиотека Apache HttpClient использовалась для отправки запроса на сервер путем поиска HTTP-заголовка «User-Agent» со значением «Apache-HttpClient / 4.2 (java 1.5)».