Перейти к основному содержимому

Аутентификация X.509 в Spring Security

· 12 мин. чтения

Задача: Наибольшая подстрока без повторений

Для заданной строки s, найдите длину наибольшей подстроки без повторяющихся символов. Подстрока — это непрерывная непустая последовательность символов внутри строки...

ANDROMEDA 42

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 — и оставляем остальные части пустыми.

./b7eea07afdfe828fab3515e3b108a0cf.jpg

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 будет выглядеть следующим образом:

  1. Введите about:preferences в адресной строке
  2. Откройте «Дополнительно» -> «Сертификаты» -> «Просмотр сертификатов» -> «Власти».
  3. Нажмите «Импорт ».
  4. Найдите папку учебных пособий ForEach и ее подпапку spring-security-x509/keystore.
  5. Выберите файл rootCA.crt и нажмите OK .
  6. Выберите « Доверять этому ЦС для идентификации веб-сайтов» и нажмите « ОК ».

Примечание. Если вы не хотите добавлять наш центр сертификации в список доверенных центров , позже у вас будет возможность сделать исключение и показать веб-сайт как небезопасный, даже если он упоминается как небезопасный. Но тогда вы увидите в адресной строке желтый восклицательный знак, указывающий на небезопасное соединение!

После этого мы перейдем к модулю spring-security-x509-basic-auth и запустим:

mvn spring-boot:run

Наконец, мы нажимаем https://localhost:8443/user , вводим наши учетные данные пользователя из application.properties и должны увидеть «Hello Admin!» сообщение. Теперь мы можем проверить статус соединения, щелкнув значок «зеленый замок» в адресной строке, и это должно быть защищенное соединение.

./549e7f1375cf77ec9f3f18d8c2f85fdf.png

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:

  1. Введите about:preferences в адресной строке
  2. Откройте «Дополнительно» -> «Просмотр сертификатов» -> «Ваши сертификаты».
  3. Нажмите «Импорт ».
  4. Найдите папку учебных пособий ForEach и ее подпапку spring-security-x509/store.
  5. Выберите файл clientBob.p12 и нажмите OK .
  6. Введите пароль для вашего сертификата и нажмите OK .

Теперь, когда мы обновим наш веб-сайт, нам будет предложено выбрать сертификат клиента, который мы хотели бы использовать:

./e198c0168dd29caf8451b2efba6a6bd2.jpg

Если мы увидим приветственное сообщение типа «Привет, Боб!» , значит все работает как положено!

./c75ac322f9331c84790dce1a34a5dcc2.jpg

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 .