«1. Обзор

В этом руководстве мы рассмотрим основы безопасности на платформе Java. Мы также сосредоточимся на том, что нам доступно для написания безопасных приложений.

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

Поэтому нецелесообразно подробно разбираться во всех этих аспектах в этом руководстве. Однако постараемся набрать хотя бы осмысленный словарный запас.

2. Функции языка

Прежде всего, безопасность в Java начинается прямо на уровне функций языка. Это позволяет нам писать безопасный код, а также пользоваться многими неявными функциями безопасности:

    Статическая типизация данных: Java — это язык со статической типизацией, что снижает возможности обнаружения во время выполнения ошибок, связанных с типами. Модификаторы доступа: Java позволяет нам использовать различные модификаторы доступа, такие как public и private, для управления доступом к полям, методам и классам. Автоматическое управление памятью: Java имеет управление памятью на основе сборки мусора, что освобождает разработчиков от управления этим вручную. это означает, что он преобразует код в байт-код, не зависящий от платформы, и среда выполнения проверяет каждый байт-код, который он загружает для выполнения

Это не полный список функций безопасности, которые предоставляет Java, но этого достаточно, чтобы дать нам некоторую уверенность!

3. Архитектура безопасности в Java

Прежде чем мы начнем исследовать конкретные области, давайте потратим некоторое время на понимание базовой архитектуры безопасности в Java.

Основные принципы безопасности в Java основаны на интероперабельных и расширяемых реализациях Provider. Конкретная реализация Provider может реализовывать некоторые или все службы безопасности.

Например, некоторые из типичных услуг, которые может реализовать поставщик:

    Криптографические алгоритмы (такие как DSA, RSA или SHA-256) Генерация ключей, преобразование и средства управления (например, для ключей, специфичных для алгоритмов) )

Java поставляется со многими встроенными поставщиками. Кроме того, приложение может настроить несколько поставщиков в порядке предпочтения.

Следовательно, структура провайдера в Java ищет конкретную реализацию сервиса во всех провайдерах в порядке установленных для них предпочтений.

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

4. Криптография

Криптография является краеугольным камнем функций безопасности в целом и в Java. Это относится к инструментам и методам для безопасной связи в присутствии злоумышленников.

4.1. Криптография Java

Криптографическая архитектура Java (JCA) обеспечивает основу для доступа и реализации криптографических функций в Java, включая:

Цифровые подписи Дайджесты сообщений Симметричные и асимметричные шифры Коды аутентификации сообщений Генераторы ключей и фабрики ключей

    Самое главное , Java использует реализации на основе провайдеров для криптографических функций.

Кроме того, в 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 принимает имя записи в конфигурации входа — в данном случае это «Sample». Кроме того, мы должны предоставить экземпляр 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-подписи могут быть трех типов:

    Отдельный: этот тип подписи над данными, которые являются внешними по отношению к элементу подписи. Оболочка: этот тип подписи над данными, которые являются внутренними по отношению к элементу подписи. подпись находится над данными, содержащими сам элемент 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, по которому он вычисляет подпись. и сертификат Наконец, XMLSignature подписывает документ, используя закрытый ключ, инкапсулированный как DOMSignContext

В результате документ XML теперь будет содержать элемент Signature, который можно использовать для проверки его целостности.

10. Безопасность за пределами Core Java

Как мы уже видели, платформа Java предоставляет множество необходимых функций для написания безопасных приложений. Однако иногда они довольно низкоуровневые и не применимы напрямую, например, к стандартному механизму безопасности в Интернете.

Например, при работе с нашей системой мы обычно не хотим читать полный RFC OAuth и реализовывать его самостоятельно. Нам часто нужны более быстрые и высокоуровневые способы обеспечения безопасности. Именно здесь в игру вступают фреймворки приложений — они помогают нам достичь нашей цели с гораздо меньшим количеством шаблонного кода.

А на платформе Java — обычно это означает Spring Security. Фреймворк является частью экосистемы Spring, но на самом деле его можно использовать вне чистого приложения Spring.

Проще говоря, это помогает реализовать аутентификацию, авторизацию и другие функции безопасности простым, декларативным и высокоуровневым способом.

Конечно, Spring Security подробно описан в серии учебных пособий, а также в виде пошаговых инструкций в курсе Learn Spring Security.

11. Заключение

Короче говоря, в этом руководстве мы рассмотрели высокоуровневую архитектуру безопасности в Java. Кроме того, мы поняли, как Java предоставляет нам реализации некоторых стандартных криптографических сервисов.

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

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