Всем привет. Данная статья расчитана на тех, кто хочет сделать так, чтобы их Spring Boot приложение могло работать с HTTPS без предупреждений со стороны браузера о небезопасном подключении. В этой статье работаем именно со Spring Boot и вшитым в него Apache Tomcat.
Внимание! Статья игнорирует всевозможные правила безопасноти Linux и призвана показать как достичь элементарной работы с HTTPS в Spring Boot, не сильно углубляясь в смежные темы.
От вас требуется:
Spring приложение на Linux сервере (например Ubuntu 20+), на котором у вас есть доступ к root правам
Действующие ендпоинты, например,
@GetMapping
Базовое представление о принципах работы HTTPS
Знакомство с системой сборки 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.
Собственно сам туториал
Еще раз напомню, что у вас уже должен быть живой Spring Boot проект, который работает через HTTP и имеет какое-то количество ендпоинтов. Чтобы начать работать с HTTPS нам надо выполнить 3 задачи:
Настроить firewall
Правильно сгенерировать сертификат
Настроить 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.
Если ваш ендпоинт не отзывается, проверьте открыт ли порт, по которому вы обращаетесь к серверу с помощью 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.
Установим саму утилиту Certbot:
sudo snap install --classic certbot
Теперь создадим symbolic link, так нас просит сделать инструкция Certbot'a:
sudo ln -s /snap/bin/certbot /usr/bin/certbot
Теперь убедитесь, что ваше Spring приложение остановлено. Следующая команда интерактивная и требует заранее подготовленной информации, а конкретно:
-
Email. Какой хотите, но в инструкции сказано, что использоваться он будет для оповещений по вопросам безопастности и срочного продлевания сертификата
Enter email address (used for urgent renewal and security notices)
Валидное доменное имя, привязанное к вашему 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
Объясняю:
server.port
- порт, на котором будет работать ваше приложение. HTTPS работает через порт 443server.ssl.key-store
- название хранилища, которое мы сгенерировали (keystore.p12
)server.ssl.key-store-password
- пароль, который вы вводили при генерации хранилища и который надо было запонмить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.
Итак, мы:
Порты настроили
Сертификат сгенерировали
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'ом.
Если все прошло успешно, вы можете обращаться к ендпоинтам вашего Spring Boot приложения через протокол HTTPS без ругани со стороны бразуера.
Комментарии (20)
iliabvf
04.05.2024 08:27Самый просто способ для меня был Cloudflare, бэкенд на субдомен с https, фронт на домен с https. Бесплатно, красиво имеем защиту, кэш фильтры.
Можно конечно и все selfhosted.Boilerplate
04.05.2024 08:27+1Только в РФ ip-адреса CF периодически блочат, к сожалению. Из-за этого от CF приходится отказываться в случаях, когда это всякие магазины и прочие сервисы на неограниченное число людей.
volatilization
04.05.2024 08:27+1Зачем? Пусть прокси сервер слушает https с настроенными сертами. А спирнг в котейнере крутиться.
mavir
04.05.2024 08:27+4Без привязанного к вашему серверу домена вы не сгенерируете сертификат.
Это не обязательно, есть режим генерации сертификата с использованием DNS. Всё что нужно, это иметь возможность управлять DNS записями вручную или с помощью API, если DNS хостинг предоставляет такую возможность. Для генерации сертификата через DNS даже не надо привязывать домен к IP. И еще большой плюс такой генерации - можно создать wildcard сертификат, например, для *.vashdomen.ru
GennPen
04.05.2024 08:27можно создать wildcard сертификат, например, для *.vashdomen.ru
Который скормить например haproxy, через который и кидать на нужный бэкенд в зависимости от запрашиваемого домена. И это более правильное решение.
А вот wildcard на стороне запросов DNS (чтобы не прописывать адреса для всех поддоменов) не все серверы поддерживают.
mavir
04.05.2024 08:27+2А вот wildcard на стороне запросов DNS (чтобы не прописывать адреса для всех поддоменов) не все серверы поддерживают.
Вот если честно, то это должен быть совсем дешманский DNS хостинг, чтобы не мог поддерживать wildcard записи
mavir
04.05.2024 08:27А вот wildcard на стороне запросов DNS (чтобы не прописывать адреса для всех поддоменов) не все серверы поддерживают.
Вообще я затупил. Причем тут wildcard DNS записи к генерации wildcard сертификата? При генерации сертификата через DNS нужно добавить у провайдера TXT запись, которую попросит certbot. А A-записей может вообще не быть у провайдера, их хоть в /etc/hosts добавляй вручную
GennPen
04.05.2024 08:27Причем тут wildcard DNS записи к генерации wildcard сертификата?
Ну, то что не нужно на DNS создавать записи типа site1.domain site2.domain site3.domain указывающие на один и тот же IP, а можно создать *.domain и при запросе любого поддомена будет указывать на один и тот же IP.
mavir
04.05.2024 08:27Для генерации wildcard сертификата не нужно создавать A-записи вида *.example.com, либо любую другую для отдельных доменов. certbot просит создать TXT и вставить содержимое, которое он даст. Например,
# certbot certonly --manual --preferred-challenges dns -d *.nekij-domen.com Saving debug log to /var/log/letsencrypt/letsencrypt.log Requesting a certificate for *.nekij-domen.com - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Please deploy a DNS TXT record under the name: _acme-challenge.nekij-domen.com. with the following value: 5pt0CXrH_icMfYXLae-jmGZV6SxZohQVWrrZ3pyYKyk
Обратите внимание, что домен на текущий момент даже не зарегистрирован, но certbot предлагает создать TXT запись _acme-challenge.nekij-domen.com с содержимым 5pt0CXrH_icMfYXLae-jmGZV6SxZohQVWrrZ3pyYKyk
Т.е. можно сгенерить wildcard сертификат, а домены создавать одиночные, не обязательно создавать wildcard DNS запись. Например, site1.example.com на одном сервере размещается, site2.example.com на другом с соответствующими A-записями, а сертификат можно один и тот же забросить на эти два сервере
tuxi
04.05.2024 08:27Ходить на бекенд по хттпс это все же извращение. Хттпс это трата допресурсов, на хайлоаде это стрелять себе дуплетом в обе ноги сразу. Бекенд должен работать внутри периметра, а там хттпс как бы не особо нужен.
Все истории про бекенд через хттпс которые я слышал, были связаны с вынужденным (внезапным) переносом бекенда куда то в другой ДЦ и это скорее исключение, чем норма.
olku
04.05.2024 08:27+1Сразу три антипаттерна. Терминирование TLS (Level 6) сервисом (Level 7), отсутствие автообновления сертификата, выставление сервиса в публичный Интернет без базовой защиты от атак.
ky0
Имхо, такой подход неверен. Для локальной разработки лучше выпустить самоподписанный сертификат и добавить его в исключения браузера. Следующий этап - не выставление Томката в интернет, а добавление к приложению веб-сервера, который и будет заниматься терминированием шифрования, помимо прочего.
Kirillchug
Ну тут не только для локальной разработки. Тут вы как минимум уже покупаете домен, что уже может давать путь в открытый интернет и выхода в прод для своего проекта. А HTTPS браузерами "поощряется", и это отличный SEO подход для выхода в топ-10
GennPen
Не только "поощряется", но и принуждает из-за HSTS например, или просто на сервере настроено правило.