So implementieren Sie eine sichere REST-API mit node.js.


204

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

Einige Tage habe ich viel über das Sichern von REST-APIs gelesen, aber ich komme nicht zu einer endgültigen Lösung. Soweit ich weiß, ist die Verwendung von HTTPS zur Bereitstellung einer grundlegenden Sicherheit. Aber wie kann ich die API in diesen Anwendungsfällen schützen:

  • Nur Besucher / Benutzer 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, verwende ich den Reisepass und für die Erlaubnis muss ich etwas für mich selbst implementieren. Alles auf HTTPS.

Kann jemand Best Practices oder Erfahrungen liefern? Fehlt meine „Architektur“?


2
Ich vermute, die API soll nur über das von Ihnen bereitgestellte Frontend verwendet werden. In diesem Fall scheint es eine gute Lösung zu sein, die Sitzung zu verwenden, um sicherzustellen, dass der Benutzer gültig ist. Für Berechtigungen können Sie einen Blick auf nehmen Knoten-Rollen .
Robertklep

2
Was hast du endlich dafür getan? Gibt es einen Kesselschildcode (Server / Client für mobile Apps), den Sie freigeben können?
Morteza Shahriari Nia

Antworten:


175

Ich hatte das gleiche Problem, das Sie beschreiben. Auf die Website, die ich erstelle, kann über ein Mobiltelefon und über den Browser zugegriffen werden. Daher benötige ich eine API, damit Benutzer sich anmelden, anmelden und bestimmte Aufgaben ausführen können. Außerdem muss ich die Skalierbarkeit unterstützen, den gleichen Code, der auf verschiedenen Prozessen / Maschinen ausgeführt wird.

Da Benutzer Ressourcen (auch als POST / PUT-Aktionen bezeichnet) erstellen können, müssen Sie Ihre API sichern. Sie können oauth verwenden oder Ihre eigene Lösung erstellen. Beachten Sie jedoch, dass alle Lösungen beschädigt werden können, wenn das Kennwort wirklich leicht zu ermitteln ist. Die Grundidee besteht darin, Benutzer mit dem Benutzernamen, dem Kennwort und einem Token, auch Apitoken genannt, zu authentifizieren. Dieses Apitoken kann mit node- uuid generiert werden und das Passwort mit pbkdf2 gehasht werden

Dann müssen Sie die Sitzung irgendwo speichern. Wenn Sie es im Speicher eines einfachen Objekts speichern, wenn Sie den Server beenden und erneut starten, wird die Sitzung zerstört. Auch dies ist nicht skalierbar. Wenn Sie Haproxy zum Lastausgleich zwischen Computern verwenden oder einfach Worker verwenden, wird dieser Sitzungsstatus in einem einzelnen Prozess gespeichert. Wenn 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 erfolgt 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 das Zugangsformular und die Benutzer-ID. Die Benutzer-ID (Schlüssel) und der Zugriff (Wert) werden in Redis mit einer Ablaufzeit von z. B. 1 Stunde gespeichert.

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

Wenn Sie den Benutzern erlauben, sich mit der restlichen 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 zu diesem Zeitpunkt kein Apitoken haben sie melden sich an.

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

Wenn Sie private Bereiche haben, vergleichen Sie den Benutzernamen mit den zulässigen Benutzern, wenn diese sich authentifizieren. Sie können den Benutzern auch Rollen zuweisen.

Zusammenfassung:

Sequenzdiagramm

Eine Alternative ohne Apitoken wäre die Verwendung von HTTPS und das Senden des Benutzernamens und des Kennworts im Authorization-Header und das Zwischenspeichern des Benutzernamens in redis.


1
Ich benutze auch Mongodb, aber es ist ziemlich einfach zu verwalten, wenn Sie die Sitzung (Zugriff) mit Redis speichern (atomare Operationen verwenden). Das Apitoken wird auf dem Server generiert, wenn der Benutzer ein Konto erstellt und es an den Benutzer zurücksendet. Wenn sich der Benutzer authentifizieren möchte, muss er Benutzername + Passwort + Apitoken senden (in den http-Text einfügen). Beachten Sie, dass HTTP den Body nicht verschlüsselt, sodass das Passwort und das Apitoken abgehört werden können. Verwenden Sie HTTPS, wenn Sie diesbezüglich Bedenken haben.
Gabriel Llamas

1
Was bringt es, ein zu verwenden apitoken? ist es ein "sekundäres" Passwort?
Salvatorelab

2
@TheBronx Das Apitoken hat zwei Anwendungsfälle: 1) Mit einem Apitoken können Sie den Zugriff der Benutzer auf Ihr System steuern und Statistiken jedes Benutzers überwachen und erstellen. 2) Es ist eine zusätzliche Sicherheitsmaßnahme, ein "sekundäres" Passwort.
Gabriel Llamas

1
Warum sollten Sie die Benutzer-ID nach erfolgreicher Authentifizierung immer wieder senden? Das Token sollte das einzige Geheimnis sein, das Sie zum Ausführen von API-Aufrufen benötigen.
Axel Napolitano

1
Die Idee des Tokens ist - neben dem Missbrauch zur Verfolgung der Benutzeraktivität -, dass ein Benutzer im Idealfall keinen Benutzernamen und kein Kennwort für die Verwendung einer Anwendung benötigt: Das Token ist der eindeutige Zugriffsschlüssel. Auf diese Weise können Benutzer jederzeit einen beliebigen Schlüssel löschen, der nur die App, nicht jedoch das Benutzerkonto betrifft. Für einen Webservice ist ein Token ziemlich unhandlich - deshalb ist eine erste Anmeldung für eine Sitzung der Ort, an dem der Benutzer dieses Token erhält - für einen "normalen" Client ab ist ein Token kein Problem: Geben Sie es einmal ein und Sie sind fast fertig ;)
Axel Napolitano

22

Ich möchte diesen Kodex als strukturelle Lösung für die gestellte Frage gemäß (ich hoffe es) zur akzeptierten Antwort beitragen. (Sie können es sehr einfach 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" 

Vielen Dank für dieses Beispiel, es ist sehr hilfreich, aber ich versuche, dem zu folgen, und wenn ich mich mit dem Login verbinde, sage ich Folgendes: curl: (51) SSL: Der Name des Zertifikatsbetreffs 'xxxx' stimmt nicht mit dem Namen des Zielhosts 'xxx.net' überein. Ich habe meine / etc / hosts fest
codiert


9

Es gibt viele Fragen zu REST-Authentifizierungsmustern hier auf SO. Dies sind die relevantesten für Ihre Frage:

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 erkannt wird), einer Kombination aus App-Schlüssel und Token (mittel) oder einer vollständigen OAuth-Implementierung (am sichersten) wählen.


Ich habe viel über oauth 1.0 und oauth 2.0 gelesen und beide Versionen scheinen nicht sehr sicher zu sein. Wikipedia schrieb, dass es in oauth 1.0 einige Sicherheitslücken gibt. Außerdem habe ich einen Artikel gefunden, der besagt, dass einer der Hauptentwickler das Team verlässt, weil oauth 2.0 zu unsicher ist.
Tschiela

12
@tschiela Sie sollten Verweise auf alles hinzufügen, was Sie hier zitieren.
Mikemaccana

3

Wenn Sie Ihre Anwendung sichern möchten, sollten Sie auf jeden Fall HTTPS anstelle von HTTP verwenden . Dies stellt sicher, dass ein sicherer Kanal zwischen Ihnen und den Benutzern erstellt wird, der verhindert, dass die an die Benutzer gesendeten Daten abgehört werden, und dass die Daten erhalten bleiben vertraulich ausgetauscht.

Sie können JWTs (JSON Web Tokens) verwenden, um RESTful-APIs zu sichern . 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 bei vielen Sitzungen eine große Belastung sein kann).

2- JWTs sind in sich geschlossen und haben die Ansprüche, die beispielsweise 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 Server trifft, kann diese authentifiziert werden & autorisiert

4- Weniger Druck auf Ihre Datenbank und Sie müssen nicht ständig Sitzungs-IDs und -Daten 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 einfach die JWT überprüfen und dann wissen Sie, wer und was dieser Benutzer tun kann.

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

Da REST-APIs im Allgemeinen darauf abzielen, den Server zustandslos zu halten, sind JWTs mit diesem Konzept besser kompatibel, da jede Anforderung mit einem eigenständigen Autorisierungstoken (JWT) gesendet wird, ohne dass der Server die Benutzersitzung im Vergleich zu Sitzungen, in denen der Server ausgeführt wird, verfolgen muss Server statusbehaftet, damit er sich an den Benutzer und seine Rolle erinnert. Sitzungen sind jedoch auch weit verbreitet und haben ihre Vorteile, nach denen Sie suchen können, wenn Sie möchten.

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

Weitere Informationen zu JWTs finden Sie unter diesem Link


1
Ich mag Ihre Antwort, die das beste Update von dieser alten Frage zu sein scheint. Ich habe mir eine andere Frage zum gleichen Thema gestellt, und Sie könnten auch hilfreich sein. => stackoverflow.com/questions/58076644/…
pbonnefoi

Danke, ich bin froh, dass ich helfen konnte. Ich
poste

2

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. Dadurch wird sichergestellt, dass niemand eine Verbindung zur Serverinstanz herstellen kann, es sei denn, in seinem Browser ist ein autorisiertes Zertifikat installiert. Letzte Woche habe ich einen Artikel über die Einrichtung des Servers geschrieben: Artikel

Dies ist eine der sichersten Einstellungen, die Sie finden werden, da keine Benutzernamen / Kennwörter beteiligt sind, sodass niemand Zugriff erhalten kann, es sei denn, einer Ihrer Benutzer übergibt die Schlüsseldateien einem potenziellen Hacker.


schöner Artikel. Der private Bereich ist jedoch für Benutzer.
Tschiela

Danke - richtig, dann sollten Sie sich für eine andere Lösung entscheiden, das Verteilen von Zertifikaten wäre ein Schmerz.
ExxKA
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.