2011-01-07 3 views
2

Я разрабатываю приложение iOS, которое является менеджером/зрителем для другого проекта. Идея заключается в том, что приложение сможет обрабатывать данные, хранящиеся в базе данных, в виде нескольких визуализаций - общий эффект аналогичен cacti. Я делаю визуализации полностью настраиваемыми пользователем: пользователь определяет, что она хочет видеть, и добавляет ограничения.Является ли непосредственно выполнение SQL-кода плохим дизайном?

Возможно, она указала, например, график графика в течение последних трех недель с учетными записями пользователей, которые в настоящее время активны и не находятся в Соединенных Штатах.

Моя проблема заключается в том, что единственный проект, о котором я могу думать, более или менее передаёт прямой SQL из приложения iOS на серверный сервер, который будет выполнен против базы данных. Я знаю, что это плохая практика, и все должно быть написано с точки зрения хранимых процедур. Но как еще я могу поддерживать достаточную гибкость, чтобы полностью отвечать запросам пользователя?

Несмотря на то, что приложение составило SQL, прямой SQL никогда не отображается или не вводится пользователем. Это все отвлечено в UIDateTimeChoosers, UIPickerViews и т. П.

ответ

3

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

В качестве тривиального примера пользователь может скомпрометировать ваш интерфейс, чтобы отправить запрос SELECT password, salt FROM users WHERE login = 'admin', захватить ответ, чтобы получить необработанные данные, и перебор пароля администратора. По мере того как популярность приложения растет, пул злонамеренных пользователей растет более чем линейно, до тех пор, пока их коллективный интеллект не станет больше, чем у вашей команды; вы не должны ставить себя в ситуации, когда успех будет вашим падением.

Вы можете взять SQL-запрос, отправленный клиентским приложением, и попытаться разобрать его на стороне сервера, чтобы применить соответствующие ограничения к запросу, чтобы, таким образом, запереть пользователя. Но для этого вам потребуется написать мини-синтаксический анализатор SQL в коде сервера, и кто хочет сделать все это? Гораздо проще написать код, который может писать SQL, чем писать код, который может его прочитать.

Моя команда решить аналогичную задачу для интерфейса отчетности в довольно сложной веб-приложения, и наш подход пошел что-то вроде этого:

Так как вы уже планируете использовать графический интерфейс для построения запроса, это будет довольно просто превратить необработанные данные из элементов интерфейса в структуру данных, которая представляет вход пользователя (и, в свою очередь, запрос). Например, пользователь может указать, используя ваш интерфейс, условие, что они хотят, чтобы результаты были ограничены теми, которые были собраны 5 мая 2010 года всеми, кроме Джона. (Предположим, что Джон UserID: 3.) Использование вариант формата JSON используется моя команда, вы бы просто разорвать, что данные из пользовательского интерфейса во что-то вроде:

{ "ConditionType": "AND", 
    "Clauses": [ 
    { "Operator": "Equals", 
     "Operands": [ 
     { "Column": "CollectedDate" }, 
     { "Value": "2010-05-05" } 
     ] 
    }, 
    { "Operator": "NotEquals", 
     "Operands": [ 
     { "Column": "CollectedByUserID" }, 
     { "Value": 3 } 
     ] 
    } 
    ] 
} 

На стороне клиента, создавая этот вид данных структура в значительной степени изоморфна задаче создания SQL-запроса и, возможно, несколько проще, так как вам не нужно беспокоиться о синтаксисе SQL.

Здесь есть тонкости, которые я затушевываю. Это только часть WHERE-части запроса и будет вынуждена жить в более крупном объекте ({ "Select": ..., "From": ..., "Where": ..., "OrderBy": ... }). Возможны более сложные сценарии.Например, если вам требуется, чтобы пользователь мог указать несколько таблиц и как они объединяются вместе, вы должны быть более конкретными при указании столбца в качестве операнда в предложении WHERE. Но опять же, все это - работа, которую вам все равно придется делать, чтобы напрямую строить запрос.

Затем сервер будет десериализовать эту структуру. (Стоит отметить, что имена столбцов, предоставленные пользователем, не должны быть грязными - мы сопоставили их с списком разрешенных столбцов в нашем приложении, если столбец не был в списке, десериализация потерпела неудачу, и пользователь получил сообщение об ошибке.) С простой структурой объектов, с которой можно работать, внесение изменений в запрос почти тривиально. Приложение-сервер может изменить список предложений WHERE, чтобы применить соответствующие ограничения доступа к данным. Например, вы можете сказать (в псевдокоде) Query.WhereClauses.Add(new WhereClause(Operator: 'Equals', Operands: { 'User.UserID', LoggedInUser.UserID })).

Код сервера затем передает объект в относительно простой построитель запросов, который перемещает объект и разбивает строку запроса SQL. Это проще, чем кажется, но убедитесь, что все предоставленные пользователем параметры передаются в чистоте. Не дезинфицируйте - используйте параметризованные запросы.

Этот подход в конечном итоге сработало очень хорошо для нас, по нескольким причинам:

  1. Это позволило разбить сложность составления запроса при помощи графического интерфейса.
  2. Это гарантирует, что пользовательские запросы никогда не выполнялись грязными.
  3. Это позволило нам добавить произвольные предложения к запросам на различные виды ограничений доступа.
  4. Это было достаточно доступно для того, чтобы мы могли делать отличные вещи, например, позволяя пользователям искать в пользовательских полях.

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

+1

+2 за хороший ответ, -1 за поощрение использования синтаксиса JS в JSON, хотя :). – svens

+0

@svens Спасибо! Однако я не заметил ошибку, которую вы указали; насколько я могу судить, мой пример JSON соответствует опубликованному стандарту. Не могли бы Вы уточнить? – WCWedin

+0

@WCWedin Запустите его через jsonlint.com, имена участников должны быть указаны. Честно говоря, я не уверен, где это указано в стандарте, я просто знаю, что некоторые декодеры JSON собираются сдаться. – svens

0

Если вы хотите, чтобы пользователи отправляли по фактическому sql, попробуйте фильтровать такие слова, как «drop and truncate». Если вам нужно разрешить удаление, вы можете принудительно использовать первичный ключ.

+0

извините, я не был ясен. Я имел в виду, что мое приложение собирает SQL с помощью довольно простых элементов управления, таких как UIDateTimePickers и т. Д. Я не беспокоюсь об этом с точки зрения внедрения sql, а также с точки зрения производительности, гибкости и масштабируемости. –

3

EDIT: Я пришел к неприязнь к моему ответу здесь. Я согласен с некоторыми комментариями ниже, и я хотел бы рекомендовать вам создавать объекты «Query» на клиенте и передавать их веб-службе, которая строит инструкцию SQL с помощью подготовленных операторов. Это безопасно для SQL-инъекций, потому что вы используете подготовленные операторы, и вы можете контролировать безопасность того, что создается в веб-службе, которую вы контролируете.

Конец Edit

Там нет ничего плохого с выполнением SQL передается от клиента. Особенно в ситуациях построения запросов.

Например, вы можете добавить так много предложений, присоединив их к «И». Однако то, что вы не должны делать, это позволить пользователю указать, что такое SQL. Вместо этого вы должны предоставить интерфейс, который позволяет вашим пользователям создавать запросы. Есть несколько причин, это выгодно:

  1. лучший пользовательский опыт
  2. Безопаснее от инъекций (кто хочет писать SQL другой, чем разработчики?). Просто вы не можете отфильтровать все опасные строки SQL.

Помимо этого, для выполнения динамического SQL, а не для использования хранимой процедуры, совершенно нормально выполнять. Ваше мнение, что everything should be written in terms of stored procedures кажется ошибочным для меня. Конечно, хранимые процедуры хороши во многих отношениях, но есть и недостатки в их использовании.

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

Одна вещь, которую вы, возможно, захотите изучить, хотя это построение SQL на стороне сервера и передача какого-либо внутреннего представления встроенного запроса. Если у вас есть какой-то веб-сервис, который открывается и позволяет вашему клиенту запускать любой SQL, который он хочет запустить, тогда у вас есть проблема с безопасностью. Это также поможет в управлении версиями. Если вы измените базу данных, вы можете изменить ее с помощью веб-службы, а не беспокоиться о том, что люди используют старые клиенты, создающие недействительный SQL.

+0

спасибо, это покрывает много того, о чем я беспокоился и очень хорошо мешает тому, что я думал делать. +1 –

+0

+1 за последние три абзаца, все находящиеся на месте. Но я не согласен с первыми двумя параграфами. Сердце никогда не может доверять клиенту, потому что нет надежного способа проверить, что клиент не был подделан или скомпрометирован. Простое перехват и изменение запроса на клиентском устройстве может обойти ограничения в пользовательском интерфейсе. Единственная безопасная альтернатива - это «передача какого-либо внутреннего представления построенного запроса», как вы выразились. Я неохотно голосую, так как случайный читатель может сэкономить, пропустить ваши предостережения в последнем абзаце и уйти с неправильной идеей. – WCWedin

+0

Передача имен таблиц и текстовых запросов никогда не является хорошей идеей, если ваше приложение имеет какие-либо защищенные данные. Вы предлагаете окно в схему. Обычно люди имеют ORM и используют номера строк для ссылки на идентификаторы, которые предотвращают вывод любого фактического идентификатора строки (строка не коррелирует с фактической строкой в ​​базе данных). Даже по-прежнему нет ничего плохого в создании большого количества хранимых процедур, просто потому, что это хранимая процедура не означает, что вы хватаете все, а вы тогда являетесь младшим программистом, и это управленческая проблема. Тренировки Dba, которые они видят, а ad hoc - кошмар dba и кодирование. –

1

Я вижу, что эта полностью настраиваемая пользователем визуализация больше похожа на строительные блоки. Я бы не передавал прямые sql-запросы в исходный код. Я хотел бы, чтобы пользователь отправлял параметры (просмотр которых использовался, фильтры в предложении where и т. Д.). Но позволяя пользователю вводить sql, это потенциальный кошмар (как для обеспечения безопасности, так и для обслуживания)

+0

вы делаете хороший момент.хотя tbh, клиент может собрать любой возможный запрос к базе данных и только прочитал приватные данные в таблице: поскольку связь с промежуточным программным обеспечением является гарантией TLS + HTTP, я не был слишком обеспокоен. хотя я, вероятно, должен обфускать ключи, чтобы они не были видны со строками ... –

0

Нет ничего плохого в том, что приложение отправляет SQL-команды в базу данных, если вы знаете проблемы с инъекциями. Поэтому не делайте этого в вашем коде:

(Pseudocode) 

String sqlCommand = "SELECT something FROM YOURTABLE WHERE A='" + aTextInputFieldInYourGui + "'"; 
cmd.execute(sqlCommand); 

Почему нет? Посмотрите, что происходит, если пользователь вводит эту строку в aTextInputFieldInYourGui
«GO DELETE * FROM YOURTABLE GO SELECT»
(предполагается, что ваш DB является MS SQL Server здесь, для других СУБД немного другой синтаксис)

Использование подготовленных заявлений и Parameterbinding вместо

(Pseudocode) 
String sqlCommand = "SELECT something FROM YOURTABLE WHERE A=?"; 
cmd.prepare(sqlCommand); 
cmd.bindParam(1, aTextInputFieldInYourGui); 
cmd.execute(); 

с уважением

Смежные вопросы