Jak zaimplementować bezpieczne REST API z węzłem.js

Zaczynam planować REST API z node.js, express i mongodb. API dostarcza dane dla strony internetowej (obszaru publicznego i prywatnego), a może później aplikacji mobilnej. Frontend zostanie opracowany za pomocą AngularJS.

Od kilku dni dużo czytam o zabezpieczeniu REST API, ale nie dochodzę do ostatecznego rozwiązania. O ile rozumiem, należy użyć HTTPS, aby zapewnić podstawowe bezpieczeństwo. Ale jak mogę chronić API w takich przypadkach użycia:

  • Tylko odwiedzający / użytkownicy strony / aplikacji są pozwala uzyskać dane do obszaru publicznego witryny / aplikacji

  • Tylko uwierzytelnieni i upoważnieni użytkownicy mogą uzyskać dane dla obszaru prywatnego (i tylko dane, w których użytkownik przyznał uprawnienia)

W tej chwili myślę o umożliwieniu korzystania z API tylko użytkownikom z aktywną sesją. Aby autoryzować użytkowników użyję passport i za pozwolenie muszę wdrożyć coś dla siebie. Wszystko na górze HTTPS.

Czy ktos moze dostarczyc jakies najlepsze praktyka czy doświadczenie? Czy brakuje w mojej "architekturze"?

Author: laggingreflex, 2013-03-19

5 answers

Miałem ten sam problem, który opisałeś. Strona internetowa, którą buduję, może być dostępna z telefonu komórkowego i przeglądarki, więc potrzebuję api, aby umożliwić użytkownikom rejestrację, logowanie i wykonywanie określonych zadań. Co więcej, muszę wspierać skalowalność, ten sam kod działający na różnych procesach / maszynach.

Ponieważ użytkownicy mogą tworzyć zasoby (aka akcje POST/PUT), musisz zabezpieczyć swoje api. Możesz użyć oauth lub zbudować własne rozwiązanie, ale pamiętaj, że wszystkie rozwiązania można złamać, jeśli hasło jest naprawdę łatwe do odkrycia. Podstawową ideą jest uwierzytelnianie użytkowników za pomocą nazwy użytkownika, hasła i tokena, aka apitoken. Ten apitoken może być wygenerowany za pomocą node-uuid , A hasło może być zahaszowane za pomocą pbkdf2

Następnie musisz gdzieś zapisać sesję. Jeśli zapiszesz go w pamięci w zwykłym obiekcie, jeśli zabijesz serwer i ponownie go uruchomisz, sesja zostanie zniszczona. Ponadto nie jest to skalowalne. Stosowanie leku haproxy balans obciążenia między maszynami lub jeśli po prostu używasz workerów, ten stan sesji będzie przechowywany w jednym procesie, więc jeśli ten sam użytkownik zostanie przekierowany do innego procesu / maszyny, będzie musiał ponownie uwierzytelnić. Dlatego musisz przechowywać sesję we wspólnym miejscu. Zazwyczaj odbywa się to za pomocą redis.

Gdy użytkownik jest uwierzytelniony (username+password+apitoken) generuje kolejny token dla sesji, aka accesstoken. Ponownie z node-uuid. Wyślij do użytkownika accesstoken i userid. Userid (klucz) i accesstoken (wartość) są przechowywane w redis z czasem wygaśnięcia, np. 1h.

Teraz, za każdym razem, gdy użytkownik wykonuje jakąkolwiek operację używając rest api, będzie musiał wysłać userid i accesstoken.

Jeśli pozwolisz użytkownikom na rejestrację za pomocą interfejsu API rest, musisz utworzyć konto administratora z apitoken administratora i przechowywać je w aplikacji mobilnej (Szyfruj nazwę użytkownika+hasło+apitoken), ponieważ nowi użytkownicy nie będą mieli apitoken podczas podpisywania w górę.

Web również używa tego api, ale nie musisz używać apitokens. Możesz użyć express ze sklepem redis lub użyć tej samej techniki opisanej powyżej, ale omijając czek apitoken i zwracając użytkownikowi identyfikator userid+accesstoken w pliku cookie.

Jeśli masz obszary prywatne, porównaj nazwę Użytkownika z dozwolonymi użytkownikami podczas uwierzytelniania. Możesz również zastosować role do użytkowników.

Podsumowanie:

schemat sekwencji

Alternatywą bez apitokena byłoby użycie HTTPS i aby wysłać nazwę użytkownika i hasło w nagłówku autoryzacji i buforować nazwę użytkownika w redis.

 170
Author: Gabriel Llamas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-25 13:43:16

Chciałbym przedstawić ten kodeks jako rozwiązanie strukturalne stawianego pytania, zgodnie (mam nadzieję) z przyjętą odpowiedzią. (Możesz go bardzo łatwo dostosować).

// ------------------------------------------------------
// 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);

Ten serwer można przetestować za pomocą 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" 
 19
Author: cibercitizen1,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-28 10:50:46

Właśnie skończyłem przykładową aplikację, która robi to w dość prosty, ale przejrzysty sposób. Wykorzystuje mongoose z mongodb do przechowywania użytkowników i paszportu do zarządzania auth.

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

 12
Author: clangager,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-02 04:25:10

Jest wiele pytań o wzorce REST auth tutaj NA SO. Są to najbardziej istotne dla Twojego pytania:

Zasadniczo musisz wybrać pomiędzy używaniem kluczy API (najmniej bezpiecznych, ponieważ klucz może zostać odkryty przez nieautoryzowanego użytkownika), kombinacją klucza aplikacji i tokenu (medium) lub pełną implementacją OAuth (najbezpieczniejszą).

 8
Author: Zim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:38

Jeśli chcesz mieć całkowicie zablokowany obszar webaplikacji, do którego dostęp mają tylko administratorzy z Twojej firmy, autoryzacja SSL może dla Ciebie. Zapewni to, że nikt nie będzie mógł nawiązać połączenia z instancją serwera, chyba że ma zainstalowany autoryzowany certyfikat w swojej przeglądarce. W zeszłym tygodniu napisałem artykuł Jak skonfigurować serwer: Article

Jest to jedna z najbezpieczniejszych konfiguracji, które znajdziesz, ponieważ nie ma nazwy użytkownika/hasła nikt nie może uzyskać dostępu, chyba że jeden z użytkowników przekaże kluczowe pliki potencjalnemu hakerowi.

 2
Author: ExxKA,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-19 12:57:01