Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Привет, Хаброжители! «Безопасно by Design» не похожа на другие книги по безопасности. В ней нет дискуссий на такие классические темы, как переполнение буфера или слабые места в криптографических хэш-функциях. Вместо собственно безопасности она концентрируется на подходах к разработке ПО. Поначалу это может показаться немного странным, но вы поймете, что недостатки безопасности часто вызваны плохим дизайном. Значительного количества уязвимостей можно избежать, используя передовые методы проектирования. Изучение того, как дизайн программного обеспечения соотносится с безопасностью, является целью этой книги. Вы узнаете, почему дизайн важен для безопасности и как его использовать для создания безопасного программного обеспечения.
Безопасность должна учитываться на каждом этапе процесса разработки ПО. Подход, изложенный в книге, поможет вам реализовывать ключевые возможности программы, исходя из стремления к безопасности. В книге описаны принципы и наилучшие практики создания исключительно защищенных приложений. На уровне кода вы откроете для себя решения, способствующие повышению надежности: безопасную обработку ошибок, безопасную валидацию и использование примитивов предметной области. Вы также освоите приемы, которые сможете использовать в пределах всего конвейера сборки-тестирования-развертывания. Кроме того, авторы делятся уникальными соображениями, касающимися современных микросервисов и облачно-ориентированного проектирования.
У читателя должен быть опыт проектирования приложений на Java, C#, .NET или другом подобном языке.
В этой книге:
• Концепции безоговорочно безопасного проектирования.
• Выявление скрытых проблем с безопасностью.
• Безопасные конструкции в коде.
• Оценка безопасности путем обнаружения распространенных изъянов в проектировании.
• Обеспечение безопасности унаследованных и микросервисных архитектур.
В этой главе
Разработчикам постоянно напоминают о приоритетах и крайних сроках. Различные грязные трюки и обход правил иногда становятся частью реальности, с которой приходится мириться. Но можно ли без этого обойтись? На самом деле решения о том, какой синтаксис использовать, с какими алгоритмами работать и как организовывать процесс выполнения, принимаете вы сами. Если вы действительно понимаете, чем одни концепции программирования лучше других, их применение становится второй натурой и занимает не больше времени, чем написание плохого кода. То же самое относится и к безопасности. Злоумышленников не заботят ваши крайние сроки и приоритеты — слабо защищенную систему можно взломать независимо от того, почему и в каких обстоятельствах она создавалась.
Мы все несем ответственность за проектирование безопасного программного обеспечения. Из данной главы вы узнаете, почему для этого не требуется дополнительное время по сравнению с разработкой слабо защищенного уязвимого ПО. В связи с этим мы разделили материал на три части, где обсуждаются разные стратегии решения проблем с безопасностью, которые вам могут встретиться в повседневной работе (табл. 4.1).
Таким образом мы попытаемся изменить ваш образ мышления, снабдить вас новым набором инструментов и дать рекомендации, которые вы сможете применять в повседневной работе. Вы также научитесь выявлять слабые места в устаревшем коде и исправлять их. Начнем с принципа неизменяемости и приведем пример того, как он помогает справляться с обновлениями.
Проектируя объект, вы должны определиться с тем, каким он должен быть: изменяемым или неизменяемым. В первом случае его состояние может меняться, а во втором — нет. Это может показаться несущественным, но с точки зрения безопасности этот аспект очень важен. Неизменяемые объекты можно безопасно разделять между потоками выполнения, с их помощью данные можно сделать высокодоступными, что очень значимо для защиты системы от DoS-атак (denial of service — «отказ в обслуживании»). А вот изменяемые объекты рассчитаны на обновление, что может привести к внесению несанкционированных изменений. Поддержка изменяемости зачастую привносится в систему из-за того, что ее требуют фреймворки, или потому, что на первый взгляд она делает код проще. Но это опасный подход, за который, возможно, придется дорого заплатить. Чтобы это проиллюстрировать, рассмотрим пример того, как использование изменяемости в архитектуре веб-магазина вызывает проблемы с безопасностью, которые можно легко решить за счет неизменяемости.
Представьте себе обыкновенный веб-магазин, клиенты которого аутентифицируются и добавляют товары в корзину покупок. У каждого клиента есть кредитный рейтинг, основанный на истории покупок и членских баллах. Низкий кредитный рейтинг позволяет платить только с помощью кредитной карты, а высокий в дополнение к этому дает возможность использовать счет-фактуру. Вычисление кредитного рейтинга требует довольно значительных ресурсов и проводится непрерывно, чтобы сделать общую нагрузку на систему более равномерной.
В целом система работала нормально — до недавних пор. Во время последней рекламной кампании на сайт магазина заходило много людей. Система плохо справлялась с нагрузкой, и клиенты жаловались на истечение времени ожидания заказа, большие задержки и нелогичные варианты оплаты. Последняя проблема казалась несущественной, но затем финансовый отдел сообщил о том, что у многих клиентов с низким кредитным рейтингом появились неоплаченные счета-фактуры. Началось полномасштабное расследование — безопасность системы под угрозой! Главным подозреваемым, естественно, был код для вычисления кредитного рейтинга, но, к всеобщему удивлению, проблема оказалась куда более серьезной — внутреннее устройство объекта Customer.
Внутреннее устройство объекта Customer
Объект Customer, показанный в листинге 4.1, имеет две интересные особенности. Первая состоит в том, что все поля инициализируются с помощью методов-сеттеров. Из этого следует, что после создания объекта его внутреннее состояние можно изменять. Это может стать источником проблем, так как мы не можем гарантировать, что объект инициализирован правильно. Еще одно наблюдение заключается в том, что каждый метод помечен ключевым словом synchronized, которое должно предотвращать конкурентное изменение полей, что, в свою очередь, может привести к конфликту потоков (это когда потоки вынуждены останавливаться и ждать, пока какой-то другой поток не снимет одну или несколько блокировок).
Пока что не совсем понятно, как эти проектные решения относятся к безопасности, но все прояснится, когда мы классифицируем проблемы веб-магазина как нарушение целостности или доступности данных.
Классификация проблем как нарушение целостности или доступности
Под целостностью данных подразумевается их согласованность на протяжении всего жизненного цикла, доступность данных — это гарантия того, что их можно получить с соблюдением ожидаемого уровня производительности в системе. Обе концепции являются ключом к пониманию причины проблем, возникших в веб-магазине. Например, невозможность извлечь данные — это проблема с доступностью, которая часто сводится к тому, что какой-то код мешает параллельному или конкурентному доступу. Аналогично анализ проблем целостности следует начинать с кода, позволяющего вносить изменения. В табл. 4.2 показаны проблемы веб-магазина, классифицированные как нарушение доступности и целостности.
Эти категории дают общее представление о том, какие участки класса Customer заслуживают особого внимания. Начнем с того, как неявное блокирование может ухудшить доступность.
Неявное блокирование ухудшает доступность
Вопрос о том, нужно ли запрещать конкурентный и параллельный доступ, зачастую становится компромиссом между производительностью и согласованностью. Если состояние всегда должно оставаться согласованным, а обновления чередуются с операциями чтения, имеет смысл прибегнуть к механизмам блокирования. Но если данные преимущественно читают, блокирование может привести к излишним конфликтам между потоками выполнения. В конфликтах, вызванных конкурентным доступом, как правило, легче разобраться, чем в тех, причина которых — параллельный доступ. Возьмем, к примеру, метод synchronized из листинга 4.1: в любой момент его может выполнять только один поток, так как для доступа к нему необходимо получить блокировку, встроенную в его объект. Все остальные потоки, пытающиеся конкурентно обратиться к этому методу, должны ждать, пока эта блокировка не будет снята, что может привести к конфликтам.
Использование ключевого слова synchronized на уровне метода также может вызвать конфликты между потоками при параллельном обращении к двум и более методам. Оказывается, чтобы получить доступ ко всем методам, объекты, помеченные как synchronized, должны получить одну и ту же встроенную блокировку. Это означает, что потоки, вызывающие эти методы параллельно, неявно блокируют друг друга и подобные конфликты иногда сложно обнаружить.
Если вернуться к нашему веб-магазину и проанализировать соотношение между операциями чтения и записи, окажется, что чтение данных о клиенте происходит намного чаще, чем их обновление. Дело в том, что изменением данных в основном занимается алгоритм вычисления кредитного рейтинга, а операции чтения выполняются в рамках многочисленных клиентских запросов, в том числе со стороны системы создания отчетов, принадлежащей отделу финансов. Это указывает на то, что параллельное и конкурентное чтение безопасны. Так почему бы и вовсе не избавиться от механизма блокирования (synchronized)?
Параллельное и конкурентное чтение, скорее всего, безопасны, но при минимизации конфликтов нельзя игнорировать операции записи. Вместо отказа от механизма блокирования необходимо подумать о другом решении. Например, можно использовать продвинутые средства блокирования наподобие ReadWriteLock, которое учитывает преобладание операций чтения. Однако механизмы блокирования усложняют код и повышают когнитивную нагрузку на разработчиков. Мы предпочитаем этого избегать.
Есть более простая и успешная стратегия, состоящая в использовании методик проектирования, рассчитанных на параллельный и конкурентный доступ, таких как неизменяемость. В листинге 4.2 вы увидите неизменяемую версию класса Customer, которая не позволяет обновлять состояние. Это означает, что объекты Customer можно безопасно разделять между потоками без помощи блокировок. В результате получается высокий уровень доступности с небольшим числом конфликтов. Иными словами, потоки больше не блокируются.
Но нам все равно нужна возможность редактировать данные клиента. Как этого добиться, если класс Customer неизменяемый? Оказывается, для поддержки изменений можно обойтись без изменяемых структур данных. Достаточно лишь отделить чтение от записи и выполнять обновления через отдельные каналы. Это может показаться слишком сложным, но если в вашей системе наблюдается дисбаланс между чтением и записью, результат может стоить приложенных усилий. О том, как реализовать это на практике, речь пойдет в главе 7, где мы подробно обсудим шаблон проектирования «Снимок сущности».
Вы уже знаете, что неизменяемость предотвращает проблемы с доступностью на уровне проектирования, но что насчет нарушения целостности в нашем веб-магазине? Поможет ли здесь неизменяемость? Возможно. Давайте посмотрим, как изменяемая архитектура Customer и CreditScore делает вероятными проблемы с целостностью.
Изменение кредитного рейтинга:
проблема с целостностью
Прежде чем погружаться в анализ, вспомним, в чем заключается проблема с кредитным рейтингом. Каждому клиенту назначается кредитный рейтинг; если он достаточно высокий, клиент может выполнять оплату по счету-фактуре. Во время последней рекламной кампании система вышла из строя, и финансовый отдел сообщил о том, что множество клиентов с низким рейтингом имеют неоплаченные счета-фактуры. Нарушение целостности данных привело к изменению кредитного рейтинга, которое открыло доступ к дополнительному способу оплаты. Но как такое возможно? Взглянув на логику управления кредитным рейтингом в изменяемом объекте Customer (листинг 4.3), мы видим следующее:
Обсудим каждое из этих наблюдений и подумаем, каким образом они приводят к нарушению целостности данных.
Первое, что может вызвать проблему целостности, — это явное изменение кредитного рейтинга путем инициализации. Поле creditScore в классе Customer инициализируется с помощью метода setCreditScore. Это было сделано сознательно, однако такой способ инициализации поля позволяет менять кредитный рейтинг клиента в любой момент, так как не гарантирует, что его можно вызвать только один раз. Это может показаться приемлемым, поскольку клиент, как ожидается, будет выполнять только чтение данных, но изменяемый характер класса Customer не позволяет предотвратить случайное использование этого метода. Это означает, что вы не можете гарантировать целостность объекта Customer.
Вторая проблема связана с изменением кредитного рейтинга за пределами Customer. Если взглянуть на метод getCreditScore внутри Customer, можно заметить непреднамеренную утечку внутреннего поля creditScore. В результате становится возможным изменение кредитного рейтинга за пределами объекта Customer без получения блокировки. Это чрезвычайно опасно, так как Customer является разделяемым изменяемым объектом и его обновление без синхронизации — это бомба замедленного действия (подробнее об этом — в главе 6). Но это еще не самое страшное.
Класс CreditScore был спроектирован как изменяемый, поэтому мы можем вручную изменить ID клиента, вызвав функцию setCustomerId, как показано в листинге 4.4. Из этого следует, что объекты Customer и CreditScore могут иметь разные ID, а такое нарушение связи способно привести к использованию неправильного значения кредитного рейтинга в методе compute!
Чтобы исправить ситуацию, мы должны модифицировать класс CreditScore. В листинге 4.5 вы видите его неизменяемую версию. Обратите внимание на то, что мы удалили ключевое слово synchronized и зависимость от ID клиента. Дело в том, что больше не нужно получать блокировку при проверке кредитного рейтинга, так как теперь он не может измениться после передачи в конструктор. Это, в свою очередь, означает, что зависимость от определенного клиента становится лишней, поэтому архитектуру можно упростить за счет выноса вычисления кредитного рейтинга за пределы объекта. Это позволяет разделять рейтинг между потоками, причем нам не угрожают несанкционированные обновления, конфликты и блокирование.
Третий способ изменения creditScore не такой очевидный, как предыдущие два, — он связан с модификацией разделяемой ссылки на кредитный рейтинг. Если взглянуть на метод setCreditScore в изменяемой версии объекта Customer, можно заметить, что внутреннему полю присваивается внешняя изменяемая ссылка на creditScore. Это не страшно, пока данную внешнюю ссылку не используют повторно в другом объекте Customer. Но если это произойдет, вычисляемое значение кредитного рейтинга будет одинаковым для всех клиентов, которые разделяют ссылку. Это серьезное нарушение целостности, которое объясняет нелогичные варианты оплаты в веб-магазине.
Определение первопричины
Все сценарии, которые мы исследовали, могут объяснить проблемы с целостностью данных в веб-магазине, но какой из них является настоящей причиной? На самом деле это неважно. Главное здесь то, что, решив использовать изменяемые классы Customer и CreditScore, разработчики системы сделали свой код менее безопасным сразу с нескольких точек зрения. Но если выбрать подход, ориентированный на неизменяемость, то потребность в блокировках и защите от случайных изменений исчезает. Такое проектное решение само по себе повышает уровень безопасности.
Теперь вы знаете, как неизменяемость позволяет избежать проблем с целостностью и доступностью данных. Возможно, вы заметили, что в некоторых случаях некорректные данные агрессивно блокировались еще до попадания в объект. Это еще один эффективный прием обеспечения безопасности. Теперь перейдем к быстрому прекращению работы с помощью контрактов.
Более подробно с книгой можно ознакомиться на сайте издательства
» Оглавление
» Отрывок
Для Хаброжителей скидка 25% по купону — Design
По факту оплаты бумажной версии книги на e-mail высылается электронная книга.
Безопасность должна учитываться на каждом этапе процесса разработки ПО. Подход, изложенный в книге, поможет вам реализовывать ключевые возможности программы, исходя из стремления к безопасности. В книге описаны принципы и наилучшие практики создания исключительно защищенных приложений. На уровне кода вы откроете для себя решения, способствующие повышению надежности: безопасную обработку ошибок, безопасную валидацию и использование примитивов предметной области. Вы также освоите приемы, которые сможете использовать в пределах всего конвейера сборки-тестирования-развертывания. Кроме того, авторы делятся уникальными соображениями, касающимися современных микросервисов и облачно-ориентированного проектирования.
У читателя должен быть опыт проектирования приложений на Java, C#, .NET или другом подобном языке.
В этой книге:
• Концепции безоговорочно безопасного проектирования.
• Выявление скрытых проблем с безопасностью.
• Безопасные конструкции в коде.
• Оценка безопасности путем обнаружения распространенных изъянов в проектировании.
• Обеспечение безопасности унаследованных и микросервисных архитектур.
Концепции программирования, способствующие безопасности
В этой главе
- Как неизменяемость решает проблемы с безопасностью.
- Как контракты с быстрым прекращением работы делают архитектуру безопасной.
- Виды проверок корректности и порядок их проведения.
Разработчикам постоянно напоминают о приоритетах и крайних сроках. Различные грязные трюки и обход правил иногда становятся частью реальности, с которой приходится мириться. Но можно ли без этого обойтись? На самом деле решения о том, какой синтаксис использовать, с какими алгоритмами работать и как организовывать процесс выполнения, принимаете вы сами. Если вы действительно понимаете, чем одни концепции программирования лучше других, их применение становится второй натурой и занимает не больше времени, чем написание плохого кода. То же самое относится и к безопасности. Злоумышленников не заботят ваши крайние сроки и приоритеты — слабо защищенную систему можно взломать независимо от того, почему и в каких обстоятельствах она создавалась.
Мы все несем ответственность за проектирование безопасного программного обеспечения. Из данной главы вы узнаете, почему для этого не требуется дополнительное время по сравнению с разработкой слабо защищенного уязвимого ПО. В связи с этим мы разделили материал на три части, где обсуждаются разные стратегии решения проблем с безопасностью, которые вам могут встретиться в повседневной работе (табл. 4.1).
Таким образом мы попытаемся изменить ваш образ мышления, снабдить вас новым набором инструментов и дать рекомендации, которые вы сможете применять в повседневной работе. Вы также научитесь выявлять слабые места в устаревшем коде и исправлять их. Начнем с принципа неизменяемости и приведем пример того, как он помогает справляться с обновлениями.
4.1. Неизменяемость
Проектируя объект, вы должны определиться с тем, каким он должен быть: изменяемым или неизменяемым. В первом случае его состояние может меняться, а во втором — нет. Это может показаться несущественным, но с точки зрения безопасности этот аспект очень важен. Неизменяемые объекты можно безопасно разделять между потоками выполнения, с их помощью данные можно сделать высокодоступными, что очень значимо для защиты системы от DoS-атак (denial of service — «отказ в обслуживании»). А вот изменяемые объекты рассчитаны на обновление, что может привести к внесению несанкционированных изменений. Поддержка изменяемости зачастую привносится в систему из-за того, что ее требуют фреймворки, или потому, что на первый взгляд она делает код проще. Но это опасный подход, за который, возможно, придется дорого заплатить. Чтобы это проиллюстрировать, рассмотрим пример того, как использование изменяемости в архитектуре веб-магазина вызывает проблемы с безопасностью, которые можно легко решить за счет неизменяемости.
4.1.1. Обыкновенный веб-магазин
Представьте себе обыкновенный веб-магазин, клиенты которого аутентифицируются и добавляют товары в корзину покупок. У каждого клиента есть кредитный рейтинг, основанный на истории покупок и членских баллах. Низкий кредитный рейтинг позволяет платить только с помощью кредитной карты, а высокий в дополнение к этому дает возможность использовать счет-фактуру. Вычисление кредитного рейтинга требует довольно значительных ресурсов и проводится непрерывно, чтобы сделать общую нагрузку на систему более равномерной.
В целом система работала нормально — до недавних пор. Во время последней рекламной кампании на сайт магазина заходило много людей. Система плохо справлялась с нагрузкой, и клиенты жаловались на истечение времени ожидания заказа, большие задержки и нелогичные варианты оплаты. Последняя проблема казалась несущественной, но затем финансовый отдел сообщил о том, что у многих клиентов с низким кредитным рейтингом появились неоплаченные счета-фактуры. Началось полномасштабное расследование — безопасность системы под угрозой! Главным подозреваемым, естественно, был код для вычисления кредитного рейтинга, но, к всеобщему удивлению, проблема оказалась куда более серьезной — внутреннее устройство объекта Customer.
Внутреннее устройство объекта Customer
Объект Customer, показанный в листинге 4.1, имеет две интересные особенности. Первая состоит в том, что все поля инициализируются с помощью методов-сеттеров. Из этого следует, что после создания объекта его внутреннее состояние можно изменять. Это может стать источником проблем, так как мы не можем гарантировать, что объект инициализирован правильно. Еще одно наблюдение заключается в том, что каждый метод помечен ключевым словом synchronized, которое должно предотвращать конкурентное изменение полей, что, в свою очередь, может привести к конфликту потоков (это когда потоки вынуждены останавливаться и ждать, пока какой-то другой поток не снимет одну или несколько блокировок).
Пока что не совсем понятно, как эти проектные решения относятся к безопасности, но все прояснится, когда мы классифицируем проблемы веб-магазина как нарушение целостности или доступности данных.
Классификация проблем как нарушение целостности или доступности
Под целостностью данных подразумевается их согласованность на протяжении всего жизненного цикла, доступность данных — это гарантия того, что их можно получить с соблюдением ожидаемого уровня производительности в системе. Обе концепции являются ключом к пониманию причины проблем, возникших в веб-магазине. Например, невозможность извлечь данные — это проблема с доступностью, которая часто сводится к тому, что какой-то код мешает параллельному или конкурентному доступу. Аналогично анализ проблем целостности следует начинать с кода, позволяющего вносить изменения. В табл. 4.2 показаны проблемы веб-магазина, классифицированные как нарушение доступности и целостности.
Эти категории дают общее представление о том, какие участки класса Customer заслуживают особого внимания. Начнем с того, как неявное блокирование может ухудшить доступность.
Неявное блокирование ухудшает доступность
Вопрос о том, нужно ли запрещать конкурентный и параллельный доступ, зачастую становится компромиссом между производительностью и согласованностью. Если состояние всегда должно оставаться согласованным, а обновления чередуются с операциями чтения, имеет смысл прибегнуть к механизмам блокирования. Но если данные преимущественно читают, блокирование может привести к излишним конфликтам между потоками выполнения. В конфликтах, вызванных конкурентным доступом, как правило, легче разобраться, чем в тех, причина которых — параллельный доступ. Возьмем, к примеру, метод synchronized из листинга 4.1: в любой момент его может выполнять только один поток, так как для доступа к нему необходимо получить блокировку, встроенную в его объект. Все остальные потоки, пытающиеся конкурентно обратиться к этому методу, должны ждать, пока эта блокировка не будет снята, что может привести к конфликтам.
Использование ключевого слова synchronized на уровне метода также может вызвать конфликты между потоками при параллельном обращении к двум и более методам. Оказывается, чтобы получить доступ ко всем методам, объекты, помеченные как synchronized, должны получить одну и ту же встроенную блокировку. Это означает, что потоки, вызывающие эти методы параллельно, неявно блокируют друг друга и подобные конфликты иногда сложно обнаружить.
Если вернуться к нашему веб-магазину и проанализировать соотношение между операциями чтения и записи, окажется, что чтение данных о клиенте происходит намного чаще, чем их обновление. Дело в том, что изменением данных в основном занимается алгоритм вычисления кредитного рейтинга, а операции чтения выполняются в рамках многочисленных клиентских запросов, в том числе со стороны системы создания отчетов, принадлежащей отделу финансов. Это указывает на то, что параллельное и конкурентное чтение безопасны. Так почему бы и вовсе не избавиться от механизма блокирования (synchronized)?
Параллельное и конкурентное чтение, скорее всего, безопасны, но при минимизации конфликтов нельзя игнорировать операции записи. Вместо отказа от механизма блокирования необходимо подумать о другом решении. Например, можно использовать продвинутые средства блокирования наподобие ReadWriteLock, которое учитывает преобладание операций чтения. Однако механизмы блокирования усложняют код и повышают когнитивную нагрузку на разработчиков. Мы предпочитаем этого избегать.
СОВЕТ
Неизменяемые значения можно безопасно разделять между потоками выполнения без применения блокировок, что позволяет избежать конфликтов.
Есть более простая и успешная стратегия, состоящая в использовании методик проектирования, рассчитанных на параллельный и конкурентный доступ, таких как неизменяемость. В листинге 4.2 вы увидите неизменяемую версию класса Customer, которая не позволяет обновлять состояние. Это означает, что объекты Customer можно безопасно разделять между потоками без помощи блокировок. В результате получается высокий уровень доступности с небольшим числом конфликтов. Иными словами, потоки больше не блокируются.
Но нам все равно нужна возможность редактировать данные клиента. Как этого добиться, если класс Customer неизменяемый? Оказывается, для поддержки изменений можно обойтись без изменяемых структур данных. Достаточно лишь отделить чтение от записи и выполнять обновления через отдельные каналы. Это может показаться слишком сложным, но если в вашей системе наблюдается дисбаланс между чтением и записью, результат может стоить приложенных усилий. О том, как реализовать это на практике, речь пойдет в главе 7, где мы подробно обсудим шаблон проектирования «Снимок сущности».
Вы уже знаете, что неизменяемость предотвращает проблемы с доступностью на уровне проектирования, но что насчет нарушения целостности в нашем веб-магазине? Поможет ли здесь неизменяемость? Возможно. Давайте посмотрим, как изменяемая архитектура Customer и CreditScore делает вероятными проблемы с целостностью.
Изменение кредитного рейтинга:
проблема с целостностью
Прежде чем погружаться в анализ, вспомним, в чем заключается проблема с кредитным рейтингом. Каждому клиенту назначается кредитный рейтинг; если он достаточно высокий, клиент может выполнять оплату по счету-фактуре. Во время последней рекламной кампании система вышла из строя, и финансовый отдел сообщил о том, что множество клиентов с низким рейтингом имеют неоплаченные счета-фактуры. Нарушение целостности данных привело к изменению кредитного рейтинга, которое открыло доступ к дополнительному способу оплаты. Но как такое возможно? Взглянув на логику управления кредитным рейтингом в изменяемом объекте Customer (листинг 4.3), мы видим следующее:
- то, как creditScore инициализируется внутри Customer, позволяет в любой момент изменить кредитный рейтинг;
- ссылка на creditScore случайно утекла из метода getCreditScore, что позволило вносить изменения за пределами Customer;
- метод setCreditScore не создает копию аргумента, что позволяет внедрить разделяемую ссылку на creditScore.
Обсудим каждое из этих наблюдений и подумаем, каким образом они приводят к нарушению целостности данных.
Первое, что может вызвать проблему целостности, — это явное изменение кредитного рейтинга путем инициализации. Поле creditScore в классе Customer инициализируется с помощью метода setCreditScore. Это было сделано сознательно, однако такой способ инициализации поля позволяет менять кредитный рейтинг клиента в любой момент, так как не гарантирует, что его можно вызвать только один раз. Это может показаться приемлемым, поскольку клиент, как ожидается, будет выполнять только чтение данных, но изменяемый характер класса Customer не позволяет предотвратить случайное использование этого метода. Это означает, что вы не можете гарантировать целостность объекта Customer.
Вторая проблема связана с изменением кредитного рейтинга за пределами Customer. Если взглянуть на метод getCreditScore внутри Customer, можно заметить непреднамеренную утечку внутреннего поля creditScore. В результате становится возможным изменение кредитного рейтинга за пределами объекта Customer без получения блокировки. Это чрезвычайно опасно, так как Customer является разделяемым изменяемым объектом и его обновление без синхронизации — это бомба замедленного действия (подробнее об этом — в главе 6). Но это еще не самое страшное.
Класс CreditScore был спроектирован как изменяемый, поэтому мы можем вручную изменить ID клиента, вызвав функцию setCustomerId, как показано в листинге 4.4. Из этого следует, что объекты Customer и CreditScore могут иметь разные ID, а такое нарушение связи способно привести к использованию неправильного значения кредитного рейтинга в методе compute!
Чтобы исправить ситуацию, мы должны модифицировать класс CreditScore. В листинге 4.5 вы видите его неизменяемую версию. Обратите внимание на то, что мы удалили ключевое слово synchronized и зависимость от ID клиента. Дело в том, что больше не нужно получать блокировку при проверке кредитного рейтинга, так как теперь он не может измениться после передачи в конструктор. Это, в свою очередь, означает, что зависимость от определенного клиента становится лишней, поэтому архитектуру можно упростить за счет выноса вычисления кредитного рейтинга за пределы объекта. Это позволяет разделять рейтинг между потоками, причем нам не угрожают несанкционированные обновления, конфликты и блокирование.
Третий способ изменения creditScore не такой очевидный, как предыдущие два, — он связан с модификацией разделяемой ссылки на кредитный рейтинг. Если взглянуть на метод setCreditScore в изменяемой версии объекта Customer, можно заметить, что внутреннему полю присваивается внешняя изменяемая ссылка на creditScore. Это не страшно, пока данную внешнюю ссылку не используют повторно в другом объекте Customer. Но если это произойдет, вычисляемое значение кредитного рейтинга будет одинаковым для всех клиентов, которые разделяют ссылку. Это серьезное нарушение целостности, которое объясняет нелогичные варианты оплаты в веб-магазине.
Определение первопричины
Все сценарии, которые мы исследовали, могут объяснить проблемы с целостностью данных в веб-магазине, но какой из них является настоящей причиной? На самом деле это неважно. Главное здесь то, что, решив использовать изменяемые классы Customer и CreditScore, разработчики системы сделали свой код менее безопасным сразу с нескольких точек зрения. Но если выбрать подход, ориентированный на неизменяемость, то потребность в блокировках и защите от случайных изменений исчезает. Такое проектное решение само по себе повышает уровень безопасности.
Теперь вы знаете, как неизменяемость позволяет избежать проблем с целостностью и доступностью данных. Возможно, вы заметили, что в некоторых случаях некорректные данные агрессивно блокировались еще до попадания в объект. Это еще один эффективный прием обеспечения безопасности. Теперь перейдем к быстрому прекращению работы с помощью контрактов.
Более подробно с книгой можно ознакомиться на сайте издательства
» Оглавление
» Отрывок
Для Хаброжителей скидка 25% по купону — Design
По факту оплаты бумажной версии книги на e-mail высылается электронная книга.