Перевод Spring Boot приложения с HTTP на HTTPS без ругани браузера

Всем привет. Данная статья расчитана на тех, кто хочет сделать так, чтобы их Spring Boot приложение могло работать с HTTPS без предупреждений со стороны браузера о небезопасном подключении. В этой статье работаем именно со Spring Boot и вшитым в него Apache Tomcat.

Внимание! Статья игнорирует всевозможные правила безопасноти Linux и призвана показать как достичь элементарной работы с HTTPS в Spring Boot, не сильно углубляясь в смежные темы.

Рисунок 1 - ругань браузера при переходе на HTTPS с помощью самописных сертификатов

Рисунок 1 — ругань браузера при переходе на HTTPS с помощью самописных сертификатов

От вас требуется:

  1. Spring приложение на Linux сервере (например Ubuntu 20+), на котором у вас есть доступ к root правам

  2. Действующие ендпоинты, например, @GetMapping

  3. Базовое представление о принципах работы HTTPS

  4. Знакомство с системой сборки Maven

Вообще, можно использовать HTTPS в Spring приложении и с помощью self-signed сертефиката, который генерируется с помощью утилиты keytool, вшитой прямо в JDK, но  вызовы ваших ендпоинтов с таким сертификатом будут вызывать предупреждение в браузере, как на рисунке 1.

Чтобы наше приложение могло работать с HTTPS без ругани со стороны браузера нам нужен «сертификат», подписанный CA (Certification Authority).

Ни вы, дорогой читатель, ни я не являемся Certification Authority. Ваш браузер или браузер любого другого пользователя знать не знает, что за кустарные (палёные) сертефикаты мы тут генерируем с помощью keytool! Отсюда и предупреждение о небезопасном соединении.

Чтобы сгенерировать сертификат, который будет валидым и безопасным в глазах всех браузеров нам не обойтись без помощи вышеупомянутого CA. Кто-то может купить сертификат у провайдеров облачных серверов, но у меня нет лишних денег поэтому в этой статье мы воспользуемся сертефикатом от Let’s Encrypt, который будет нам выдан через Certbot’a.

Важно понимать, что Let’s Encrypt — это Certification Authority (nonprofit организация), сертефикаты который являются валидными в глазах браузеров, а выдавать их сертефикат нам будет утилита под названием Certbot.
Взаимосвязанны ли они? Да. Являются ли они одним и тем же? Нет.

Для примера можно привести HTTPS сертефикат Stack Overflow, они тоже используют Let’s Encrypt.

Рисунок 2 - услугами Let's Encrypt пользуются Stack Overflow

Рисунок 2 — услугами Let’s Encrypt пользуются Stack Overflow

Собственно сам туториал

Еще раз напомню, что у вас уже должен быть живой Spring Boot проект, который работает через HTTP и имеет какое-то количество ендпоинтов. Чтобы начать работать с HTTPS нам надо выполнить 3 задачи:

  1. Настроить firewall

  2. Правильно сгенерировать сертификат

  3. Настроить Spring Boot

1. Настройка firewall

Firewall мы настроим через ufw, но это не панацея и вы можете использовать свои инструменты. Установим ufw

sudo apt install ufw

Откроем интересующие нас порты:

sudo ufw allow 80; 
sudo ufw allow 443; 
sudo ufw allow 8080;
sudo ufw allow ssh;

Внимание! следующая команда заблокирует любой входящий трафик. Это значит, что все порты будут закрыты, пока вы их не откроете, например, через sudo ufw allow. Если вы на удаленном сервере обязательно выполните sudo ufw allow ssh, иначе рискуете заблокировать себе доступ к серверу.

sudo ufw enable

Чтобы проверить способность вашего сервера принимать запросы после ufw конфигурации, запустите приложение и попробуйте отправить любой запрос на: http://<ваш_ipv4_адрес>:8080

Наглядный пример URL запроса с портом: http://90.156.210.6:8080

Имея @RestController класс с таким методом:

@RestController
public class DuelController {
  
  @GetMapping     
  public String test(){       
    return "Transition to https";   
  }
  
}

Мы можем достучаться до нашего сервера по порту 8080, как на рисунке 3.

Рисунок 3 - проверяем доступность портов

Рисунок 3 — проверяем доступность портов

Если ваш ендпоинт не отзывается, проверьте открыт ли порт, по которому вы обращаетесь к серверу с помощью sudo ufw status

Мы хотим увидеть следующий вывод:

habr@personal_computer:~/demo$ sudo ufw status 
Status: active 
To                         Action      From
22/tcp                     ALLOW       Anywhere 
443                        ALLOW       Anywhere 
80                         ALLOW       Anywhere 
8080                       ALLOW       Anywhere 
22/tcp (v6)                ALLOW       Anywhere (v6) 
443 (v6)                   ALLOW       Anywhere (v6) 
80 (v6)                    ALLOW       Anywhere (v6)
8080 (v6)                  ALLOW       Anywhere (v6) 

2. Правильная генерация сертификата

Подключитесь к своему Linux серверу и в терминале начните с проверки наличия на сервере snapd:

sudo systemctl status snapd

Если вы видите вывод как на рисунке 4 (active (running)...), то все в порядке и можно двигаться дальше. Если нет, то вам надо установить snap или перезапустить его daemon.

Рисунок 4 - вывод, который говорит, что daemon snapd в данный момент работает

Рисунок 4 — вывод, который говорит, что daemon snapd в данный момент работает

Установим саму утилиту Certbot:

sudo snap install --classic certbot

Теперь создадим symbolic link, так нас просит сделать инструкция Certbot’a:

sudo ln -s /snap/bin/certbot /usr/bin/certbot

Теперь убедитесь, что ваше Spring приложение остановлено. Следующая команда интерактивная и требует заранее подготовленной информации, а конкретно:

  1. Email. Какой хотите, но в инструкции сказано, что использоваться он будет для оповещений по вопросам безопастности и срочного продлевания сертификата

    Enter email address (used for urgent renewal and security notices)

  2. Валидное доменное имя, привязанное к вашему IPv4 адрессу. Вам надо купить (или каким-нибудь способом достать) доменное имя, например, vashdomen.ru и привязать его к вашему серверу.
    Без привязанного к вашему серверу домена вы не сгенерируете сертификат. Certbot выдаст вам ошибку.

    Вы можете купить самый простецкий домен за «сотку» у рег.ру (не рефералка) и по их инструкции привязать IP адресс вашего сервера к их DNS серверам. Если вы укажите другие DNS сервера, то и настройку надо будет делать в другом месте. Возможно на сайте владельцев этих DNS серверов.

    Процесс привязки домена может занять некоторое время.

    Чтобы протестировать привязался ли домен к адресу, попробуйте выполнить следующую команду: ping vashdomen.ru
    Возможно ping отзовется не так быстро, как обычно, стоит чутка подождать.

С Email и рабочим доменным именем начинаем генерацию сертификата. Вас попросят согласится с парой пунктов, указать почту и доменное имя, которое вы привязали к серверу.

sudo certbot certonly --standalone

Если все хорошо, то вас встретит сообщение об успешной генерации файлов:

...
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/<доменное_имя>/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/<доменное_имя>/privkey.pem
This certificate expires on 2024-07-30.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.
...

Самая важное тут — это пути к вашим новым файлам:

/etc/letsencrypt/live/<доменное_имя>/

Прошу обратить внимание, что хоть нам и выводится путь лишь к двум файлам по факту их сгенерировалось пять (5). Чтобы посмотреть содержимое этой директории нам нужны root права:

sudo su

Заглянем в директорию:

 ls /etc/letsencrypt/live/<доменное_имя>

Вам должен выпасть вот такой список:

cert.pem  chain.pem  fullchain.pem  privkey.pem  README

Вы сгенерировали сертификат, подписанный CA Let’s Encrypt. Этот сертификат принимается браузерами за валидный и на него не падают предупреждения! Вы можете применить его и за пределами Spring приложения.

3. Настройка Spring Boot

Теперь надо познакомить ваше приложение с сертификатом. Для этого нам нужна команда openssl. Если ее нет, то установите:

 sudo apt install openssl

Перед тем как двигаться дальше вам надо перейти в директорию вашего Spring приложения. Так чтобы при выводе ls вы видели ваши src, target, pom.xml...

habr@personal_computer:~/demo$ ls
HELP.md  mvnw  mvnw.cmd  pom.xml  src  target

Теперь не торопитесь и внимательно прочитайте следующую команду. Укажите валидные пути к сгенерированным файлам. Эта команда тоже интерактивная и вы будете создавать пароль к хранилищу. Запомните его! В рамках этого туториала можете использовать пароль «test_tomcat».

sudo openssl pkcs12 -export -chain -inkey /etc/letsencrypt/live/<доменное_имя>/privkey.pem -CAfile /etc/letsencrypt/live/<доменное_имя>/fullchain.pem -in /etc/letsencrypt/live/<доменное_имя>/cert.pem -out keystore.p12 -name tomcat 

Теперь, когда у вас на руках есть пароль и хранилище приватных ключей, которое лежит в корневой директории, рядом с pom.xml, в самом Spring надо отредактировать application.properties

Вставьте туда следующие строчки:

server.port = 443 
server.ssl.key-store = keystore.p12 
server.ssl.key-store-password = test_tomcat 
server.ssl.keyStoreType = PKCS12
server.ssl.keyAlias = tomcat

Объясняю:

  1. server.port — порт, на котором будет работать ваше приложение. HTTPS работает через порт 443

  2. server.ssl.key-store — название хранилища, которое мы сгенерировали (keystore.p12)

  3. server.ssl.key-store-password — пароль, который вы вводили при генерации хранилища и который надо было запонмить

  4. server.ssl.keyAlias — это флаг (-name tomcat), который мы указали в команде при генерации хранилища

Финальное тестирование

Проверим нужные порты:

sudo ufw status

Видим вывод:

habr@personal_computer:~/demo$ sudo ufw status 
Status: active 
To                         Action      From
22/tcp                     ALLOW       Anywhere 
443                        ALLOW       Anywhere 
80                         ALLOW       Anywhere 
8080                       ALLOW       Anywhere 
22/tcp (v6)                ALLOW       Anywhere (v6) 
443 (v6)                   ALLOW       Anywhere (v6) 
80 (v6)                    ALLOW       Anywhere (v6)
8080 (v6)                  ALLOW       Anywhere (v6) 

Порты подготовлены.

Порт 22 — это ssh соединение, а (v6) является обозначением для IPv6.

Итак, мы:

  1. Порты настроили

  2. Сертификат сгенерировали

  3. Spring Boot приложение с сертификатом познакомили

Пора запускать.

Перед тем как запускать приложение надо понимать, что первые 1024 порта зарезервированы и для запуска приложений, которые общаются с этими портами нам нужны root права. Наше приложение будет работать на порте 443, а значит запуск Spring Boot требует повышенных прав.

Укажите полный путь до бинарного файла Maven, чтобы терминал распознал команду через sudo:
Или можете отредактировать PATH для root пользователя.

sudo /home/habr/apache-maven-3.9.6/bin/mvn spring-boot:run

В информационных логах Spring нас интересует 3 лога.

Первый лог говорит нам о том, что Tomcat запустился на порте 443

INFO 19474 --- [demo] [main] o.s.b.w.embedded.tomcat.TomcatWebServer:
Tomcat initialized with port 443 (https)

Второй лог выводит нам информацию о хранилище приватных ключей

INFO 19474 --- [demo] [main] o.a.t.util.net.NioEndpoint.certificate: 
Connector [https-jsse-nio-443], TLS virtual host [default], certificate type [UNDEFINED] configured from keystore [/root/.keystore] using alias [tomcat] with trust store [null]

Третий лог дополняет первый и показывает нам базовый context path. По дефолту »/»

 INFO 19474 --- [demo] [main] o.s.b.w.embedded.tomcat.TomcatWebServer: 
Tomcat started on port 443 (https) with context path ''

Теперь можем смело обращаться к нашему ендпоинту. Прошу заметить, что при вводе доменного имени в адресной строке не нужно указывать порт, как мы это делали при проверке работы с firewall’ом.

Рисунок 5 - наш сертефикат verified by: Let's Encrypt

Рисунок 5 — наш сертефикат verified by: Let’s Encrypt

Если все прошло успешно, вы можете обращаться к ендпоинтам вашего Spring Boot приложения через протокол HTTPS без ругани со стороны бразуера.

© Habrahabr.ru