1. Обзор
В этой статье мы сосредоточимся на основных случаях использования аутентификации сертификата X.509 — проверке подлинности узла связи при использовании протокола HTTPS (HTTP через SSL).
Проще говоря — пока устанавливается безопасное соединение, клиент проверяет сервер по своему сертификату (выданному доверенным центром сертификации).
Но помимо этого, X.509 в Spring Security можно использовать для проверки личности клиента сервером при подключении. Это называется «взаимной аутентификацией»,
и мы также рассмотрим, как это делается здесь.
Наконец, мы коснемся того, когда имеет смысл использовать этот вид аутентификации .
Чтобы продемонстрировать проверку сервера, мы создадим простое веб-приложение и установим пользовательский центр сертификации в браузере.
Кроме того, для взаимной аутентификации
мы создадим сертификат клиента и изменим наш сервер, чтобы разрешить только проверенных клиентов.
Настоятельно рекомендуется следовать этому руководству шаг за шагом и создавать сертификаты, а также хранилище ключей и хранилище доверенных сертификатов самостоятельно в соответствии с инструкциями, представленными в следующих разделах. Однако все готовые к использованию файлы можно найти в нашем репозитории на GitHub .
2. Самоподписанный корневой ЦС
Чтобы иметь возможность подписывать наши сертификаты на стороне сервера и на стороне клиента, нам нужно сначала создать собственный самозаверяющий сертификат корневого ЦС. Таким образом, мы будем действовать как собственный центр сертификации .
Для этой цели мы будем использовать библиотеку openssl , поэтому нам нужно установить ее перед следующим шагом.
Давайте теперь создадим сертификат CA:
openssl req -x509 -sha256 -days 3650 -newkey rsa:4096 -keyout rootCA.key -out rootCA.crt
Когда мы выполняем указанную выше команду, нам нужно указать пароль для нашего закрытого ключа. Для целей этого руководства мы используем changeit
в качестве парольной фразы.
Кроме того, нам нужно ввести информацию, которая образует так называемое отличительное имя . Здесь мы указываем только CN (общее имя) — ForEach.com — и оставляем остальные части пустыми.
3. Хранилище ключей
Необязательное требование : для использования криптографически стойких ключей вместе с функциями шифрования и дешифрования нам потребуются « Файлы политик юрисдикции с неограниченной силой Java Cryptography Extension (JCE)
», установленные на нашей JVM .
Их можно загрузить, например, с Oracle (следуйте инструкциям по установке, включенным в загрузку). Некоторые дистрибутивы Linux также предоставляют устанавливаемый пакет через свои менеджеры пакетов.
Хранилище ключей — это репозиторий, который наше приложение Spring Boot будет использовать для хранения закрытого ключа и сертификата нашего сервера. Другими словами, наше приложение будет использовать хранилище ключей для предоставления сертификата клиентам во время рукопожатия SSL.
В этом руководстве мы используем формат Java Key-Store (JKS) и инструмент командной строки keytool .
3.1. Серверный сертификат
Чтобы реализовать аутентификацию X.509 на стороне сервера в нашем приложении Spring Boot, нам **сначала нужно создать сертификат на стороне сервера.
**
Начнем с создания так называемого запроса на подпись сертификата (CSR):
openssl req -new -newkey rsa:4096 -keyout localhost.key –out localhost.csr
Точно так же, как и для сертификата CA, мы должны указать пароль для закрытого ключа. Кроме того, давайте использовать localhost
в качестве общего имени (CN).
Прежде чем мы продолжим, нам нужно создать файл конфигурации — localhost.ext
. В нем будут храниться некоторые дополнительные параметры, необходимые при подписании сертификата.
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost
Готовый к использованию файл также доступен здесь .
Теперь пришло время подписать запрос нашим сертификатом rootCA.crt
и его закрытым ключом :
openssl x509 -req -CA rootCA.crt -CAkey rootCA.key -in localhost.csr -out localhost.crt -days 365 -CAcreateserial -extfile localhost.ext
Обратите внимание, что мы должны указать тот же пароль, который мы использовали при создании нашего сертификата CA.
На этом этапе у нас наконец есть готовый к использованию сертификат localhost.crt
, подписанный нашим собственным центром сертификации.
Чтобы распечатать детали нашего сертификата в удобочитаемой форме, мы можем использовать следующую команду:
openssl x509 -in localhost.crt -text
3.2. Импорт в хранилище ключей
В этом разделе мы увидим, как импортировать подписанный сертификат и соответствующий закрытый ключ в файл keystore.jks
.
Мы будем использовать архив PKCS 12 , чтобы упаковать закрытый ключ нашего сервера вместе с подписанным сертификатом. Затем мы импортируем его во вновь созданный keystore.jks.
Мы можем использовать следующую команду для создания файла .p12
:
openssl pkcs12 -export -out localhost.p12 -name "localhost" -inkey localhost.key -in localhost.crt
Итак, теперь у нас есть localhost.key
и localhost.crt
, объединенные в один файл localhost.p12 .
Давайте теперь воспользуемся keytool для создания репозитория keystore.jks
и импортируем файл localhost.p12
с помощью одной команды :
keytool -importkeystore -srckeystore localhost.p12 -srcstoretype PKCS12 -destkeystore keystore.jks -deststoretype JKS
На данном этапе у нас есть все необходимое для части аутентификации сервера. Давайте приступим к настройке нашего приложения Spring Boot.
4. Пример приложения
Наш проект защищенного сервера SSL состоит из аннотированного класса приложения @SpringBootApplication
(который является разновидностью @Configuration )
, файла конфигурации application.properties
и очень простого внешнего интерфейса в стиле MVC.
Все, что должно сделать приложение, — это представить HTML-страницу с сообщением «Hello {User}!»
сообщение. Таким образом, мы можем проверить сертификат сервера в браузере, чтобы убедиться, что соединение проверено и защищено.
4.1. Зависимости Maven
Во-первых, мы создаем новый проект Maven с тремя включенными пакетами Spring Boot Starter:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
Для справки: мы можем найти пакеты на Maven Central ( security , web , thymeleaf ).
4.2. Весеннее загрузочное приложение
На следующем этапе мы создаем основной класс приложения и пользовательский контроллер:
@SpringBootApplication
public class X509AuthenticationServer {
public static void main(String[] args) {
SpringApplication.run(X509AuthenticationServer.class, args);
}
}
@Controller
public class UserController {
@RequestMapping(value = "/user")
public String user(Model model, Principal principal) {
UserDetails currentUser
= (UserDetails) ((Authentication) principal).getPrincipal();
model.addAttribute("username", currentUser.getUsername());
return "user";
}
}
Теперь мы сообщаем приложению, где найти наш keystore.jks
и как получить к нему доступ. Мы устанавливаем SSL в статус «включено» и меняем стандартный порт прослушивания, чтобы указать защищенное соединение.
Кроме того, мы настраиваем некоторые данные пользователя
для доступа к нашему серверу через обычную аутентификацию:
server.ssl.key-store=../store/keystore.jks
server.ssl.key-store-password=${PASSWORD}
server.ssl.key-alias=localhost
server.ssl.key-password=${PASSWORD}
server.ssl.enabled=true
server.port=8443
spring.security.user.name=Admin
spring.security.user.password=admin
Это будет HTML-шаблон, расположенный в папке resources/templates :
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>X.509 Authentication Demo</title>
</head>
<body>
<h2>Hello <span th:text="${username}"/>!</h2>
</body>
</html>
4.3. Установка корневого ЦС
Прежде чем мы закончим этот раздел и посмотрим на сайт, нам нужно установить наш сгенерированный корневой центр сертификации в качестве доверенного сертификата в браузере .
Примерная установка нашего центра сертификации для Mozilla Firefox
будет выглядеть следующим образом:
- Введите
about:preferences
в адресной строке - Откройте
«Дополнительно» -> «Сертификаты» -> «Просмотр сертификатов» -> «Власти».
- Нажмите
«Импорт ».
- Найдите
папку учебных пособий ForEach
и ее подпапкуspring-security-x509/keystore.
- Выберите файл
rootCA.crt
и нажмитеOK .
- Выберите «
Доверять этому ЦС для идентификации веб-сайтов»
и нажмите «ОК ».
Примечание. Если вы не хотите добавлять наш центр сертификации
в список доверенных центров
, позже у вас будет возможность сделать исключение
и показать веб-сайт как небезопасный, даже если он упоминается как небезопасный. Но тогда вы увидите в адресной строке желтый восклицательный знак, указывающий на небезопасное соединение!
После этого мы перейдем к модулю spring-security-x509-basic-auth
и запустим:
mvn spring-boot:run
Наконец, мы нажимаем https://localhost:8443/user
, вводим наши учетные данные пользователя из application.properties
и должны увидеть «Hello Admin!»
сообщение. Теперь мы можем проверить статус соединения, щелкнув значок «зеленый замок» в адресной строке, и это должно быть защищенное соединение.
5. Взаимная аутентификация
В предыдущем разделе мы представили, как реализовать наиболее распространенную схему аутентификации SSL — аутентификацию на стороне сервера. Это означает, что только сервер аутентифицирует себя для клиентов.
В этом разделе мы опишем, как добавить другую часть аутентификации — аутентификацию на стороне клиента . Таким образом, только клиенты с действительными сертификатами, подписанными уполномоченным органом, которому доверяет наш сервер, могут получить доступ к нашему защищенному веб-сайту.
Но прежде чем мы продолжим, давайте посмотрим, каковы плюсы и минусы использования взаимной аутентификации SSL.
Плюсы:
- Закрытый ключ сертификата клиента X.509 надежнее любого пользовательского пароля . Но это нужно держать в секрете!
- Благодаря сертификату личность клиента хорошо известна и ее легко проверить .
- Больше никаких забытых паролей!
Минусы:
- Нам нужно создать сертификат для каждого нового клиента.
- Сертификат клиента должен быть установлен в клиентском приложении. На самом деле: аутентификация клиента X.509 зависит от устройства , что делает невозможным использование этого вида аутентификации в общественных местах, например, в интернет-кафе.
- Должен существовать механизм отзыва скомпрометированных клиентских сертификатов.
- Мы должны поддерживать сертификаты клиентов. Это может легко стать дорогостоящим.
5.1. доверенный магазин
Trustsore в некотором роде является противоположностью хранилища ключей. Он содержит сертификаты внешних объектов, которым мы доверяем .
В нашем случае достаточно оставить корневой сертификат CA в доверенном хранилище.
Давайте посмотрим, как создать файл truststore.jks
и импортировать rootCA.crt
с помощью keytool:
keytool -import -trustcacerts -noprompt -alias ca -ext san=dns:localhost,ip:127.0.0.1 -file rootCA.crt -keystore truststore.jks
Обратите внимание, что нам нужно указать пароль для вновь созданного trusstore.jks
. Здесь мы снова использовали кодовую фразу changeit
.
Вот и все, мы импортировали собственный сертификат ЦС, и хранилище доверенных сертификатов готово к использованию.
5.2. Конфигурация безопасности Spring
Чтобы продолжить, мы модифицируем наш X509AuthenticationServer
, чтобы он расширялся от WebSecurityConfigurerAdapter
и переопределял один из предоставленных методов настройки. Здесь мы настраиваем механизм x.509 для анализа поля Common Name (CN)
сертификата для извлечения имен пользователей.
С помощью этих извлеченных имен пользователей Spring Security ищет в предоставленной службе UserDetailsService
соответствующих пользователей. Поэтому мы также реализуем этот сервисный интерфейс, содержащий одного демонстрационного пользователя.
Совет: В рабочих средах этот UserDetailsService
может загружать своих пользователей, например, из источника данных JDBC .
Вы должны заметить, что мы аннотируем наш класс @EnableWebSecurity
и @EnableGlobalMethodSecurity
с включенной пре-/пост-авторизацией.
С последним мы можем аннотировать наши ресурсы с помощью @PreAuthorize
и @PostAuthorize
для детального контроля доступа:
@SpringBootApplication
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class X509AuthenticationServer extends WebSecurityConfigurerAdapter {
...
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated()
.and()
.x509()
.subjectPrincipalRegex("CN=(.*?)(?:,|$)")
.userDetailsService(userDetailsService());
}
@Bean
public UserDetailsService userDetailsService() {
return new UserDetailsService() {
@Override
public UserDetails loadUserByUsername(String username) {
if (username.equals("Bob")) {
return new User(username, "",
AuthorityUtils
.commaSeparatedStringToAuthorityList("ROLE_USER"));
}
throw new UsernameNotFoundException("User not found!");
}
};
}
}
Как было сказано ранее, теперь мы можем использовать управление доступом на основе выражений
в нашем контроллере. В частности, наши аннотации авторизации соблюдаются из-за аннотации @EnableGlobalMethodSecurity
в нашей @Configuration
: ``
@Controller
public class UserController {
@PreAuthorize("hasAuthority('ROLE_USER')")
@RequestMapping(value = "/user")
public String user(Model model, Principal principal) {
...
}
}
Обзор всех возможных вариантов авторизации можно найти в официальной документации .
В качестве последнего шага изменения мы должны сообщить приложению, где находится наше хранилище доверенных сертификатов
и что необходима аутентификация клиента SSL
( server.ssl.client-auth=need
).
Итак, мы помещаем в наш application.properties
следующее :
server.ssl.trust-store=store/truststore.jks
server.ssl.trust-store-password=${PASSWORD}
server.ssl.client-auth=need
Теперь, если мы запустим приложение и укажем в браузере https://localhost:8443/user
, нам сообщат, что одноранговая сеть не может быть проверена и отказывается открывать наш веб-сайт.
5.3. Клиентский сертификат
Теперь пришло время создать клиентский сертификат. Шаги, которые нам нужно предпринять, почти такие же, как и для уже созданного нами сертификата на стороне сервера.
Во-первых, мы должны создать запрос на подпись сертификата:
openssl req -new -newkey rsa:4096 -nodes -keyout clientBob.key -out clientBob.csr
Нам нужно будет предоставить информацию, которая будет включена в сертификат. Для этого упражнения давайте введем только общее имя (CN) — Bob . Это важно, так как мы используем эту запись во время авторизации, а наше тестовое приложение распознает только Боба.
Далее нам нужно подписать запрос нашим CA:
openssl x509 -req -CA rootCA.crt -CAkey rootCA.key -in clientBob.csr -out clientBob.crt -days 365 -CAcreateserial
Последний шаг, который нам нужно сделать, это упаковать подписанный сертификат и закрытый ключ в файл PKCS:
openssl pkcs12 -export -out clientBob.p12 -name "clientBob" -inkey clientBob.key -in clientBob.crt
Наконец, мы готовы установить клиентский сертификат в браузере .
Опять же, мы будем использовать Firefox:
- Введите
about:preferences
в адресной строке - Откройте
«Дополнительно» -> «Просмотр сертификатов» -> «Ваши сертификаты».
- Нажмите
«Импорт ».
- Найдите
папку учебных пособий ForEach
и ее подпапкуspring-security-x509/store.
- Выберите файл
clientBob.p12
и нажмитеOK .
- Введите пароль для вашего сертификата и нажмите
OK .
Теперь, когда мы обновим наш веб-сайт, нам будет предложено выбрать сертификат клиента, который мы хотели бы использовать:
Если мы увидим приветственное сообщение типа «Привет, Боб!»
, значит все работает как положено!
6. Взаимная аутентификация с помощью XML
Также возможно добавление аутентификации клиента X.509 в конфигурацию безопасности http в
XML
:
<http>
...
<x509 subject-principal-regex="CN=(.*?)(?:,|$)"
user-service-ref="userService"/>
<authentication-manager>
<authentication-provider>
<user-service id="userService">
<user name="Bob" password="" authorities="ROLE_USER"/>
</user-service>
</authentication-provider>
</authentication-manager>
...
</http>
Чтобы настроить базовый Tomcat, мы должны поместить наше хранилище ключей
и наше хранилище доверенных сертификатов
в его папку conf
и отредактировать server.xml
:
<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true" scheme="https" secure="true"
clientAuth="true" sslProtocol="TLS"
keystoreFile="${catalina.home}/conf/keystore.jks"
keystoreType="JKS" keystorePass="changeit"
truststoreFile="${catalina.home}/conf/truststore.jks"
truststoreType="JKS" truststorePass="changeit"
/>
Совет: Если для clientAuth
задано значение «want»
, SSL
по-прежнему включен, даже если клиент не предоставляет действительный сертификат. Но в этом случае мы должны использовать второй механизм аутентификации, например, логин-форму, для доступа к защищенным ресурсам.
7. Заключение
Таким образом, мы узнали, как создать самозаверяющий сертификат ЦС и как использовать его для подписи других сертификатов .
Кроме того, мы создали как серверные, так и клиентские сертификаты. Затем мы представили, как импортировать их в хранилище ключей и хранилище доверенных сертификатов соответственно.
Более того, теперь вы сможете упаковать сертификат вместе с его закрытым ключом в формат PKCS12 .
Мы также обсудили, когда имеет смысл использовать аутентификацию клиента Spring Security X.509, поэтому вам решать, внедрять ее в свое веб-приложение или нет.
И в завершение найдите исходный код этой статьи на Github .