wake-up-neo.com

Implementieren einer sicheren REST API mit node.js

Ich beginne mit der Planung einer REST API mit node.js, express und mongodb. Die API liefert Daten für eine Website (öffentlicher und privater Bereich) und möglicherweise später für eine mobile App. Das Frontend wird mit entwickelt AngularJS.

Seit einigen Tagen lese ich viel über das Sichern von REST APIs, aber ich komme nicht zu einer endgültigen Lösung. Soweit ich weiß, soll HTTPS eine grundlegende Sicherheit bieten. Aber wie ich kann die API in diesen Anwendungsfällen schützen:

  • Nur Besucher/Nutzer der Website/App dürfen Daten für den öffentlichen Bereich der Website/App abrufen

  • Nur authentifizierte und autorisierte Benutzer dürfen Daten für den privaten Bereich abrufen (und nur Daten, für die der Benutzer Berechtigungen erteilt hat).

Im Moment denke ich darüber nach, nur Benutzern mit einer aktiven Sitzung die Verwendung der API zu erlauben. Um die Benutzer zu autorisieren, benutze ich den Pass und für die Erlaubnis muss ich etwas für mich selbst implementieren. Alles über HTTPS.

Kann jemand bewährte Verfahren oder Erfahrungen vorlegen? Fehlt es an meiner „Architektur“?

198
tschiela

Ich habe das gleiche Problem gehabt, das Sie beschreiben. Auf die von mir erstellte Website kann von einem Mobiltelefon und vom Browser aus zugegriffen werden. Daher benötige ich eine API, damit sich Benutzer anmelden, anmelden und bestimmte Aufgaben ausführen können. Außerdem muss ich die Skalierbarkeit unterstützen, dh, derselbe Code muss auf verschiedenen Prozessen/Maschinen ausgeführt werden.

Da Benutzer Ressourcen ERSTELLEN können (auch bekannt als POST/PUT-Aktionen), müssen Sie Ihre API sichern. Sie können oauth oder Ihre eigene Lösung erstellen, aber denken Sie daran, dass alle Lösungen beschädigt werden können, wenn das Kennwort wirklich leicht zu ermitteln ist. Die Grundidee besteht darin, Benutzer mithilfe des Benutzernamens zu authentifizieren , password und ein Token, auch als Apitoken bekannt. Dieses Apitoken kann mit node-uuid generiert werden, und das Passwort kann mit pbkdf2 gehasht werden

Dann müssen Sie die Sitzung irgendwo speichern. Wenn Sie es in einem einfachen Objekt im Speicher ablegen, den Server beenden und erneut starten, wird die Sitzung zerstört. Auch das ist nicht skalierbar. Wenn Sie zum Lastenausgleich zwischen Computern Haproxy verwenden oder einfach nur Worker verwenden, wird dieser Sitzungsstatus in einem einzelnen Prozess gespeichert. Wenn also derselbe Benutzer zu einem anderen Prozess/Computer umgeleitet wird, muss er sich erneut authentifizieren. Daher müssen Sie die Sitzung an einem gemeinsamen Ort speichern. Dies geschieht normalerweise mit redis.

Wenn der Benutzer authentifiziert ist (Benutzername + Passwort + Apitoken), generieren Sie ein weiteres Token für die Sitzung, auch bekannt als Accesstoken. Wieder mit Node-UUID. Senden Sie dem Benutzer die Zugangsdaten und die Benutzer-ID. Die Benutzer-ID (Schlüssel) und der Zugriffscode (Wert) werden mit der Zeit, z. 1h.

Jetzt muss der Benutzer jedes Mal, wenn er eine Operation mit der Rest-API ausführt, die Benutzer-ID und den Zugriff senden.

Wenn Sie den Benutzern erlauben, sich mit der Rest-API anzumelden, müssen Sie ein Administratorkonto mit einem Administrator-Apitoken erstellen und diese in der mobilen App speichern (Benutzername + Passwort + Apitoken verschlüsseln), da neue Benutzer kein Apitoken haben, wenn sie melden sich an.

Das Web verwendet auch diese API, aber Sie müssen keine Apitokens verwenden. Sie können Express in einem Redis-Shop verwenden oder die oben beschriebene Technik anwenden, jedoch die Apitoken-Prüfung umgehen und die User-ID + -Zugriff in einem Cookie an den Benutzer zurückgeben.

Wenn Sie über private Bereiche verfügen, vergleichen Sie den Benutzernamen bei der Authentifizierung mit den zulässigen Benutzern. Sie können den Benutzern auch Rollen zuweisen.

Zusammenfassung:

sequence diagram

Eine Alternative ohne Apitoken wäre, HTTPS zu verwenden und den Benutzernamen und das Kennwort im Authorization-Header zu senden und den Benutzernamen in redis zwischenzuspeichern.

170
Gabriel Llamas

Ich möchte diesen Code als strukturelle Lösung für die gestellte Frage beitragen, entsprechend (ich hoffe es) der akzeptierten Antwort. (Sie können es sehr leicht anpassen).

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

Dieser Server kann mit curl getestet werden:

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" 
20
cibercitizen1

Ich habe gerade eine Beispiel-App fertiggestellt, die dies auf eine ziemlich einfache, aber klare Art und Weise erledigt. Es verwendet mongoose mit mongodb, um Benutzer und Reisepass für die Authentifizierungsverwaltung zu speichern.

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

12
clangager

Es gibt viele Fragen zu REST auth patterns hier auf SO. Diese sind für Ihre Frage am relevantesten:

Grundsätzlich müssen Sie zwischen der Verwendung von API-Schlüsseln (am wenigsten sicher, da der Schlüssel möglicherweise von einem nicht autorisierten Benutzer entdeckt wird), einer Kombination aus App-Schlüssel und Token (mittel) oder einer vollständigen OAuth Implementierung (die meisten) wählen sichern).

8
Zim

Wenn Sie einen vollständig gesperrten Bereich Ihrer Webanwendung haben möchten, auf den nur Administratoren Ihres Unternehmens zugreifen können, ist die SSL-Autorisierung möglicherweise für Sie. Es wird sichergestellt, dass niemand eine Verbindung zur Serverinstanz herstellen kann, es sei denn, er hat ein autorisiertes Zertifikat in seinem Browser installiert. Letzte Woche habe ich einen Artikel über das Einrichten des Servers geschrieben: Article

Dies ist eine der sichersten Einstellungen, die Sie finden werden, da kein Benutzername/Kennwort erforderlich ist, sodass niemand Zugriff erhalten kann, es sei denn, einer Ihrer Benutzer übergibt die Schlüsseldateien einem potenziellen Hacker.

2
ExxKA

Wenn Sie Ihre Anwendung sichern möchten, dann sollten Sie auf jeden Fall HTTPS anstelle von HTTP verwenden, dies gewährleistet eine Durch die Einrichtung eines sicheren Kanals zwischen Ihnen und den Benutzern wird verhindert, dass die an die Benutzer gesendeten Daten abgehört werden, und die ausgetauschten Daten werden vertraulich behandelt.

Sie können JWTs (JSON-Web-Tokens) zum Sichern von RESTful-APIs verwenden . Dies hat im Vergleich zu serverseitigen Sitzungen viele Vorteile. Die Vorteile sind hauptsächlich:

1- Skalierbarer, da Ihre API-Server nicht für jeden Benutzer Sitzungen verwalten müssen (was eine große Belastung sein kann, wenn Sie viele Sitzungen haben)

2- JWTs sind in sich geschlossen und haben die Ansprüche, die zum Beispiel die Benutzerrolle definieren und auf die er zugreifen kann und die zum Datum und Ablaufdatum ausgestellt wurden (danach ist JWT nicht mehr gültig).

3- Einfachere Handhabung über Load-Balancer hinweg. Wenn Sie über mehrere API-Server verfügen, müssen Sie weder Sitzungsdaten freigeben noch den Server so konfigurieren, dass die Sitzung an denselben Server weitergeleitet wird, wenn eine Anforderung mit einem JWT einen beliebigen Server trifft, der authentifiziert werden kann & autorisiert

4- Verringern Sie den Druck auf Ihre Datenbank, und Sie müssen Sitzungs-IDs und -Daten nicht ständig für jede Anforderung speichern und abrufen

5- Die JWTs können nicht manipuliert werden, wenn Sie einen starken Schlüssel zum Signieren der JWT verwenden. Sie können also den Ansprüchen in der JWT vertrauen, die mit der Anforderung gesendet werden, ohne die Benutzersitzung überprüfen zu müssen und ob er autorisiert ist oder nicht Sie können nur das JWT überprüfen und dann wissen Sie, wer und was dieser Benutzer tun kann.

Viele Bibliotheken bieten einfache Möglichkeiten, JWTs in den meisten Programmiersprachen zu erstellen und zu validieren, zum Beispiel: In node.js ist jsonwebtoken eine der beliebtesten

Da REST APIs im Allgemeinen darauf abzielen, den Server statusfrei zu halten, sind JWTs mit diesem Konzept besser kompatibel, da jede Anforderung mit einem in sich geschlossenen Autorisierungstoken gesendet wird ( JWT) ohne dass der Server die Benutzersitzung im Vergleich zu Sitzungen verfolgen muss, die den Server in einen statusbehafteten Zustand versetzen, damit er sich an den Benutzer und seine Rolle erinnert. Sitzungen sind jedoch ebenfalls weit verbreitet und haben ihre Vorzüge , nach denen Sie suchen können, wenn Sie möchten.

Eine wichtige Sache, die Sie beachten müssen, ist, dass Sie das JWT mit HTTPS sicher an den Client übermitteln und an einem sicheren Ort (z. B. im lokalen Speicher) speichern müssen.

Sie können mehr über JWTs erfahren von diesem Link

0
Ahmed Elkoussy