1. Обзор
В этом руководстве мы рассмотрим основы безопасности на платформе Java. Мы также сосредоточимся на том, что нам доступно для написания безопасных приложений.
Безопасность — обширная тема, охватывающая множество областей . Некоторые из них являются частью самого языка, например, модификаторы доступа и загрузчики классов. Кроме того, другие доступны в качестве услуг, которые включают шифрование данных, безопасную связь, аутентификацию и авторизацию, и это лишь некоторые из них.
Поэтому нецелесообразно получать осмысленное представление обо всем этом в этом руководстве. Однако постараемся набрать хотя бы осмысленный словарный запас.
2. Особенности языка
Прежде всего, безопасность в Java начинается прямо на уровне языковых функций . Это позволяет нам писать безопасный код, а также пользоваться многими неявными функциями безопасности:
- Статическая типизация данных: Java — это язык со статической типизацией, что снижает возможности обнаружения во время выполнения ошибок, связанных с типами.
- Модификаторы доступа: Java позволяет нам использовать различные модификаторы доступа, такие как общедоступные и частные, для управления доступом к полям, методам и классам .
- Автоматическое управление памятью: Java имеет управление памятью на основе сборки мусора , что освобождает разработчиков от управления этим вручную .
- Проверка байт-кода: Java — это скомпилированный язык, что означает, что он преобразует код в независимый от платформы байт-код, а среда выполнения проверяет каждый байт-код, загружаемый для выполнения .
Это не полный список функций безопасности, которые предоставляет Java, но этого достаточно, чтобы дать нам некоторую уверенность!
3. Архитектура безопасности в Java
Прежде чем мы начнем исследовать конкретные области, давайте потратим некоторое время на понимание базовой архитектуры безопасности в Java.
Основные принципы безопасности в Java основаны на интероперабельных и расширяемых реализациях Provider
. Конкретная реализация Provider
может реализовывать некоторые или все службы безопасности.
Например, некоторые из типичных услуг, которые может реализовать поставщик
:
- Криптографические алгоритмы (например, DSA, RSA или SHA-256)
- Средства генерации, преобразования и управления ключами (например, для ключей, специфичных для алгоритма)
Java поставляется со многими встроенными поставщиками . Кроме того, приложение может настроить несколько поставщиков в порядке предпочтения.
Следовательно, структура провайдера в Java ищет конкретную реализацию службы во всех провайдерах в порядке установленных для них предпочтений.
Кроме того, в этой архитектуре всегда можно реализовать пользовательских поставщиков с подключаемыми функциями безопасности.
4. Криптография
Криптография является краеугольным камнем функций безопасности в целом и в Java. Это относится к инструментам и методам для безопасной связи в присутствии злоумышленников .
4.1. Криптография Java
Криптографическая архитектура Java ( JCA) обеспечивает основу для доступа и реализации криптографических функций в Java, включая:
- Цифровые подписи
- Дайджесты сообщений
- Симметричные и асимметричные шифры
- Коды аутентификации сообщений
- Генераторы ключей и фабрики ключей
Самое главное, Java использует реализации на основе Provider
для криптографических функций.
Более того, Java включает в себя встроенных поставщиков для часто используемых криптографических алгоритмов, таких как RSA, DSA и AES, и это лишь некоторые из них. Мы можем использовать эти алгоритмы для повышения безопасности данных в состоянии покоя, при использовании или в движении.
4.2. Криптография на практике
Очень частым вариантом использования в приложениях является хранение паролей пользователей. Мы используем это для аутентификации в более поздний момент времени. Теперь очевидно, что хранение простых текстовых паролей ставит под угрозу безопасность.
Таким образом, одно из решений состоит в том, чтобы зашифровать пароли таким образом, чтобы процесс был повторяемым, но односторонним. Этот процесс известен как криптографическая хэш-функция, и SHA1 является одним из таких популярных алгоритмов.
Итак, давайте посмотрим, как мы можем сделать это на Java:
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] hashedPassword = md.digest("password".getBytes());
Здесь MessageDigest
— интересующая нас криптографическая служба. Мы используем метод getInstance
() для запроса этой службы у любого из доступных поставщиков безопасности .
5. Инфраструктура открытых ключей
Инфраструктура открытых ключей (PKI) относится к настройке, которая обеспечивает безопасный обмен информацией по сети с использованием шифрования с открытым ключом . Эта установка основана на доверии, которое создается между сторонами, участвующими в общении. Это доверие основано на цифровых сертификатах, выданных нейтральным и доверенным органом, известным как центр сертификации (ЦС).
5.1. Поддержка PKI в Java
Платформа Java имеет API для облегчения создания, хранения и проверки цифровых сертификатов :
KeyStore
: Java предоставляеткласс KeyStore
для постоянного хранения криптографических ключей и доверенных сертификатов. ЗдесьKeyStore
может представлять как файлы хранилища ключей, так и файлы хранилища доверия . Эти файлы имеют схожее содержимое, но различаются по использованию.CertStore
: Кроме того, в Java есть классCertStore
, который представляет общедоступный репозиторий потенциально ненадежных сертификатов и списков отзыва. Нам нужно получить сертификаты и списки отзыва для создания пути к сертификату среди других применений .
В Java есть встроенное хранилище доверенных сертификатов под названием «cacerts» , которое содержит сертификаты известных ЦС.
5.2. Java-инструменты для PKI
В Java есть несколько действительно удобных инструментов для облегчения надежной связи:
- Существует встроенный инструмент под названием «keytool» для создания и управления хранилищем ключей и хранилищем доверия.
- Существует также еще один инструмент «jarsigner», который мы можем использовать для подписи и проверки файлов JAR.
5.3. Работа с сертификатами в Java
Давайте посмотрим, как мы можем работать с сертификатами в Java, чтобы установить безопасное соединение с использованием SSL. SSL-соединение с взаимной аутентификацией требует от нас двух вещей:
- Представить сертификат — нам нужно предоставить действительный сертификат другой стороне в общении. Для этого нам нужно загрузить файл хранилища ключей, где у нас должны быть наши открытые ключи:
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
char[] keyStorePassword = "changeit".toCharArray();
try(InputStream keyStoreData = new FileInputStream("keystore.jks")){
keyStore.load(keyStoreData, keyStorePassword);
}
- Проверка сертификата. Нам также необходимо проверить сертификат, представленный другой стороной в общении. Для этого нам нужно загрузить хранилище доверия, где у нас должны быть ранее доверенные сертификаты от других сторон:
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
// Load the trust-store from filesystem as before
Нам редко приходится делать это программно, и обычно системные параметры передаются в Java во время выполнения:
-Djavax.net.ssl.trustStore=truststore.jks
-Djavax.net.ssl.keyStore=keystore.jks
6. Аутентификация
Аутентификация — это процесс проверки представленной личности пользователя или машины на основе дополнительных данных, таких как пароль, токен или множество других учетных данных, доступных сегодня.
6.1. Аутентификация в Java
API-интерфейсы Java используют подключаемые модули входа в систему для предоставления различных и часто множественных механизмов аутентификации для приложений. LoginContext
предоставляет эту абстракцию, которая, в свою очередь, ссылается на конфигурацию и загружает соответствующий LoginModule
.
В то время как несколько провайдеров предоставляют свои модули входа в систему, в Java есть несколько модулей по умолчанию, доступных для использования:
Krb5LoginModule
для проверки подлинности на основе Kerberos .JndiLoginModule
для аутентификации на основе имени пользователя и пароля, поддерживаемой хранилищем LDAP.KeyStoreLoginModule
для аутентификации на основе криптографического ключа .
6.2. Войти по примеру
Одним из наиболее распространенных механизмов аутентификации является имя пользователя и пароль. Давайте посмотрим, как мы можем добиться этого с помощью JndiLoginModule
.
Этот модуль отвечает за получение имени пользователя и пароля от пользователя и их проверку в службе каталогов, настроенной в JNDI:
LoginContext loginContext = new LoginContext("Sample", new SampleCallbackHandler());
loginContext.login();
Здесь мы используем экземпляр LoginContext
для входа в систему . LoginContext
принимает имя записи в конфигурации входа — в данном случае это «Образец». Кроме того, мы должны предоставить экземпляр CallbackHandler
, используя LoginModule
, который взаимодействует с пользователем для получения таких сведений, как имя пользователя и пароль.
Давайте посмотрим на нашу конфигурацию входа:
Sample {
com.sun.security.auth.module.JndiLoginModule required;
};
Достаточно просто, это предполагает, что мы используем JndiLoginModule
как обязательный LoginModule
.
7. Безопасное общение
Связь по сети уязвима для многих векторов атак. Например, кто-то может подключиться к сети и прочитать наши пакеты данных во время их передачи. За прошедшие годы в отрасли было создано множество протоколов для защиты этой связи.
7.1. Поддержка Java для безопасной связи
Java предоставляет API-интерфейсы для защиты сетевого обмена данными с шифрованием, целостностью сообщений и аутентификацией как клиента, так и сервера :
- SSL/TLS: SSL и его преемник, TLS, обеспечивают безопасность при ненадежном сетевом обмене данными посредством шифрования данных и инфраструктуры с открытым ключом. Java обеспечивает поддержку SSL/TLS через
SSLSocket,
определенный в пакете «java.security.ssl
». - SASL: Simple Authentication and Security Layer (SASL) — это стандарт аутентификации между клиентом и сервером. Java поддерживает SASL как часть пакета «
java.security.sasl
». - GGS-API/Kerberos: универсальный API службы безопасности (GSS-API) предлагает единый доступ к службам безопасности с помощью различных механизмов безопасности, таких как Kerberos v5. Java поддерживает GSS-API как часть пакета «
java.security.jgss
».
7.2. Связь SSL в действии
Давайте теперь посмотрим, как мы можем открыть безопасное соединение с другими сторонами в Java с помощью SSLSocket
:
SocketFactory factory = SSLSocketFactory.getDefault();
try (Socket connection = factory.createSocket(host, port)) {
BufferedReader input = new BufferedReader(
new InputStreamReader(connection.getInputStream()));
return input.readLine();
}
Здесь мы используем SSLSocketFactory
для создания SSLSocket
. В рамках этого мы можем установить дополнительные параметры, такие как наборы шифров и используемый протокол.
Чтобы это работало правильно, мы должны создать и установить наше хранилище ключей и хранилище доверия, как мы видели ранее.
8. Контроль доступа
Контроль доступа относится к защите конфиденциальных ресурсов, таких как файловая система или кодовая база, от несанкционированного доступа. Обычно это достигается путем ограничения доступа к таким ресурсам.
8.1. Контроль доступа в Java
Мы можем добиться контроля доступа в Java , используя классы Policy
и Permission
, опосредованные через класс SecurityManager
. SecurityManager
является частью пакета « java.lang
» и отвечает за принудительную проверку контроля доступа в Java.
Когда загрузчик классов загружает класс во время выполнения, он автоматически предоставляет некоторые разрешения по умолчанию для класса, инкапсулированного в объекте Permission .
Помимо этих разрешений по умолчанию, мы можем предоставить больше возможностей классу с помощью политик безопасности. Они представлены классом Policy
.
Если во время выполнения кода среда выполнения обнаруживает запрос на защищенный ресурс, SecurityManager
проверяет запрошенное разрешение
на соответствие установленной политике
через стек вызовов. Следовательно, он либо предоставляет разрешение, либо выдает SecurityException
.
8.2. Инструменты Java для политики
Java имеет реализацию политики
по умолчанию , которая считывает данные авторизации из файла свойств. Однако записи политики в этих файлах политики должны быть в определенном формате.
Java поставляется с «policytool», графической утилитой для создания файлов политик.
8.3. Контроль доступа через пример
Давайте посмотрим, как мы можем ограничить доступ к ресурсу, например к файлу, в Java:
SecurityManager securityManager = System.getSecurityManager();
if (securityManager != null) {
securityManager.checkPermission(
new FilePermission("/var/logs", "read"));
}
Здесь мы используем SecurityManager
для проверки нашего запроса на чтение файла, заключенного в FilePermission
.
Но SecurityManager
делегирует этот запрос AccessController
. AccessController
внутренне использует установленную политику
для принятия решения.
Давайте посмотрим на пример файла политики:
grant {
permission
java.security.FilePermission
<<ALL FILES>>, "read";
};
По сути, мы даем разрешение на чтение всем файлам для всех. Но мы можем обеспечить гораздо более детальный контроль с помощью политик безопасности .
Стоит отметить, что SecurityManager
может быть не установлен по умолчанию в Java. Мы можем убедиться в этом, всегда запуская Java с параметром:
-Djava.security.manager -Djava.security.policy=/path/to/sample.policy
9. XML-подпись
Подписи XML полезны для защиты данных и обеспечения целостности данных . W3C предоставляет рекомендации по управлению подписью XML. Мы можем использовать XML-подпись для защиты данных любого типа, например двоичных данных.
9.1. XML-подпись в Java
API Java поддерживает создание и проверку подписей XML в соответствии с рекомендуемыми рекомендациями. Java XML Digital Signature API инкапсулирован в пакет « java.xml.crypto
».
Сама подпись — это просто XML-документ. XML-подписи могут быть трех типов:
- Отсоединенный: этот тип подписи находится над данными, которые являются внешними по отношению к элементу подписи.
- Enveloping: этот тип подписи над данными, которые являются внутренними для элемента Signature.
- Enveloped: этот тип подписи находится над данными, содержащими сам элемент Signature.
Конечно, Java поддерживает создание и проверку всех вышеперечисленных типов XML-подписей.
9.2. Создание XML-подписи
Теперь мы засучим рукава и сгенерируем XML-подпись для наших данных. Например, мы можем отправить XML-документ по сети. Следовательно, мы хотели бы, чтобы наш получатель мог проверить его целостность .
Итак, давайте посмотрим, как мы можем добиться этого в Java:
XMLSignatureFactory xmlSignatureFactory = XMLSignatureFactory.getInstance("DOM");
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
Document document = documentBuilderFactory
.newDocumentBuilder().parse(new FileInputStream("data.xml"));
DOMSignContext domSignContext = new DOMSignContext(
keyEntry.getPrivateKey(), document.getDocumentElement());
XMLSignature xmlSignature = xmlSignatureFactory.newXMLSignature(signedInfo, keyInfo);
xmlSignature.sign(domSignContext);
Чтобы уточнить, мы генерируем XML-подпись для наших данных, присутствующих в файле «data.xml».
Между тем, есть несколько замечаний по поводу этого фрагмента кода:
- Во- первых,
XMLSignatureFactory
— это фабричный класс для создания XML-подписей . XMLSigntaure
требует объектSignedInfo
, по которому он вычисляет подпись.XMLSigntaure
также нуждаетсяв KeyInfo
, которая инкапсулирует ключ подписи и сертификат.- Наконец,
XMLSignature
подписывает документ, используя закрытый ключ, инкапсулированный какDOMSignContext.
В результате XML-документ теперь будет содержать элемент Signature , который можно использовать для проверки его целостности.
10. Безопасность за пределами Core Java
Как мы уже видели, платформа Java предоставляет множество необходимых функций для написания безопасных приложений. Однако иногда они довольно низкоуровневые и не применимы напрямую, например, к стандартному механизму безопасности в сети.
Например, при работе с нашей системой мы, как правило, не хотим читать полный RFC OAuth и реализовывать его самостоятельно . Нам часто нужны более быстрые и высокоуровневые способы обеспечения безопасности. Именно здесь на сцену выходят фреймворки приложений — они помогают нам достичь нашей цели с гораздо меньшим количеством шаблонного кода.
А на платформе Java — обычно это означает Spring Security . Фреймворк является частью экосистемы Spring, но на самом деле его можно использовать вне чистого приложения Spring.
Проще говоря, это помогает реализовать аутентификацию, авторизацию и другие функции безопасности простым, декларативным и высокоуровневым способом.
Конечно, Spring Security широко освещается в серии руководств , а также в виде пошаговых инструкций в курсе Learn Spring Security .
11. Заключение
Короче говоря, в этом руководстве мы рассмотрели высокоуровневую архитектуру безопасности в Java. Кроме того, мы поняли, как Java предоставляет нам реализации некоторых стандартных криптографических сервисов.
Мы также рассмотрели некоторые общие шаблоны, которые можно применять для обеспечения расширяемой и подключаемой безопасности в таких областях, как аутентификация и контроль доступа.
Подводя итог, можно сказать, что это просто краткий обзор функций безопасности Java. Следовательно, каждая из областей, обсуждаемых в этом руководстве, заслуживает дальнейшего изучения. Но, надеюсь, у нас должно быть достаточно понимания, чтобы начать работу в этом направлении!