предисловие
Что такое безопасность?
- Программы не могут злонамеренно повредить среду компьютера пользователя, например, троянские кони и другие вредоносные программы, способные воспроизводить себя.
- Программы не могут получить частную информацию о хосте и его сети.
- Идентификация провайдера и пользователя программы требует специальной аутентификации.
- Данные, задействованные в программе, должны быть зашифрованы после передачи и сохранения.
- Работа программы ограничена соответствующими правилами и не может потреблять слишком много системных ресурсов.
Первоначальная и самая основная цель разработки Java состоит в том, чтобы защитить информацию на компьютере от незаконного получения и изменения, но в то же время она также должна гарантировать, что выполнение программы Java на хосте не будет затронуто.
Поддержка безопасности Java
Сам JDK предоставляет базовые функции безопасности, такие как настраиваемые политики безопасности, создание дайджестов сообщений, создание цифровых подписей и т. д. В то же время в Java также есть некоторые расширения, которые более комплексно поддерживают всю систему безопасности.
Пакет расширения криптографии Java(JCE) предоставляет такие функции, как криптография, безопасный обмен ключами, безопасный дайджест сообщений, система управления ключами и т. д.
Пакет расширения безопасных сокетов Java(JSSE) обеспечивает функцию шифрования SSL (Secure Sockets Layer), которая обеспечивает безопасность связи с SSL-сервером или SSL-клиентом.
Служба аутентификации и авторизации Java(JAAS) может обеспечивать аутентификацию пользователей на платформе Java и позволяет разработчикам предоставлять или запрещать пользователям доступ к программам на основе предоставленных пользователем учетных данных аутентификации.
1. Java-песочница
Как понять? Чтобы программа была установлена на хосте, хост должен предоставить место (среду выполнения) для запуска программы, которое поддерживает запуск программы, а также ограничивает ресурсы, которые она может получить. Это похоже на то, как ребенок идет к вам домой, чтобы поиграть, вам нужно предоставить ему место для игр, не причинив ему вреда, и в то же время убедиться, что новое косметическое зеркало вашей подруги не будет разбито ребенком.
Java-песочницаОтвечает за защиту одних системных ресурсов, причем уровень защиты разный.
- Внутренние ресурсы, такие как локальная память;
- внешние ресурсы, такие как доступ к своей файловой системе или другим машинам в той же локальной сети;
- Для работающего компонента (апплета) возможен доступ к его веб-серверу;
- Поток данных, который хост передает на диск по сети.
Вообще говоря, состояние песочницы по умолчанию позволяет программам в ней получать доступ к таким ресурсам, как процессор, память и т. д., а также к веб-серверу, на котором она установлена. Если песочница полностью открыта, программы в ней имеют те же права, что и хост.
В последней реализации механизма безопасности представлена концепция домена, которую можно понимать как разделение песочницы на несколько конкретных небольших песочниц. Виртуальная машина загружает все коды в разные системные домены и домены приложений, именно системная доменная часть отвечает за взаимодействие с ключевыми ресурсами, а каждая прикладная доменная часть получает доступ к различным требуемым ресурсам через некоторых агентов системного домена. Разным защищенным доменам (Protected Domains) в виртуальной машине соответствуют разные разрешения (Permission). Файлы классов, существующие в разных доменах, имеют все разрешения текущего домена, как показано на следующем рисунке:
Реализация песочницыЗависит от следующих трех аспектов:
- Менеджер безопасности, используя предоставляемый им механизм, позволяет Java API определять, разрешено ли выполнение операций, связанных с безопасностью.
- Контроллер доступа, основа для реализации менеджера безопасности по умолчанию.
- Загрузчик классов, который может реализовывать политику безопасности и инкапсуляцию классов.
С точки зрения Java API политикой безопасности приложения управляет менеджер безопасности. Менеджер безопасности решает, может ли приложение выполнять действие. Возможность выполнения этих операций зависит от того, могут ли они получить доступ к некоторым более важным системным ресурсам, и эта проверка контролируется контроллером доступа. Таким образом, контроллер доступа является базовой реализацией менеджера безопасности, и то, что может делать менеджер безопасности, может делать и контроллер доступа. Так вот вопрос, зачем вам нужен менеджер по безопасности?
До Java2 контроллера доступа не было, в то время менеджер безопасности использовал свою внутреннюю логику для определения политики безопасности приложения, а для корректировки политики безопасности необходимо модифицировать сам менеджер безопасности. Начиная с Java 2, диспетчер безопасности передает эти задачи контроллеру доступа, который может использовать файл политики для гибкого определения политики безопасности, а также предоставляет более простой метод для предоставления определенных разрешений более точным образом. . Следовательно, программы до Java2 используют интерфейс менеджера безопасности для обеспечения безопасности системы, а это означает, что менеджер безопасности не может быть изменен, поэтому введенный контроллер доступа не может полностью заменить менеджера безопасности. Отношения между ними следующие:
2. Менеджер по безопасности
Менеджер безопасности — это «сторонний орган» между Java API и приложением. Например, при получении кредита банк проверит кредитный статус пользователя в соответствии с системой кредитной информации центрального банка, чтобы решить, давать ли кредит. Приложение Java запрашивает API Java для завершения операции, а API Java спрашивает администратора безопасности, может ли он быть выполнен.Если менеджер безопасности не хочет выполнять операцию, он выдает исключение для API Java, в противном случае Java API завершит операцию и вернется в обычном режиме.
1. Первое знакомство с SecurityManager
Класс SecurityManager является очень важным классом в API Java.Он предоставляет соответствующий интерфейс для других API Java, так что он может проверять, может ли быть выполнена операция, и действует как менеджер безопасности. Давайте посмотрим, как работает менеджер безопасности из следующего кода?
public static void main(String[] args) {
String s;
try {
FileReader fr = new FileReader(new File("E:\\test.txt"));
BufferedReader br = new BufferedReader(fr);
while ((s = br.readLine()) != null)
System.out.println(s);
} catch (IOException e) {
e.printStackTrace();
}
}
Первым шагом является создание экземпляра FileInputStream на основе объекта File при создании объекта FileReader.Исходный код выглядит следующим образом:
public FileInputStream(File file) throws FileNotFoundException {
String name = (file != null ? file.getPath() : null);
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(name);
}
if (name == null) {
throw new NullPointerException();
}
if (file.isInvalid()) {
throw new FileNotFoundException("Invalid file path");
}
fd = new FileDescriptor();
fd.attach(this);
path = name;
open(name);
}
На втором этапе API Java хочет создать объект потока байтов, который читает File.Сначала он должен получить диспетчер безопасности текущей системы, а затем проверить операцию через диспетчер безопасности.Если она проходит, то вызвать приватный метод для фактического выполнения операции (open() — частный метод экземпляра класса FileInputStream), если проверка не пройдена, будет выдано исключение безопасности, которое будет передано пользователю.
public void checkRead(String file) {
checkPermission(new FilePermission(file,SecurityConstants.FILE_READ_ACTION));
}
public void checkPermission(Permission perm) {
java.security.AccessController.checkPermission(perm);
}
Выше приведен исходный код двух методов, задействованных в SecurityManager (jdk1.8). Можно видеть, что проверка файла доступа с помощью SecurityManager в конечном итоге реализуется контроллером доступа, и AccessController выдает исключение AccessControlException во время проверки разрешений, чтобы сообщить вызывающей стороне, что проверка не удалась. Это исключение наследуется от SecurityException, которое, в свою очередь, наследуется от RuntimeException, поэтому AccessControlException является исключением времени выполнения. Обычно вызов метода часто включает в себя вызов ряда других методов.Как только возникает исключение безопасности, оно передается по цепочке вызовов к методу верхнего уровня, и, наконец, поток прерывается.
2. Операция SecurityManager
В обычных условиях диспетчер безопасности не устанавливается по умолчанию. Следовательно, в исходном коде FileInputStream, созданном выше, security==null не будет выполнять checkRead (заинтересованные студенты могут использовать метод, предоставленный System, для проверки непосредственно в основном методе). Класс System предоставляет пользователю два метода управления диспетчером безопасности.
public static SecurityManager getSecurityManager()
Этот метод используется для получения ссылки на текущий установленный менеджер безопасности или null, если он не установлен.public static void setSecurityManager(final SecurityManager s)
Этот метод используется для установки экземпляра указанного менеджера безопасности в качестве менеджера безопасности системы.
Приведенный выше код для чтения test.txt может быть выполнен в обычном режиме, и консоль будет печатать содержимое файла построчно. После настройки собственного диспетчера безопасности (наследование SecurityManager, переопределение метода checkRead) посмотрите на результат выполнения.
public class Main {
class SecurityManagerImpl extends SecurityManager {
public void checkRead(String file) {
throw new SecurityException();
}
}
public static void main(String[] args) {
System.out.println("CurrentSecurityManager is " + System.getSecurityManager());
Main m = new Main();
System.setSecurityManager(m.new SecurityManagerImpl());
System.out.println("CurrentSecurityManager is " + System.getSecurityManager());
String s;
try {
FileReader fr = new FileReader(new File("E:\\test.txt"));
BufferedReader br = new BufferedReader(fr);
while ((s = br.readLine()) != null) {
System.out.println(s);
}
}catch (IOException e) {
e.printStackTrace();
}
}
}
Результаты:
CurrentSecurityManager is null
CurrentSecurityManager is Main$SecurityManagerImpl@135fbaa4
Exception in thread "main" java.lang.SecurityException
at Main$SecurityManagerImpl.checkRead(Main.java:10)
at java.io.FileInputStream.<init>(FileInputStream.java:127)
at java.io.FileReader.<init>(FileReader.java:72)
at Main.main(Main.java:21)
Примечание. Если вы хотите установить диспетчер безопасности по умолчанию в среде java, один из способов — установить экземпляр диспетчера безопасности по умолчанию, как указано выше, а другой — добавить -Djava.security.manager при настройке параметров работы JVM. Обычно рекомендуется последний вариант, поскольку вы можете гибко указать файл политики безопасности, настроив параметр -Djava.security.policy="x:/xxx.policy" без изменения кода.
3. Используйте диспетчер безопасности
Менеджер безопасности предоставляет общедоступные методы для всех аспектов проверки безопасности, допуская произвольные вызовы. В основном API Java есть много методов, которые прямо или косвенно вызывают методы, предоставляемые менеджером безопасности, для реализации собственных операций проверки безопасности. Существует также понятие в проверках безопасности, доверенных и недоверенных классах. Очевидно, класс либо доверенный, либо ненадежный.
Класс является доверенным классом, если он является основным классом Java API или если он явно имеет разрешение на выполнение операции.
3.1 Методы проверки безопасности, связанные с доступом к файлам
Доступ к файлу здесь относится к обработке доступа к файлу в локальной сети, а не только к доступу к файлу на локальном диске.
public void checkRead(FileDescriptor fd)
public void checkRead(String file)
public void checkRead(String file, Object context)
Проверить, может ли программа прочитать указанный файл. Разные входные параметры представляют разные методы проверки. Первый метод проверяет наличие у текущего домена защиты права выполнения с именем readFileDescriptor, второй метод проверяет наличие у текущего домена защиты разрешения на чтение указанного файла, третий метод аналогичен второму, отличие состоит в том, что в Проверяется в контексте указанного контроллера доступа.
public void checkWrite(FileDescriptor fd)
public void checkWrite(String file)
Проверяет, разрешена ли программе запись в указанный файл. Первый метод проверяет, есть ли у текущего домена защиты разрешение среды выполнения с именем writeFileDescriptor, а второй метод проверяет, есть ли у текущего домена защиты разрешение на запись для указанного файла.
public void checkDelete(String file)
Проверить, разрешено ли программе удалить указанный файл. Проверяет, есть ли у текущего домена защиты права на удаление указанного файла.
В следующей таблице кратко перечислены методы, которые напрямую вызывают checkRead(), checkWrite() и checkDelete() в Java API.
3.2 Методы проверки безопасности, связанные с доступом к сети
Доступ к сети в Java обычно достигается путем открытия сетевого сокета. Сетевые сокеты логически делятся на две категории: клиентские сокеты и серверные сокеты.
public void checkConnect(String host, int port)
public void checkConnect(String host, int port, Object context)
Проверяет, может ли программа открыть клиентский сокет для указанного порта на указанном хосте. Убедитесь, что текущий домен защиты имеет права на подключение к указанному имени хоста и порту.
public void checkListen(int port)
Проверяет, может ли программа создать серверный сокет, прослушивающий определенный порт.
public void checkAccept(String host, int port)
Проверяет, может ли программа принимать клиентские соединения с указанного хоста и порта на текущем сокете сервера.
public void checkMulticast(InetAddress maddr)
Проверяет, может ли программа создать многоадресный сокет по указанному многоадресному адресу.
public void checkSetFactory()
Убедитесь, что программа может изменить реализацию сокета по умолчанию. Когда сокет создается с помощью Socket, фабрика сокетов получает новый сокет. Программы могут расширять сокеты с различной семантикой, устанавливая фабрику сокетов, которая требует, чтобы домен защиты имел разрешение во время выполнения, называемое setFactory.
3.3 Метод проверки безопасности для защиты виртуальной машины Java
Для ненадежных классов необходимо предусмотреть некоторые методы, чтобы они не могли обойти диспетчер безопасности и API Java, тем самым обеспечив безопасность виртуальной машины Java.
public void checkCreateClassLoader()
Проверьте, имеет ли текущий домен защиты разрешение среды выполнения creatClassLoader, чтобы определить, может ли программа создать загрузчик классов.
public void checkExec(String cmd)
Проверьте, есть ли у домена защиты полномочия на выполнение указанной команды, чтобы определить, может ли программа выполнить системную команду.
public void checkLink(String lib)
Проверьте, может ли программа быть подключена к виртуальной машине для подключения общей библиотеки (собственный код выполняется через библиотеку).
public void checkExit(int status)
Проверьте, есть ли у программы разрешение на выключение виртуальной машины.
public void checkPermission(Permission perm)
public void checkPermission(Permission perm, Object context)
Проверьте, имеет ли текущий домен защиты (который можно понимать как текущий поток) указанное разрешение.
3.4 Метод проверки безопасности для защиты программных потоков
Выполнение Java-программы зависит от многих потоков. Помимо потоков самой программы, виртуальная машина автоматически создает множество потоков системного уровня для пользователей, таких как сборка мусора, управление входными и выходными запросами связанных интерфейсов и так далее. Ненадежные классы не могут управлять этими потоками, влияющими на программу.
public void checkAccess(Thread t)
public void checkAccess(ThreadGroup g)
Проверяет, разрешено ли изменение состояния указанного потока (группы потоков и потоков внутри группы).
3.5 Методы проверки безопасности для защиты системных ресурсов
Программы Java могут получать доступ к некоторым ресурсам системного уровня, таким как задачи печати, буфер обмена, системные свойства и т. д. Из соображений безопасности ненадежные классы не могут получить доступ к этим ресурсам.
public void checkPrintJobAccess()
Проверьте, может ли программа получить доступ к пользовательскому принтеру (queuePrintJob — разрешение во время выполнения)
public void checkSystemClipboardAccess()
Проверьте, может ли программа получить доступ к системному буферу обмена (разрешение accessClipboard-AWT)
public void checkAwtEventQueueAccess()
Проверьте, может ли программа получить очередь системного времени (разрешение accessEventQueue-AWT)
public void checkPropertiesAccess()
public void checkPropertyAccess(String key)
Проверьте, может ли программа получить информацию о свойствах системы, принадлежащую виртуальной машине Java.
public boolean checkTopLevelWindow(Object window)
Проверьте, может ли программа создать новое окно на рабочем столе
3.6 Метод проверки безопасности для защиты самого механизма безопасности Java
public void checkMemberAccess(Class<?> clazz, int which)
Проверьте, может ли программа получить доступ к членам класса во время отражения.
public void checkSecurityAccess(String target)
Убедитесь, что программа может выполнять операции, связанные с безопасностью.
public void checkPackageAccess(String pkg)
public void checkPackageDefinition(String pkg)
Когда класс загружается с помощью загрузчика классов и указывается имя пакета, он проверяет, может ли программа получить доступ к содержимому указанного пакета.
3. Контроллер доступа
Базовый Java API предоставляет политики безопасности администратором безопасности, но большинство реализаций менеджера безопасности основаны на контроллерах доступа.
1. Создайте основу контроллера доступа
Источник кода: адрес, с которого загружается класс Java, должен быть инкапсулирован с источником кода.
Разрешения: для реализации конкретной операции требуются разрешения для инкапсуляции соответствующего запроса.
Политика: предоставьте соответствующие разрешения указанному источнику кода, и политика может быть выражена как инкапсуляция всех разрешений.
Домен защиты: инкапсуляция источника кода и соответствующие разрешения источника кода.
1.1 CodeSource
Исходный объект кода представляет собой URL-адрес класса, из которого он загружается, а также информацию о сигнатуре класса, которая создается и управляется загрузчиком классов.
public CodeSource(URL url, Certificate certs[])
Функция конструктора создает исходный объект кода для кода, загруженного по указанному URL-адресу. Второй параметр — это необязательный массив сертификатов, используемый для указания открытого ключа, который можно использовать для подписи кода.
public boolean implies(CodeSource codesource)
В соответствии с требованиями класса разрешений (Permission) определите, может ли текущий источник кода использоваться для представления источника кода, указанного параметром. Источник кода может представлять собой другой источник кода при условии, что первый должен включать все сертификаты второго, а URL-адрес последнего может быть получен из URL-адреса первого.
1.2 Permission
Экземплярный объект класса Permission — это объект разрешений, который является базовым объектом, обрабатываемым контроллером доступа. Класс Permission является абстрактным классом, и разные классы реализации отражаются как разные типы разрешений в файле политики безопасности. Экземпляр класса Permission представляет конкретное разрешение, а конкретный набор разрешений представлен экземпляром Permissions.
Чтобы реализовать собственный класс разрешений, вам необходимо наследовать класс Permission, а его абстрактные методы выглядят следующим образом:
//校验权限参数对象拥有的权限名和权限操作是否符合创建对象时的设置是否一致
public abstract boolean implies(Permission permission);
//比较两个权限对象的类型、权限名以及权限操作
public abstract boolean equals(Object obj);
public abstract int hashCode();
//返回创建对象时设置的权限操作,未设置返回空字符串
public abstract String getActions();
1.3 Policy
Контроллеру доступа необходимо определить, к каким источникам кода применяются разрешения, и предоставить им соответствующую функциональность, которая называется политикой безопасности. Java использует политику для инкапсуляции политик безопасности Класс политики безопасности по умолчанию предоставляется sun.security.provider.PolicyFile, который основан на файле политики, настроенном в jdk (%JAVA_HOME%/jre/lib/security/java.policy) разрешения для определенных источников кода. Конфигурация по умолчанию выглядит следующим образом:
// Standard extensions get all permissions by default
grant codeBase "file:${{java.ext.dirs}}/*" {
permission java.security.AllPermission;
};
// default permissions granted to all domains
grant {
// Allows any thread to stop itself using the java.lang.Thread.stop()
// method that takes no argument.
// Note that this permission is granted by default only to remain
// backwards compatible.
// It is strongly recommended that you either remove this permission
// from this policy file or further restrict it to code sources
// that you specify, because Thread.stop() is potentially unsafe.
// See the API specification of java.lang.Thread.stop() for more
// information.
permission java.lang.RuntimePermission "stopThread";
// allows anyone to listen on dynamic ports
permission java.net.SocketPermission "localhost:0", "listen";
// "standard" properies that can be read by anyone
permission java.util.PropertyPermission "java.version", "read";
permission java.util.PropertyPermission "java.vendor", "read";
permission java.util.PropertyPermission "java.vendor.url", "read";
permission java.util.PropertyPermission "java.class.version", "read";
permission java.util.PropertyPermission "os.name", "read";
permission java.util.PropertyPermission "os.version", "read";
permission java.util.PropertyPermission "os.arch", "read";
permission java.util.PropertyPermission "file.separator", "read";
permission java.util.PropertyPermission "path.separator", "read";
permission java.util.PropertyPermission "line.separator", "read";
permission java.util.PropertyPermission "java.specification.version", "read";
permission java.util.PropertyPermission "java.specification.vendor", "read";
permission java.util.PropertyPermission "java.specification.name", "read";
permission java.util.PropertyPermission "java.vm.specification.version", "read";
permission java.util.PropertyPermission "java.vm.specification.vendor", "read";
permission java.util.PropertyPermission "java.vm.specification.name", "read";
permission java.util.PropertyPermission "java.vm.version", "read";
permission java.util.PropertyPermission "java.vm.vendor", "read";
permission java.util.PropertyPermission "java.vm.name", "read";
};
Первый грант определяет все классы и jar-файлы по пути к системному свойству ${{java.ext.dirs}} (/* означает все классы и jar-файлы, если только / означает все классы, но не jar-файлы) владеет всеми разрешениями на операцию ( java.security.AllPermission) из java.ext.dirs соответствует каталогу %JAVA_HOME%/jre/lib/ext, а второй грант определяет разрешения, которые есть у всех программ JAVA, включая остановку потоков, запуск сервера сокетов и чтение некоторых свойства системы.
Класс политики обеспечиваетaddStaticPerms(PermissionCollection perms, PermissionCollection statics)
метод добавления определенного набора разрешений к набору разрешений внутри объекта политики, также обеспечиваетpublic PermissionCollection getPermissions(CodeSource codesource)
Метод устанавливает набор разрешений политики безопасности для классов из определенного источника кода.
В любом случае на виртуальной машине может быть установлен только один экземпляр класса политики безопасности, но его можно установить черезPolicy.setPolicy(Policy p)
Замените политику безопасности текущей системы или черезPolicy.getPolicy()
Получить текущий класс политики безопасности программы.
1.4 ProtectionDomain
Домен защиты — это элемент авторизации, который можно понимать как комбинацию источника кода и соответствующих разрешений. Каждый класс в виртуальной машине принадлежит одному и только одному домену защиты, который загружается с адреса, указанного источником кода, при этом набор разрешений, содержащийся в домене защиты, где находится источник кода, задает некоторые разрешения, и этот класс имеет эти разрешения. Защищенный домен устроен следующим образом:
public ProtectionDomain(CodeSource codesource,PermissionCollection permissions)
2. Контроллер доступа
Конструктор класса AccessController является закрытым, поэтому его нельзя создать. Он предоставляет некоторые статические методы снаружи, наиболее важным из которых являетсяcheckPermission(Permission p)
, этот метод определяет, хочет ли текущая защита иметь указанное разрешение на основе установленного в данный момент объекта политики. Ряд методов проверки***, предоставляемых диспетчером безопасности SecurityManager, в основномAccessController.checkPermission(Permission p)
Заканчивать.
public static void main(String[] args) {
System.setSecurityManager(new SecurityManager());
SocketPermission sp = new SocketPermission(
"127.0.0.1:6000", "connect");
try {
AccessController.checkPermission(sp);
System.out.println("Ok to open socket");
} catch (AccessControlException ace) {
System.out.println(ace);
}
}
Приведенный выше код сначала устанавливает диспетчер безопасности по умолчанию, затем создает экземпляр объекта разрешений, который подключается к локальному порту 6000, и, наконец, проходит проверку контроллера доступа. Результат печати следующий:
java.security.AccessControlException: access denied ("java.net.SocketPermission" "127.0.0.1:6000" "connect,resolve")
Контроллер доступа выдал исключение о том, что у него нет разрешения на подключение к этому адресу. Настройте разрешения на подключение для этого порта в файле политики безопасности по умолчанию:
permission java.net.SocketPermission "127.0.0.1:6000", "connect";
распечатать результат:
Ok to open socket
В реальной работе вы можете столкнуться с вызовами методов между несколькими проектами. Предположим, есть два проекта A и B. Метод testA() в классе TestA в проекте A внутренне вызывает метод testB() в классе TestB в проекте B, чтобы открыть сокет сервера, на котором расположен проект B. . При проверке разрешения, чтобы этот вид вызова работал нормально. Необходимо настроить класс TestA в файле политики безопасности виртуальной машины, на которой находится проект A, чтобы открыть разрешение на подключение к назначенному порту сервера, на котором находится проект B, а также настроить класс TestB в файле политики безопасности. файл политики безопасности виртуальной машины, на которой находится проект B, чтобы открыть соединение того же адреса и порта. Такой способ работы возможен, но он явно слишком сложен и непредсказуем. AccessController предоставляет метод doPivileged() для временного открытия разрешений для вызывающей стороны, но требует, чтобы у вызываемой стороны были соответствующие разрешения на операции.