ACL на основе ресурсов Node.js - PullRequest
2 голосов
/ 27 марта 2019

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

Я использую Node ACL , и этоМне не понятно, как блокировать для каждого ресурса.

Давайте рассмотрим следующий пример: USER ->* PROJECT ->* ENTRY.Пользователи могут иметь несколько проектов, которые содержат много записей.Пользователями могут быть ADMIN или USER.

. Я создал конечную точку /entry/{ID}, где пользователь может получить доступ к деталям записи.Конечная точка доступна каждому, ADMIN s может видеть все записи, но для User мне нужно сделать что-то похожее:

app.get('/entry/{id}', (req, res) => {
    if (user.admin) {
        // Return eveything
    }
    else {
       if (entry.project == user.project) {
           // return it
       }
       else {
           // Unathorized
       }
    }
})

Есть ли лучший подход / шаблон для реализации этой проверки владенияна ресурсе?

1 Ответ

0 голосов
/ 08 апреля 2019

Это очень широкий вопрос, поэтому я постараюсь дать вам пару советов в качестве ответа, но

Есть ли в javascript шаблон ACL?

Есть несколько решений, но я бы не назвал ни одно из них шаблоном. Сейчас я буду очень субъективен, но способы passport.js и подобных модулей, по меньшей мере, непрозрачны - и это не совсем ACL ...

Кто-то может сказать - эй, это node.js, должен быть модуль, чтобы сделать это и сделать ваши node_modules более тяжелыми, но в поисках хорошего модуля acl в npm , я нашел только некоторые устаревшие и сильно связано с экспресс. Так как ваш вопрос не был which is the best npm module for acl, я перестал искать его на странице 3, что не означает, что там что-то не готово, поэтому вы можете захотеть присмотреться.

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

Отделите логику вашего запроса от логики управления доступом

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

app.all('/entry/{id}', (req, res, next) => {
    const {user, entry} = extractFromRequest(req);
    if (user.admin || entry.project === user.project) {
        next();
    } else {
        res.status(403).send("Forbidden");
    }
});

app.get('/entry/{id}', (req, res) => {
    // simply respond here
})

Таким образом, первый обратный вызов проверяет, есть ли у пользователя доступ, и это не повлияет на логику ответа. Использование next() специфично для экспресс-подобных фреймворков, которые, как я предполагал, вы используете, просматривая свой код - при его вызове будет выполнен следующий обработчик, иначе никакие другие обработчики не будут запущены.

См. Express.js app.all документацию для примера acl .

Используйте сервис широкий acl

Гораздо безопаснее хранить базовый ACL в одном месте и не определять его для каждого пути без необходимости. Таким образом, вы не пропустите один путь и не оставите дыру в безопасности где-то в середине запроса. Для этого нам нужно разбить ACL на части:

  • Проверка доступа к URL (если путь общедоступен / открыт для всех пользователей)
  • Проверка подлинности пользователя и сеанса (пользователь вошел в систему, сеанс не истек)
  • Проверка администратором / пользователем (таким образом, уровень разрешений)
  • В противном случае мы ничего не допустим.

    app.all ('*', (req, res, next) => { if (path.isPublic) next (); // публичные пути можно разблокировать иначе if (user.valid && user.expires> Date.now ()) next (); // сеанс и пользователь должны быть действительными иначе if (user.admin) next (); // админ может пойти куда угодно иначе if (path.isOpen && user.valid) next (); // также могут пройти пути для зарегистрированных пользователей иначе выведите новую ошибку («Запрещено»); });

Эта проверка не очень ограничительна, но нам не нужно повторяться. Также обратите внимание на ошибку throw внизу - мы обработаем это в обработчике ошибок:

app.use(function (err, req, res, next) {
    if (err.message === "Forbidden") res.status(403).send("Forbidden");
    else res.status(500).send("Something broke");
})

Express.js будет считать любой обработчик с 4 аргументами обработчиком ошибок.

На определенном уровне пути, если есть необходимость в ACL, просто выдать ошибку обработчику:

app.all('/entry/{id}', (req, res, next) => {
    if (!user.admin && user.project !== entry.project) throw new Error("Forbidden");
    // then respond...
});

Что напоминает мне еще один намек ...

Не использовать user.admin

Хорошо, хорошо, используйте его, если хотите. Я не. Первой попыткой взломать ваш код будет попытка установить admin для любого объекта, который имеет свойства. Это обычное имя в общей проверке безопасности, так что это похоже на то, чтобы оставить логин WiFI AP по умолчанию.

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

Это просто целая статья об этом, так что вот немного дальнейшего прочтения ACL на основе ролей .

Использовать стандартные ответы HTTP

Некоторое из этого упомянуто выше, но рекомендуется просто использовать в качестве ответа одно из стандартных состояний HTTP-кода 4xx - это будет иметь значение для клиента. В сущности, ответьте 401, когда пользователь не вошел в систему (или сеанс истек), 403, если нет достаточных привилегий, 429, если пределы использования превышены. больше кодов и что делать, когда запрос в чайнике в Википедии .

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

class AuthError extends Error {
    constructor(status, message = "Access denied") {
        super(message);
        this.status = status;
    }
}

Действительно легко обрабатывать и выдавать такую ​​ошибку в коде, например:

app.all('*', (req, res, next) => {
    // check if all good, but be more talkative otherwise
    if (!path.isOpen && !user.valid) throw new AuthError(401, "Unauthenticated");
    throw new AuthError(403);
});

function checkRoles(user, entry) {
    // do some checks or...
    throw new AuthError(403, "Insufficient Priviledges");
}

app.get('/entry/{id}', (req, res) => {
    checkRoles(user, entry); // throws AuthError
    // or respond...
})

И в своем обработчике ошибок вы отправляете свой статус / сообщение, как поймано из вашего кода:

app.use(function (err, req, res, next) {
    if (err instanceof AuthError) res.send(err.status).send(err.message);
    else res.status(500).send('Something broke!')
})

Не отвечайте немедленно

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

app.use(function (err, req, res, next) {
    // some errors from the app can be handled here - you can respond immediately if
    // you think it's better.
    if (err instanceof AppError) return res.send(err.status).send(err.message);
    setTimeout(() => {
        if (err instanceof AuthError) res.send(err.status).send(err.message);
        else res.status(500).send('Something broke!')
    }, 3000);
})

Фу ... Не думаю, что этот список является исчерпывающим, но, на мой взгляд, это разумное начало.

...