Комментарии 16
Хотелось бы все же примеры различных стратегий борьбы с N+1 проблемами.
И хоть в комментариях к предыдущей статье уже не раз говорилось о том, что безопасность вашего приложения находится в ваших руках, а не руках GraphQL, я все же покажу пару простых способов обезопасить свое приложение при использовании graphql-php.
Смотрите, обычно у нас есть роли, типа — админ, редактор, зарегистрированный юзер, анонимус.
Конечно мы им, обычно, даём разные урлы (в вашем случае типа localhost/graphql.php )
Это мы на уровни роли разрулим у себя на сервере нашего приложения.
Но, обычно мы знаем (через специфические URI запроса) — что посылать обратно клиенту.
В случае GraphQL — мы имеем большую возможность, что юзер с ролью пришлёт такой запрос на который (на часть которого) у него нет разрешения (нет политики).
К примеру, юзер с ролью анонимус, запрашивая имя, к примеру, не имеет права видеть номер телефон в своём ответе, хотя в своём GraphQL-запросе он это потребовал (указал)!
Вот как разграничить это удобно? И желательно на стадии когда запрос, пришедший на GraphQL сервер для обработки, ещё не выполнился.
Напрашивается, что в этом случае было бы здорово как-то «навешать роли» на схему GraphQL?
Иначе при обработки запроса потребуется через типа if проверять — а имеет ли право эта роль требовать чтобы ей в ответе прислали номер телефона?
Вот о какой безопасно идёт речь, в случае GraphQL. Имхо.
Вот о какой безопасно идёт речь, в случае GraphQL. Имхо.
Просто «в комментариях к предыдущей статье» в основном спрашивали про тяжелые запросы и nested attacks.
Что касается вашего вопроса:
Вот как разграничить это удобно?
Я пожалуй отвечу так: А как вы это делаете сейчас (без GraphQL)?
Конечно мы им, обычно, даём разные урлы
Ну вообще-то урлам (а точнее ресурсам) дела нет до ваших ролей. Так что плохая идея.
Вот как разграничить это удобно?
пользователь запросил несколько филдов. К некоторым из них у него нет доступа. Стало быть для этих филдов мы ничего не возвращаем и добавляем информацию об ошибках.
было бы здорово как-то «навешать роли» на схему GraphQL?
это будет работать только в самых примитивных случаях. Как правило все намного сложнее. В документации к graphql есть вполне себе неплохой момент описывающий основную суть проблемы: Делегируй авторизацию действий слою с бизнес логикой. То есть в ресолвере мы лишь запрашиваем данные, а чуть что — ловим исключение и добавляем в список ошибок для полей.
Иначе при обработки запроса потребуется через типа if проверять — а имеет ли право эта роль требовать чтобы ей в ответе прислали номер телефона?
Давайте так, вот вам бизнес правило: email пользователя можно показывать только самому пользователю, администратору, модератору, друзьям пользователя. Тут уже просто ролями не отделаешься и вам придется делать if
. А так как это "бизнес" правило — его надо проверять на уровне бизнес логики а не во "вьюхе" (а именно вьюхой является резолверы в graphql).
Вот о какой безопасно идёт речь, в случае GraphQL. Имхо.
Graphql = view layer. Authorization = business layer. Я думаю идея понятна.
Я пожалуй отвечу так: А как вы это делаете сейчас (без GraphQL)?Очень по разному.
Начиная от доступа к URI по ролям, использование ролей при выводе результатов (уже после основного запроса/запросов) и заканчивая — ставим роль на конкретный метод сервиса (чтоб уж наверняка не «пробило»).
Но дело в том, что мы сейчас знаем — что выдать клиенту — ибо фактически не он, а мы управляем запросом.
А вот GraphQL — тут первую скрипку ведёт клиент. И в этом то и отличие.
Fesor
пользователь запросил несколько филдов. К некоторым из них у него нет доступа. Стало быть для этих филдов мы ничего не возвращаем и добавляем информацию об ошибках.Не факт.
Нам надо как-то понять, что к этой инфе у него нет доступа (например на основании роли — но это надо
где-то прописать).
Вернуть ему можно и null и без ошибки. Или ничего не вернуть и ошибку — но это нюансы реализации, я думаю.
Fesor
это будет работать только в самых примитивных случаях. Как правило все намного сложнее. В документации к graphql есть вполне себе неплохой момент описывающий основную суть проблемы: Делегируй авторизацию действий слою с бизнес логикой. То есть в ресолвере мы лишь запрашиваем данные, а чуть что — ловим исключение и добавляем в список ошибок для полей.Да, автор пишет — «It is tempting to place authorization logic in the GraphQL layer like so:» И действительно заманчиво туда и помещать — в схему GraphQL.
Автор просто «выбрасывает» проверку роли во вне. Но почему? — Тогда нам придётся во вне строить дерево доступа по ролям.
Не просто «заманчиво» — а нужно помещать роли в в схему GraphQL.
Фактически обработка запроса от клиента должна быть типа такой:
- дерево-запроса-0 — исходное дерево запроса от клиента.
- 1) функция от дерева-запроса-0 -> проверка дерева-запроса-0 на корректность и выдача ошибки (или null в ветке). На выходе дерево-запроса-1
- 2) функция от дерева-запроса-1 -> проверка дерева-запроса-1 на допустимость выдачи (в зависимости от роли клиента) и выдача ошибки (или null в ветке где недопустимо). На выходе дерево-запроса-2
- 3) функция от дерева-запроса-2 -> выполнение запроса и выдача ошибки (если что-то пошло не так с базой...). На выходе дерево-ответа.
- Отправка клиенту дерево-ответа.
Fesor
Давайте так, вот вам бизнес правило: email пользователя можно показывать только самому пользователю, администратору, модератору, друзьям пользователя. Тут уже просто ролями не отделаешься и вам придется делать if.
Верно. Идеально чтобы там не просто задать список ролей, а был примитивный язык — как он сейчас во многих фреймворках и есть.
Понимаете, на практике, при отсутствие просто настроить доступ по ролям, будет либо распределение URI по ролям (что грубо), либо, если это публичный сервис — типа Google-map — который ничего не скрывает и роли ему до фонаря.
Имхо, конечно, имхо.
Иметь развесистое дерево (схема GraphQL) где каждую ветку, каждый лист было бы просто настроить по ролям — это идеал.
Начиная от доступа к URI по ролям, использование ролей при выводе результатов (уже после основного запроса/запросов) и заканчивая — ставим роль на конкретный метод сервиса (чтоб уж наверняка не «пробило»).
Вот точно так же и с GraphQL. )
Но дело в том, что мы сейчас знаем — что выдать клиенту — ибо фактически не он, а мы управляем запросом.
А вот GraphQL — тут первую скрипку ведёт клиент. И в этом то и отличие.
Клиент может выбирать какие данные запросить из тех, которые ему доступны и задавать их структуру. Вам же остается только ограничить те данные, которые доступны клиенту (как вы это и делаете сейчас) и пусть балуется.
А вот GraphQL — тут первую скрипку ведёт клиент. И в этом то и отличие.
на самом деле отличие незначительное. Просто по умолчанию мы не возвращаем ничего, и далее уже исходя из того что запросил клиент. Мы все еще контролируем схему. А знание о том что юзается дает больше контроля а не меньше.
Автор просто «выбрасывает» проверку роли во вне. Но почему? — Тогда нам придётся во вне строить дерево доступа по ролям.
Потому что проверка прав не является обязанностью view layer.
Не просто «заманчиво» — а нужно помещать роли в в схему GraphQL.
это как помещать бизнес правила в шаблоны. Вы же так не делаете?
Фактически обработка запроса от клиента должна быть типа такой:
как-то так и происходит, просто все эти решения делегируются вашему коду.
Верно. Идеально чтобы там не просто задать список ролей, а был примитивный язык — как он сейчас во многих фреймворках и есть.
Зачем вам примитивный язык если можно написать вполне себе нормальным кодом?
Понимаете, на практике, при отсутствие просто настроить доступ по ролям, будет либо
на практике все решения в духе "можно или нельзя" делегируются отдельным объектам которые за это отвечают.
где каждую ветку, каждый лист было бы просто настроить по ролям — это идеал.
вам никто не мешает сделать простенький декоратор/ивент листенер для ресорверов каждой ноды дерева. Собственно именно так это делается "во многих фреймворках". Просто это не входит в обязанности graphql, но вас никто не останавливает.
Клиент может выбирать какие данные запросить из тех, которые ему доступны и задавать их структуру. Вам же остается только ограничить те данные, которые доступны клиенту (как вы это и делаете сейчас) и пусть балуется.Сейчас мне проще это сделать ибо у меня множество URI (точек входа — «дверей») которым я могу сопоставить роли.
В случае GraphQL у меня будет фактически одно URI — "одно окно" — и возникает вопрос — где мне прописывать ограничения?
Fesor
Зачем вам примитивный язык если можно написать вполне себе нормальным кодом?Примитивные языки появляются не потому что нельзя писать «нормальным кодом» — а потому что писать в этом случае «нормальным кодом» неудобно из-за boilerplate code.
Fesor
вам никто не мешает сделать простенький декоратор/ивент листенер для ресорверов каждой ноды дерева. Собственно именно так это делается «во многих фреймворках». Просто это не входит в обязанности graphql, но вас никто не останавливает.
Из
![image](https://habrastorage.org/getpro/habr/comment_images/9e1/8d7/97a/9e18d797aa9f6acb60fea2431adbc91f.png)
Хорошо видно куда они задвинули авторизацию. Но если принято решание использовать только и только graphql — то возникает смысл авторизацию двинуть в описание схемы.
Иначе придётся «городить» что-то вроде строк (где-то рядом в файле настройке доступа):
user / name: all
user / {name, telephon}: admin
Фактически превращая ветки и листья дерева ответа в линейные строки — и это только для того чтобы настроить доступ то!
Вся эта настройка просто просится разместиться в GraphQL-схеме — ей там самое место.
Иначе будет не айс.
Примитивные языки появляются не потому что нельзя писать «нормальным кодом» — а потому что писать в этом случае «нормальным кодом» неудобно из-за boilerplate code.
давайте на конкретных примерах. Приведите мне удобный для вас DSL описания прав а дальше будем рассматривать "чем он плох".
Хорошо видно куда они задвинули авторизацию.
я вам больше скажу, ее и в других вариантах (http api, rpc) задвинули тудаже и правильно сделали. однако вас никто не останавливает ее оттуда вытащить и сделать что-то типа мидлвари.
Но если принято решание использовать только и только graphql — то возникает смысл авторизацию двинуть в описание схемы.
role-based авторизация удобна только для простых случаев. Даже в случае с админкой у меня есть проекты где все вроде и админы но права у всех разные. Ну либо мне надо генерить еще и пермутации маленьких "ролей".
Вся эта настройка просто просится разместиться в GraphQL-схеме — ей там самое место.
и бизнес логику тоже так и просится в контроллерах писать. Но самодисциплина это хорошо.
Сейчас мне проще это сделать ибо у меня множество URI (точек входа — «дверей») которым я могу сопоставить роли.
В случае GraphQL у меня будет фактически одно URI — «одно окно» — и возникает вопрос — где мне прописывать ограничения?
Да ведь можно же ограничить сам список полей в схеме. Например, если роль «Авторизованный пользователь», то он может запрашивать поле «messages», а если «Гость», то нет (возвращать null или выводить ошибку).
И в конце концов, если вам хочется, можно сделать несколько endpoint`ов (URI/точек входа/дверей/окон) с разными схемами данных и сопоставлять им соответствующие роли.
давайте на конкретных примерах. Приведите мне удобный для вас DSL описания прав а дальше будем рассматривать «чем он плох».Не будем. Язык называется SpEL и он на своём месте.
XAHTEP26
И в конце концов, если вам хочется, можно сделать несколько endpoint`ов (URI/точек входа/дверей/окон) с разными схемами данных и сопоставлять им соответствующие роли.Нет. Понимаете GraphQL — это всё равно «одно окно». Сама логика GraphQL стремится к «одно окно».
Сейчас у меня всё просто. Есть список URI — он двухмерный:
Сверху вниз:
<sec:intercept-url access="IS_AUTHENTICATED_ANONYMOUSLY" pattern="/home.html"/>
<sec:intercept-url access="IS_AUTHENTICATED_ANONYMOUSLY" pattern="/search.html"/>
...
<sec:intercept-url access="ROLE_PERSON_VIEWER" pattern="/person-edit.html"/>
<sec:intercept-url access="ROLE_PERSON_VIEWER,ROLE_PERSON_EDITOR"/> pattern="/delete-photos.json"
...
То есть сверху строки — всем разрешено, а потом идёт снижение доступа до определённых ролей. — Чем ниже строка тем больший приоритет она имеет.
Этого мне хватает. — Если нужно, я использую проверку в контроллере или на выходе (на JSP — там в виде тега, но это неважно)
В случае же GraphQL, который логикой своей влечёт к «одному окну» — такой «фокус не проходит. И если как-то его реализовать — то будет чужеродным.
В случае GraphQL роли надо вставлять в саму схему GraphQL. Иначе будет чужеродно.
То есть на данном этапе GraphQL можно использовать только и только как public выдача — где скрывать или ограничивать нечего — что клиент запросил то и получил. Типа Google map и т.п.
В ином случае использовать GraphQL будет проблематично и даже очень проблематично.
Имхо.
Не будем. Язык называется SpEL и он на своём месте.
Неплохой DSL. Реально удобный, по сравнению с java. Но тут есть нюансы:
- его надо изучать. В этом собственно вся проблема spring integration. Невероятно мощная штука которая заставляет тебя не код писать а тонны конфигов.
- если мы говорим не про Java а скажем про JavaScript/TypeScript, все это можно сделать лаконичнее. Да еще и с композицией. Потому все же когда мы говорим про "удобно" стоит все же думать о контексте.
Если нужно, я использую проверку в контроллере или на выходе (на JSP — там в виде тега, но это неважно)
Но сами бизнес правила вы же делегируете неким другим объектам? Не в контроллерах же у вас бизнес правила? Точно так же и в graphql. Проверку вы делаете в ресолверах, но сама логика делегируется куда-то ниже по дереву зависимостей.
такой «фокус не проходит. И если как-то его реализовать — то будет чужеродным.
Почему? Почему не вынести проверки на уровень декораторов над ресолверами? Это жа намного удобнее.
Делаем GraphQL API на PHP и MySQL. Часть 2: Мутации, переменные, валидация и безопасность