Подтвердить что ты не робот

Layer-Design: где проверить разрешения для чтения/обновления базы данных?

В большинстве сценариев вы хотите, чтобы пользователь мог иметь доступ к объектам в базе данных, созданной самим пользователем. Например, если есть календарь, который был создан User1, то только пользователь1 должен иметь возможность читать, обновлять или удалять этот конкретный календарь и его содержимое в базе данных. Речь идет не о авторизации вообще - в моем проекте уже есть компонент авторизации на основе ролей, который проверяет, принадлежат ли пользователи роли "calendar-editor", но не проверяет, разрешен ли конкретному пользователю доступ к конкретный календарь.

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

  • Я мог бы сделать это на уровне DAO. Но тогда каждому DAO-методу нужен дополнительный параметр, который представляет идентификатор пользователя, который делает эти методы более подробными и уменьшает повторное использование.

    например.

    def findCalById(id: Int): Future[Option[Calendar]]

    становится

    def findCalById(id: Int, ownerId: Int ): Future[Option[Calendar]]

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

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

Возможно, есть дополнительные варианты, которые я даже не рассматривал. Есть ли лучшие методы?

Btw: в моем веб-приложении нет календарей, это просто пример, иллюстрирующий проблему.

4b9b3361

Ответ 1

Я думаю, ключевым моментом является думать о том, что именно вы имеете в виду, когда говорите, что подход DAO "уменьшает повторное использование". Если ваше требование обеспечить права доступа пользователей является универсальным для всех приложений вашего DAO, то выполнение этого на уровне DAO фактически увеличивает возможность повторного использования, а не уменьшает его: все, кто использует DAO, смогут извлечь выгоду из этих проверок, а не выполнять их самостоятельно.

Вы можете сделать идентификатор пользователя неявным параметром, чтобы сделать эти методы более дружественными для пользователя вверх по течению. Вы также можете заставить его возвратить Try (или, возможно, Either), чтобы решить вашу проблему с различием между отсутствующими и недоступными объектами:

case class UserId(id: Int)
def findCalById(id: Int)(implicit user: UserId): Future[Try[Option[Calendar]]] = ???

Затем вызывающий может сделать что-то вроде этого:

implicit val currentUser = UserId(request.getUserId)
dao.findCalById(request.getCalendarId).map { 
    case Failure(IllegalAccessException()) => "You are not allowed to use this calendar"
    case Return(None) => "Calendar not found"
    case Return(Some(cal)) => calendarToString(cal)
}

С другой стороны, если есть возможные случаи, когда DAO будет использоваться без контекста пользователя (возможно, "приложение администратора" ), вы можете рассмотреть либо подклассирование, чтобы обеспечить управление доступом к вашим "обычным приложениям", или, возможно, просто сделать дополнительную роль, которая позволила бы пользователю получить доступ ко всем календарям в отношении владения, а затем использовать этот "суперпользователь" в вашем приложении администратора.

Я бы не стал беспокоиться о стоимости загрузки объекта перед проверкой доступа (даже если объект действительно дорого загружается, должно быть довольно редкое явление, когда кто-то пытается получить доступ к объекту, которого он не имеет), Я считаю, что более сильный аргумент в пользу подхода уровня обслуживания - это точно повторное использование и модульность кода: само существование класса DAO с открытым интерфейсом предполагает, что он может, по крайней мере, потенциально повторно использоваться более чем одним компонентом. Кажется глупым требовать, чтобы все такие компоненты выполняли свои собственные проверки доступа, особенно, учитывая, что такой контракт не может быть осуществлен: нет способа убедиться, что кто-то, кто решает использовать ваш класс DAO через пару лет, будет помнить об этом требовании (или прочитать комментарий). Если вы все равно создаете слой для доступа к базе данных, вы также можете сделать его полезным для чего-то.

Ответ 2

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

Некоторые причины включают:

  • Чище иметь все проверки/проверки в одном месте. Код имеет структуру. Будут случаи, когда некоторые проверки не могут быть выполнены на уровне DAO. И тогда он становится ad-hoc, каковы проверки на уровень обслуживания и что на уровне DAO.

  • Метод findCalById должен возвращать только Calendar по id с использованием идентификатора. Он более многоразовый. Что, если завтра вам понадобится функциональность, которую администратор может видеть во всех календарях независимо от владельца. В конце вы напишите еще один запрос для этой функции. Будет проще добавить эту проверку в сервисный уровень.

  • Предполагая, что когда-нибудь у вас будет другое хранилище данных, которое вернет запись, вы получите подтверждение в нескольких местах. Этого не произойдет, если для проверки правильности есть уровень обслуживания. Уровень обслуживания не изменится, так как он не заботится о том, где идет запись.

  • Борьба с новым коллегой становится проще. Предполагая, что новый парень, специализирующийся на домене DB, начнет работать с вами. Он будет более продуктивным, если его беспокоят только записи, которые БД должна вернуть, забывая о том, как приложение использует эти данные. (разделение беспокойства распространяется и на реальную жизнь:)).

Ответ 3

Здесь вы задали очень интересный вопрос. Как уже подчеркивали другие, правильный подход зависит от значения вашего вопроса.

как различать календарь, который не существует, и существующий календарь, к которому текущий пользователь не может получить доступ?

Когда вы начинаете думать о внедрении логики фильтрации в DAO и в сервисах, вы фактически начинаете решать проблему с несколькими арендаторами, которая, вероятно, уже уже решена. Это звучит как многопользовательская аренда, потому что вы думаете, как изолировать данные между разными пользователями, использующими одно и то же приложение. Проблема сужается, так как вы хотите иметь общую базу данных - что данный (не может быть изменен). Вы также отмечаете, что данные не всегда изолированы (администратор может видеть, что все остальные видят в их изоляции). Если это так, то место реализации фактически не имеет значения, и для каждого варианта использования у вас может быть другой выбор - в зависимости от того, что имеет смысл для конкретного случая использования. Было бы важно, если бы у вас не было смешанных случаев использования, но у вас есть (admin vs regular user). Таким образом, ваш многопользовательский процесс не настолько сложный - он просто основан на использовании.

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

Вот как я вижу это для нескольких случаев использования, которые могут существовать в вашем приложении:

  • Пользователь аутентифицируется, который определяет права доступа для календарей для каждого пользователя, прошедшего проверку подлинности (он может быть либо "Просмотр только пользовательских календарей", либо может быть "Просмотр всех календарей" ).
  • Пользователь открывает экран для просмотра только пользовательских календарей - отображаются пользовательские календари. Если вы считаете, что администратор должен также использовать тот же самый экран, то вы можете либо сопоставить другую службу для администратора, либо вызвать другой метод DAO для администратора, либо передать разные параметры DAO для администратора.
  • Пользователь обращается к экрану для просмотра всех календарей (ну, эта страница, вероятно, защищена только для доступа администратора). Затем отобразите все календари. Если вы считаете, что обычный пользователь должен также использовать тот же самый экран, то вы можете либо сопоставить другую услугу для пользователя, либо вызвать другой метод DAO для пользователя, либо передать различные параметры DAO для пользователя.

Ответ 4

Фильтрация результатов на уровне DAO - хороший подход по нескольким причинам:

  • вы сохраняете ресурсы на сервере приложений.
  • фильтрация на уровне базы данных быстрее
  • фильтрация как можно скорее уменьшает риск ошибок в более высоких слоях приложений

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

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

Ответ 5

Фильтрация на Уровне доступа к данным будет моим выбором. Нормальный, я разделяю свой доступ к базе данных в отдельной библиотеке классов под названием DAL. Внутри этой библиотеки я определяю интерфейс с методами, возвращающими данные. Когда вы создаете экземпляр этого интерфейса, будет создан конструктор, который будет иметь пользовательский параметр. Таким образом, интерфейс будет фильтровать данные для вас без необходимости передавать информацию о пользователе по каждому методу.

public class DatabaseInterface {
    private UserIdentity UserInfo;
    private Database Data;

    public DatabaseInterface(UserIdentity user) {
        UserInfo = user;
        Data = new Database();
    }

    public List<cal> findCalById(int id) {
        return Data.cal.Where(x => x.user == this.UserInfo && x.id == id).ToList();
    }
}

Использование интерфейса

var dal = new DatabaseInterface(user);
var myData = dal.findCalById(1);

Ответ 6

Фильтрация результата в слое DAO предпочтительна для меня.

В качестве способа сокращения списка параметров, поскольку календарь извлекается с точки зрения владельца, нет необходимости передавать идентификатор календаря. Вместо того, чтобы делать: def findCalById(id: Int, ownerId: Int ): Future[Option[Calendar]], я сделаю: def findCal(ownerId: Int): Future[Option[Calendar]].

Относительно:

Как провести различие между календарем, который не существует, и существующим календарем, который не может быть доступен текущему пользователю?

С помощью метода def findCal(ownerId: Int): Future[Option[Calendar]] вам даже не нужно различать два случая. Поскольку с точки зрения пользователя/владельца DAO просто нужно вернуть календарь, если он присутствует.