Как реализовать безопасный REST API с узлом.js


Я начинаю планировать REST API с узла.js, express и mongodb. API предоставляет данные для веб-сайта (публичной и частной области) и, возможно, позже для мобильного приложения. Интерфейс будет разработан с AngularJS.

в течение нескольких дней я много читал о защите API REST, но я не добираюсь до окончательного решения. Насколько я понимаю, это использовать HTTPS для обеспечения базовой безопасности. Но как я могу защитить API в этом случае использования:

  • только посетители/пользователи веб-сайт / приложение могут получать данные для публичной области веб-сайта/приложения

  • только аутентифицированные и авторизованные пользователи могут получать данные для частной области (и только данные, где пользователю предоставлены разрешения)

на данный момент я думаю о том, чтобы только разрешить пользователям с активным сеансом использовать API. Для авторизации пользователей я буду использовать паспорт, а для разрешения мне нужно что-то реализовать для себя. Все на вершине ПРОТОКОЛ HTTPS.

может кто-нибудь предоставить некоторые лучшие практики или опыт? Есть ли недостаток в моей "архитектуре"?

5 195

5 ответов:

У меня была та же проблема, которую вы описываете. Веб-сайт, который я создаю, можно получить с мобильного телефона и из браузера, поэтому мне нужен api, чтобы позволить пользователям регистрироваться, входить в систему и выполнять некоторые конкретные задачи. Кроме того, мне нужно поддерживать масштабируемость, один и тот же код, работающий на разных процессах/машинах.

потому что пользователи могут создавать ресурсы (ака POST/PUT действия) вам нужно защитить свой api. Вы можете использовать oauth или вы можете построить свое собственное решение, но имейте в виду, что все решения могут быть нарушены, если пароль действительно легко обнаружить. Основная идея состоит в том, чтобы аутентифицировать пользователей, используя имя пользователя, пароль и токен, он же apitoken. Этот apitoken может быть создан с помощью node-uuid и пароль может быть хэширован с помощью pbkdf2

затем вам нужно сохранить сеанс где-то. Если вы сохраните его в памяти в простом объекте, если вы убьете сервер и перезагрузите его снова, сеанс будет уничтожен. Кроме того, это не масштабируемый. Если вы используете haproxy для балансировки нагрузки между машинами или просто используете workers, это состояние сеанса будет сохранено в одном процессе, поэтому, если один и тот же пользователь перенаправлен на другой процесс/машину, ему нужно будет снова пройти проверку подлинности. Поэтому вам нужно хранить сеанс в общем месте. Обычно это делается с помощью Redis.

когда пользователь аутентифицируется (имя пользователя+пароль+apitoken), создайте другой токен для сеанса, aka accesstoken. Опять же, с узлом-uuid. Отправьте пользователю accesstoken и идентификатор пользователя. Идентификатор пользователя (ключ) и accesstoken (значение) хранятся в redis с истекшим временем, например 1h.

теперь каждый раз, когда пользователь выполняет какую-либо операцию с помощью REST api, ему нужно будет отправить идентификатор пользователя и accesstoken.

Если вы разрешите пользователям регистрироваться с помощью REST api, вам нужно будет создать учетную запись администратора с администратором apitoken и сохранить их в мобильном приложении (зашифровать имя пользователя+пароль+apitoken), потому что новые пользователи не будет апитокена, когда они зарегистрируются.

веб также использует этот api, но вам не нужно использовать apitokens. Вы можете использовать express с хранилищем redis или использовать тот же метод, описанный выше, но минуя проверку apitoken и возвращая пользователю идентификатор пользователя+accesstoken в файле cookie.

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

резюме:

sequence diagram

альтернативой без apitoken было бы использовать HTTPS и отправлять имя пользователя и пароль в заголовке авторизации и кэшировать имя пользователя в redis.

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

// ------------------------------------------------------
// server.js 

// .......................................................
// requires
var fs = require('fs');
var express = require('express'); 
var myBusinessLogic = require('../businessLogic/businessLogic.js');

// .......................................................
// security options

/*
1. Generate a self-signed certificate-key pair
openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout key.pem -out certificate.pem

2. Import them to a keystore (some programs use a keystore)
keytool -importcert -file certificate.pem -keystore my.keystore
*/

var securityOptions = {
    key: fs.readFileSync('key.pem'),
    cert: fs.readFileSync('certificate.pem'),
    requestCert: true
};

// .......................................................
// create the secure server (HTTPS)

var app = express();
var secureServer = require('https').createServer(securityOptions, app);

// ------------------------------------------------------
// helper functions for auth

// .............................................
// true if req == GET /login 

function isGETLogin (req) {
    if (req.path != "/login") { return false; }
    if ( req.method != "GET" ) { return false; }
    return true;
} // ()

// .............................................
// your auth policy  here:
// true if req does have permissions
// (you may check here permissions and roles 
//  allowed to access the REST action depending
//  on the URI being accessed)

function reqHasPermission (req) {
    // decode req.accessToken, extract 
    // supposed fields there: userId:roleId:expiryTime
    // and check them

    // for the moment we do a very rigorous check
    if (req.headers.accessToken != "you-are-welcome") {
        return false;
    }
    return true;
} // ()

// ------------------------------------------------------
// install a function to transparently perform the auth check
// of incoming request, BEFORE they are actually invoked

app.use (function(req, res, next) {
    if (! isGETLogin (req) ) {
        if (! reqHasPermission (req) ){
            res.writeHead(401);  // unauthorized
            res.end();
            return; // don't call next()
        }
    } else {
        console.log (" * is a login request ");
    }
    next(); // continue processing the request
});

// ------------------------------------------------------
// copy everything in the req body to req.body

app.use (function(req, res, next) {
    var data='';
    req.setEncoding('utf8');
    req.on('data', function(chunk) { 
       data += chunk;
    });
    req.on('end', function() {
        req.body = data;
        next(); 
    });
});

// ------------------------------------------------------
// REST requests
// ------------------------------------------------------

// .......................................................
// authenticating method
// GET /login?user=xxx&password=yyy

app.get('/login', function(req, res){
    var user = req.query.user;
    var password = req.query.password;

    // rigorous auth check of user-passwrod
    if (user != "foobar" || password != "1234") {
        res.writeHead(403);  // forbidden
    } else {
        // OK: create an access token with fields user, role and expiry time, hash it
        // and put it on a response header field
        res.setHeader ('accessToken', "you-are-welcome");
        res.writeHead(200); 
    }
    res.end();
});

// .......................................................
// "regular" methods (just an example)
// newBook()
// PUT /book

app.put('/book', function (req,res){
    var bookData = JSON.parse (req.body);

    myBusinessLogic.newBook(bookData, function (err) {
        if (err) {
            res.writeHead(409);
            res.end();
            return;
        }
        // no error:
        res.writeHead(200);
        res.end();
    });
});

// .......................................................
// "main()"

secureServer.listen (8081);

этот сервер можно протестировать с помощью curl:

echo "----   first: do login "
curl -v "https://localhost:8081/login?user=foobar&password=1234" --cacert certificate.pem

# now, in a real case, you should copy the accessToken received before, in the following request

echo "----  new book"
curl -X POST  -d '{"id": "12341324", "author": "Herman Melville", "title": "Moby-Dick"}' "https://localhost:8081/book" --cacert certificate.pem --header "accessToken: you-are-welcome" 

Я просто законченный пример приложения, которое делает это в довольно простой, но ясный путь. Он использует Мангуст с mongodb для хранения пользователей и паспорта для управления auth.

https://github.com/Khelldar/Angular-Express-Train-Seed

есть много вопросов по поводу отдыха двиг вот так. Они наиболее актуальны для вашего вопроса:

в основном вам нужно выбрать между использованием ключей API (наименее безопасный, поскольку ключ может быть обнаружен несанкционированным пользователем), ключом приложения и комбинацией токенов (средний) или полной реализацией OAuth (наиболее безопасный).

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

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