Почему публичный статический финальный массив является дырой в безопасности?
Эффективный Java говорит:
// Потенциальная дыра в безопасности!
static public final Thing [] VALUES = {...};
Может кто-нибудь сказать мне, что такое дыра в безопасности?
8 ответов
Декларирование static final public
Поля обычно являются отличительной чертой константы класса. Он отлично подходит для примитивных типов (целых, двойных и т. Д.) И неизменных классов, таких как строки и java.awt.Color
, Проблема с массивами заключается в том, что, хотя ссылка на массив является постоянной, элементы массива все еще могут быть изменены, и, поскольку это поле, изменения неохраняются, не контролируются и обычно нежелательны.
Для борьбы с этим видимость поля массива может быть ограничена частным или частным пакетом, поэтому у вас есть меньший объем кода, который необходимо учитывать при поиске подозрительных изменений. В качестве альтернативы, а зачастую и лучше, - покончить с массивом и использовать "Список" или другой подходящий тип коллекции. Используя коллекцию, вы контролируете, разрешены ли обновления, поскольку все обновления проходят через методы. Вы можете предотвратить обновления, обернув свою коллекцию, используя Collections.unmodifiableList()
, Но имейте в виду, что, хотя коллекция является неизменной, вы также должны быть уверены, что типы, хранящиеся в ней, также являются неизменяемыми, иначе снова появится риск нежелательных изменений предполагаемой константы.
Чтобы понять, почему это потенциальная дыра в безопасности, а не просто плохая инкапсуляция, рассмотрим следующий пример:
public class SafeSites {
// a trusted class with permission to create network connections
public static final String[] ALLOWED_URLS = new String[] {
"http://amazon.com", "http://cnn.com"};
// this method allows untrusted code to connect to allowed sites (only)
public static void doRequest(String url) {
for (String allowed : ALLOWED_URLS) {
if (url.equals(allowed)) {
// send a request ...
}
}
}
}
public class Untrusted {
// An untrusted class that is executed in a security sandbox.
public void naughtyBoy() {
SafeSites.ALLOWED_URLS[0] = "http://myporn.com";
SafeSites.doRequest("http://myporn.com");
}
}
Как вы можете видеть, ошибочное использование окончательного массива означает, что ненадежный код может нарушить ограничение, которое пытается установить доверенный код / песочница. В этом случае это явно проблема безопасности.
Если ваш код не является частью критически важного приложения, вы можете игнорировать эту проблему. Но ИМО это плохая идея. В какой-то момент в будущем вы (или кто-то еще) можете повторно использовать свой код в контексте, где безопасность является проблемой. В любом случае, именно поэтому автор называет публичные финальные массивы проблемой безопасности.
Эмбер сказала это в комментарии:
Не будет больше дыры в безопасности, чем частной, если вы можете прочитать исходный код и / или байт-код в любом случае...
Это неправда.
Тот факт, что "плохой парень" может использовать исходный код / байт-коды, чтобы определить, что private
существует и ссылается на массив недостаточно для нарушения безопасности. Плохой парень также должен внедрить код в JVM, которая имеет необходимые разрешения для использования отражения. Это разрешение недоступно для ненадежного кода, работающего в (правильно реализованной) изолированной программной среде безопасности.
Обратите внимание, что массив ненулевой длины всегда изменчив, поэтому для класса неправильно иметь открытое статическое поле конечного массива или метод доступа, который возвращает такое поле. Если у класса есть такое поле или метод доступа, клиенты смогут изменять содержимое массива.
- Эффективная Java, 2-е изд. (страница 70)
Внешний класс может изменять содержимое массива, что, вероятно, не то, что вы хотите, чтобы пользователи вашего класса делали (вы хотите, чтобы они делали это с помощью метода). Похоже, автор имел в виду, что нарушает инкапсуляцию, а не безопасность.
Я предполагаю, что кто-то, объявляющий эту строку, может подумать, что другие классы не могут изменить содержимое массива, так как он помечен как final, но это не так, final только останавливает вас от повторного назначения атрибута.
В этом объявлении клиент может изменить Thing[0], Thing[1] и т. Д. (Т.е. элементы в массиве).
Я также добавил бы то, что предложил Джошуа Блох в издании Effective Java 3rd. Конечно, мы можем легко изменить значение массива, если оно объявлено как:
public static final String[] VALUES = { "a", "b" };
a.VALUES[0] = "changed value on index 0";
System.out.println(String.format("Result: %s", a.VALUES[0]));
и мы получаем Result: changed value on index 0
Джошуа Блох предложил вернуть копию массива:
private static final String[] VALUES = { "a", "b" };
public static final String[] values()
{
return VALUES.clone();
}
так что теперь, когда мы пытаемся:
a.values()[0] = "changed value on index 0";
System.out.println(String.format("Result: %s", a.values()[0]));
мы получаем Result: a
и это то, что мы хотели достичь - VALUES
неизменны.
Там также нет ничего плохого в декларации public static final
Примитивы значения, строки или другие неизменные объекты, такие как public static final int ERROR_CODE = 59;
Потому что последнее ключевое слово обеспечивает только ссылочное значение (например, в качестве области памяти), но не содержимое в нем.
Думаю, это просто означает, что все общественное против частного. Рекомендуется, чтобы локальные переменные объявлялись как закрытые, а затем использовались методы get и set вместо прямого доступа к ним. Делает их немного сложнее связываться вне вашей программы. Об этом, насколько я знаю.