Статьи

Сервер SSL / TLS REST — клиент с Spring и TomEE

При создании системы разработчики обычно игнорируют аспекты безопасности. Безопасность всегда была чем-то очень важным, о чем нужно беспокоиться, но она вызывает еще большую озабоченность, чем раньше. Только в этом году у нас было несколько случаев, таких как Ошибка Heartbleed или скандал CelebrityGate. Это не имеет ничего общего с постом, а лишь примеры того, что безопасность действительно имеет значение, и мы должны об этом знать.

С ростом популярности REST-сервисов становится понятно, что их необходимо каким-то образом защитить. Пару недель назад мне пришлось интегрировать моего клиента с сервисом REST за https. Я никогда не делал этого раньше, и в этом причина этого поста. Я должен признаться, что я сам не эксперт по безопасности, поэтому, пожалуйста, поправьте меня, если я напишу что-нибудь глупое.

Настройка

Для этого примера я использовал следующую настройку:

Я не буду вдаваться в подробности о SSL и TSL, поэтому, пожалуйста, проверьте здесь дополнительный контент. Обратите внимание, что TLS — это новое имя для эволюции SSL. Иногда между ними возникает путаница, и люди часто говорят SSL, но используют новейшую версию TSL. Запомни.

Не забудьте следовать инструкциям на следующей странице, чтобы настроить SSL для Tomcat: Конфигурация SSL . Это необходимо для того, чтобы сервер предоставил клиенту набор учетных данных, сертификат для защиты соединения между сервером и клиентом.

Код

обслуживание

Давайте создадим простой Spring REST Service:

RestService.java

1
2
3
4
5
6
7
8
9
@Controller
@RequestMapping("/")
public class RestService {
    @RequestMapping(method = RequestMethod.GET)
    @ResponseBody
    public String get() {
        return "Called the get Rest Service";
    }
}

И нам также нужна проводка, чтобы это работало:

RestConfig.java

1
2
3
4
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.radcortez.rest.ssl")
public class RestConfig {}

web.xml

01
02
03
04
05
06
07
08
09
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
<?xml version="1.0" encoding="UTF-8"?>
<web-app
    version="3.1"
 
    <servlet>
        <servlet-name>rest</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
        </init-param>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.radcortez.rest.ssl</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
 
    <servlet-mapping>
        <servlet-name>rest</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
 
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Rest Application</web-resource-name>
            <url-pattern>/*</url-pattern>
        </web-resource-collection>
        <user-data-constraint>
            <!-- Needed for our application to respond to https requests -->
            <transport-guarantee>CONFIDENTIAL</transport-guarantee>
        </user-data-constraint>
    </security-constraint>
</web-app>

Обратите внимание на элементы security-constraint , user-data-constraint и <transport-guarantee>CONFIDENTIAL</transport-guarantee> . Они необходимы, чтобы указать, что приложение требует безопасного соединения. Проверьте безопасность веб-приложений для приложений Java.

Запуск Сервиса

Просто разверните приложение на сервере TomEE, используя вашу любимую среду IDE, и получите доступ к https://localhost:8443/ . Вы должны получить следующее (вам может понадобиться сначала принять сертификат сервера):

остальное-сервис-локальный

Обратите внимание, что протокол браузера — https а порт — 8443 (при условии, что вы сохранили настройки по умолчанию в разделе Конфигурация SSL .

клиент

Теперь, если вы попытаетесь вызвать эту службу REST с помощью клиента Java, скорее всего, вы получите следующее сообщение и исключение (или подобное):

Сообщение: ошибка ввода-вывода при запросе GET для «https: // localhost: 8443 /»: sun.security.validator.ValidatorException:

Исключение: вызвано: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: сбой построения пути PKIX: sun.security.provider.certpath.SunCertPathBuilderException: невозможно найти действительный путь сертификации для запрошенной цели

Это происходит потому, что у запущенного JDK нет действительного сертификата для вашего сервера. Вы можете импортировать его и избавиться от проблемы, но давайте сделаем что-то более интересное. Мы собираемся программно снабдить доверенное хранилище ключей нашим сертификатом сервера.

Это особенно полезно, если:

  • вы запускаете свой код в нескольких средах
  • вам не нужно каждый раз вручную импортировать сертификат в JDK
  • если вы обновляете JDK, вы должны помнить о сертификатах
  • по какой-то странной причине у вас нет доступа к самому JDK для импорта сертификата

Давайте напишем некоторый код:

RestClientConfig.java

01
02
03
04
05
06
07
08
09
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
@Configuration
@PropertySource("classpath:config.properties")
public class RestClientConfig {
    @Bean
    public RestOperations restOperations(ClientHttpRequestFactory clientHttpRequestFactory) throws Exception {
        return new RestTemplate(clientHttpRequestFactory);
    }
 
    @Bean
    public ClientHttpRequestFactory clientHttpRequestFactory(HttpClient httpClient) {
        return new HttpComponentsClientHttpRequestFactory(httpClient);
    }
 
    @Bean
    public HttpClient httpClient(@Value("${keystore.file}") String file,
                                 @Value("${keystore.pass}") String password) throws Exception {
        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        FileInputStream instream = new FileInputStream(new File(file));
        try {
            trustStore.load(instream, password.toCharArray());
        } finally {
            instream.close();
        }
 
        SSLContext sslcontext =
                SSLContexts.custom().loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()).build();
        SSLConnectionSocketFactory sslsf =
                new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1.2"}, null,
                                               BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
        return HttpClients.custom().setSSLSocketFactory(sslsf).build();
    }
 
    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
}

Здесь мы используем интерфейс Spring RestOperations, в котором указан базовый набор операций RESTful. Далее мы используем Apache HTTP Components SSLConnectionSocketFactory, который дает нам возможность проверять подлинность сервера по списку доверенных сертификатов. Сертификат загружается из того же файла, который используется на сервере KeyStore .

RestServiceClientIT.java

01
02
03
04
05
06
07
08
09
10
11
12
13
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = RestClientConfig.class)
public class RestServiceClientIT {
    @Autowired
    private RestOperations rest;
 
    @Test
    public void testRestRequest() throws Exception {
        ResponseEntity response = rest.getForEntity("https://localhost:8443/", String.class);
        System.out.println("response = " + response);
        System.out.println("response.getBody() = " + response.getBody());
    }
}

Простой тестовый класс. Нам также нужен файл свойств с расположением файла хранилища ключей и паролем:

config.properties

1
2
keystore.file=${user.home}/.keystore
keystore.pass=changeit

Это должно работать нормально, если вы использовали все значения по умолчанию.

Выполнение теста

Если вы сейчас запустите тест, который вызывает службу REST в клиенте Java, вы должны получить следующий вывод:

Ответ : <200 OK, вызван get Rest Service, {Server = [Apache-Coyote / 1.1], Cache-Control = [private], Expires = [Чт, 01 января 1970 01:00:00 WET], Content-Type = , Content-Length = [27], Дата = [Вт, 23 декабря 2014 г. 01:29:20 GMT]}>

Тело : Вызывается сервис Get Rest

Вывод

Это оно! Теперь вы можете безопасно позвонить в службу REST вместе с клиентом. Если вы предпочитаете добавить сертификат в хранилище ключей JDK, проверьте этот пост .

Оставайтесь с нами для эквивалента для Java EE JAX-RS эквивалент.

Ресурсы

Вы можете клонировать полную рабочую копию из моего репозитория github: REST SSL .

Ссылка: Сервер SSL / TLS REST — клиент с Spring и TomEE от нашего партнера по JCG Роберто Кортеса в блоге