Node.js ile güvenli bir REST API'si nasıl uygulanır?


204

Node.js, express ve mongodb ile bir REST API planlamaya başladım. API, bir web sitesi (genel ve özel alan) ve belki de bir mobil uygulama için veri sağlar. Ön uç AngularJS ile geliştirilecek.

Bazı günlerde REST API'lerini koruma hakkında çok şey okudum, ancak son bir çözüme ulaşamıyorum. Anladığım kadarıyla temel bir güvenlik sağlamak için HTTPS kullanmak. Ancak bu durumlarda API'yı nasıl koruyabilirim:

  • Yalnızca web sitesinin / uygulamanın ziyaretçileri / kullanıcılarının web sitesinin / uygulamanın genel alanı için veri almasına izin verilir

  • Yalnızca kimliği doğrulanmış ve yetkilendirilmiş kullanıcıların özel alan (ve yalnızca kullanıcının izin verdiği veriler) için veri almasına izin verilir

Şu anda yalnızca aktif bir oturumu olan kullanıcıların API'yı kullanmasına izin vermeyi düşünüyorum. Kullanıcıları yetkilendirmek için pasaport kullanacağım ve izin için kendim için bir şeyler uygulamam gerekiyor. Hepsi HTTPS'nin üstünde.

Birisi en iyi uygulamaları veya deneyimleri sağlayabilir mi? “Mimarim” de bir eksiklik var mı?


2
Ben sadece API sağladığınız ön uç kullanılacak tahmin ediyorum? Bu durumda, kullanıcının geçerli olduğundan emin olmak için oturumu kullanmak iyi bir çözüm gibi görünür. İzinler için, düğüm rollerine bakabilirsiniz .
robertklep

2
Sonunda bunun için ne yaptın? Paylaşabileceğiniz herhangi bir kazan plakası kodu (sunucu / mobil uygulama istemcisi)?
Morteza Shahriari Nia

Yanıtlar:


176

Tarif ettiğin aynı problemi yaşadım. Oluşturduğum web sitesine bir cep telefonundan ve tarayıcıdan erişilebilir, bu yüzden kullanıcıların kaydolmasına, giriş yapmasına ve bazı belirli görevleri yapmasına izin vermek için bir API'ye ihtiyacım var. Ayrıca, ölçeklenebilirliği desteklemem gerekiyor, aynı kod farklı süreçlerde / makinelerde çalışıyor.

Kullanıcılar kaynakları oluşturabileceğinden (POST / PUT eylemleri olarak da bilinir) API'nızı güvenceye almanız gerekir. Oauth'u kullanabilir veya kendi çözümünüzü oluşturabilirsiniz, ancak parolanın keşfedilmesi gerçekten kolaysa tüm çözümlerin kırılabileceğini unutmayın. Temel fikir, kullanıcı adı, şifre ve bir belirteç olan apitoken kullanarak kullanıcıların kimliğini doğrulamaktır. Bu apitoken node-uuid kullanılarak oluşturulabilir ve parola pbkdf2 kullanılarak özetlenebilir.

Ardından oturumu bir yere kaydetmeniz gerekir. Düz bir nesnede belleğe kaydederseniz, sunucuyu öldürüp yeniden başlatırsanız oturum yok edilir. Ayrıca, bu ölçeklenebilir değildir. Makineler arasında denge yüklemek için haproxy kullanırsanız veya yalnızca işçileri kullanırsanız, bu oturum durumu tek bir işlemde depolanır; böylece aynı kullanıcı başka bir işleme / makineye yönlendirilirse yeniden kimlik doğrulaması gerekir. Bu nedenle oturumu ortak bir yerde saklamanız gerekir. Bu genellikle redis kullanılarak yapılır.

Kullanıcının kimliği doğrulandığında (kullanıcı adı + şifre + apitoken) oturum için başka bir belirteç, yani erişim belirteci oluşturun. Yine, düğüm-uuid ile. Kullanıcıya erişim belirteci ve kullanıcı kimliği gönderin. Kullanıcı kimliği (anahtar) ve erişim belirteci (değer) yeniden kullanım süresi içinde ve son kullanma süresi ile saklanır, örneğin 1 saat.

Şimdi, kullanıcı geri kalan api'yi kullanarak herhangi bir işlem yaptığında, kullanıcı kimliği ve erişim belirteci göndermesi gerekecektir.

Kullanıcıların geri kalan api'yi kullanarak kaydolmasına izin verirseniz, bir yönetici apitokeniyle bir yönetici hesabı oluşturmanız ve bunları mobil uygulamada (kullanıcı adı + şifre + apitoken şifreleme) depolamanız gerekir; çünkü yeni kullanıcıların kaydoluyorlar.

Web de bu API'yı kullanıyor ancak apitoken kullanmanıza gerek yok. Express'i bir redis deposuyla kullanabilir veya yukarıda açıklananla aynı tekniği kullanabilir, ancak apitoken kontrolünü atlayarak ve bir tanımlama bilgisinde userid + accesstoken'i kullanıcıya geri gönderebilirsiniz.

Özel alanlarınız varsa, kimlik doğrulaması sırasında kullanıcı adını izin verilen kullanıcılarla karşılaştırın. Kullanıcılara roller de uygulayabilirsiniz.

Özet:

dizi diyagramı

Apitoken içermeyen bir alternatif, HTTPS kullanmak ve Yetkilendirme başlığında kullanıcı adını ve şifreyi göndermek ve kullanıcı adını redis olarak önbelleğe almak olabilir.


1
Ayrıca mongodb kullanıyorum ama redis kullanarak (atomik işlemleri kullanın) oturumu (accesstoken) kaydederseniz yönetmek oldukça kolaydır. Kullanıcı bir hesap oluşturup kullanıcıya geri gönderdiğinde sunucuda sunucu oluşturulur. Daha sonra, kullanıcı kimlik doğrulaması yapmak istediğinde kullanıcı adı + şifre + apitoken göndermelidir (http gövdesine koymalıdır). HTTP'nin gövdeyi şifrelemediğini unutmayın, böylece şifre ve apitoken koklanabilir. Bu sizin için bir sorunsa HTTPS kullanın.
Gabriel Llamas

1
an kullanmanın anlamı apitokennedir? "ikincil" bir şifre mi?
Salvatorelab

2
@TheBronx Apitoken'in 2 kullanım durumu vardır: 1) bir apitoken ile kullanıcıların sisteminize erişimini kontrol edebilir ve her kullanıcının istatistiklerini izleyebilir ve oluşturabilirsiniz. 2) Ek bir güvenlik önlemi, "ikincil" bir şifre.
Gabriel Llamas

1
Başarılı kimlik doğrulamasından sonra neden kullanıcı kimliğini tekrar tekrar göndermelisiniz. Jeton, API çağrılarını gerçekleştirmek için ihtiyacınız olan tek sır olmalıdır.
Axel Napolitano

1
Simgenin fikri - kullanıcı etkinliğini izlemek için kötüye kullanmanın yanı sıra - bir kullanıcının uygulamayı kullanmak için ideal olarak herhangi bir kullanıcı adı ve parolaya ihtiyaç duymamasıdır: token benzersiz erişim anahtarıdır. Bu, kullanıcıların herhangi bir anda yalnızca uygulamayı etkileyen ancak kullanıcı hesabını etkileyen herhangi bir anahtarı bırakmasına olanak tanır. Bir web hizmeti için bir token oldukça kullanışlı değildir - bu yüzden bir oturum için ilk giriş, kullanıcının bu jetonu aldığı yerdir - "normal" bir müşteri ab için, bir jeton sorun değildir: Bir kez girin ve neredeyse bitti ;)
Axel Napolitano

22

Kabul edilen cevaba göre (umarım) sorulan soruya yapısal bir çözüm olarak bu kodu katkıda bulunmak istiyorum. (Kolayca özelleştirebilirsiniz).

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

Bu sunucu curl ile test edilebilir:

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" 

Bu örnek için teşekkürler onun çok yararlı, ancak ben bunu takip etmeye çalışın, ve ben bunu söyleyerek giriş bağlanmak zaman: curl: (51) SSL: sertifika konu adı 'xxxx' hedef ana bilgisayar adı 'xxx.net' ile eşleşmiyor. Aynı makinede https bağlanmasına izin vermek için benim / etc / hosts
kodlarımı yazdım


9

Burada REST yetkilendirme kalıpları hakkında birçok soru var. Bunlar sorunuzla en alakalı olanları:

Temel olarak, API anahtarlarını kullanma (anahtar yetkisiz bir kullanıcı tarafından keşfedilebileceği için en az güvenli), bir uygulama anahtarı ve jeton açılan (orta) veya tam bir OAuth uygulaması (en güvenli) arasında seçim yapmanız gerekir.


Oauth 1.0 ve oauth 2.0 hakkında çok şey okudum ve her iki sürüm de çok güvenli görünmüyor. Wikipedia, oauth 1.0'da bazı güvenlik sızıntıları olduğunu yazdı. Ayrıca çekirdek geliştiricilerden biri hakkında oauth 2.0 güvensiz olduğundan önce takım terk bir makale buldum.
tschiela

12
@tschiela Burada alıntıladığınız her şeye referans eklemelisiniz.
mikemaccana

3

Uygulamanızı güven altına almak istiyorsanız, kesinlikle HTTP yerine HTTPS kullanarak başlamalısınız , bu, kullanıcılar arasında ileri geri gönderilen verilerin koklanmasını önleyecek ve verilerin korunmasına yardımcı olacak güvenli bir kanal oluşturmanızı sağlar. gizli değişim.

RESTful API'leri güvenceye almak için JWT'leri (JSON Web Belirteçleri) kullanabilirsiniz , bunun sunucu tarafı oturumlarına kıyasla birçok faydası vardır, faydaları temel olarak şunlardır:

1- Daha fazla ölçeklenebilir, çünkü API sunucularınızın her kullanıcı için oturumları sürdürmesi gerekmeyecektir (bu, birçok oturumunuz olduğunda büyük bir yük olabilir)

2- JWT'ler bağımsızdır ve örneğin kullanıcı rolünü ve neye erişebildiğini ve tarih ve son kullanma tarihinde ne yayınlayabileceğini iddia eder (bundan sonra JWT geçerli olmayacaktır)

3- Yük dengeleyicilerin üstesinden gelmek daha kolaydır ve oturum verilerini paylaşmak veya sunucuyu oturumu aynı sunucuya yönlendirecek şekilde yapılandırmak zorunda kalmayacağınız için birden fazla API sunucunuz varsa, bir JWT'den gelen bir istek herhangi bir sunucuya çarptığında doğrulanabilir & yetkili

4- DB'niz üzerinde daha az baskı ve her istek için oturum kimliği ve verilerini sürekli olarak depolamak ve almak zorunda kalmayacaksınız

5- JWT'yi imzalamak için güçlü bir anahtar kullanırsanız, JWT'lere müdahale edilemez, böylece JWT'deki, kullanıcı oturumunu ve yetkilendirilmiş olup olmadığını kontrol etmek zorunda kalmadan, istekle birlikte gönderilen taleplere güvenebilirsiniz. , JWT'yi kontrol edebilirsiniz ve sonra bu kullanıcının kim ve ne yapabileceğini öğrenmeye hazırsınız.

Birçok kütüphane çoğu programlama dilinde JWT oluşturmak ve doğrulamak için kolay yollar sağlar, örneğin: node.js'de en popüler olanlardan biri jsonwebtoken

REST API'leri genelde müstakil olmasıdır belirteç her istek Yetkilendirme ile gönderilir olarak JWTS bu konsept ile daha uyumlu olacak şekilde, sunucu durum bilgisi olmayan tutmayı amaçlayan bu yana (JWT'yi) sunucu yapmak oturumları kıyasla kullanıcı oturumu izlemek zorunda kalmadan sunucu durum, böylece kullanıcı ve rolünü hatırlar, ancak oturumlar da yaygın olarak kullanılan ve kendi artıları var, isterseniz arayabilirsiniz.

Dikkat edilmesi gereken önemli bir nokta, JWT'yi HTTPS kullanarak güvenli bir şekilde istemciye teslim etmeniz ve güvenli bir yere (örneğin yerel depolamada) kaydetmeniz gerektiğidir.

JWT'ler hakkında daha fazla bilgiyi bu bağlantıdan edinebilirsiniz .


1
Bu eski sorunun en iyi güncellemesi gibi görünen cevabınızı beğendim. Kendime aynı konuda başka bir soru sordum ve siz de yardımcı olabilirsiniz. => stackoverflow.com/questions/58076644/…
pbonnefoi

Teşekkürler, yardımcı olabilirim, sorunuz için bir cevap gönderiyorum
Ahmed Elkoussy

2

Web uygulamanızın yalnızca şirketinizden yöneticiler tarafından erişilebilen tamamen kilitli bir alana sahip olmak istiyorsanız, SSL yetkisi sizin için olabilir. Tarayıcılarına yetkili bir sertifika yüklenmedikçe hiç kimsenin sunucu yönetim ortamıyla bağlantı kuramayacağını garanti eder. Geçen hafta sunucunun nasıl kurulacağına dair bir makale yazdım: Makale

Bu, kullanacağınız kullanıcı adı / şifreler olmadığı için bulacağınız en güvenli kurulumlardan biridir, böylece kullanıcılarınızdan biri anahtar dosyalarını potansiyel bir bilgisayar korsanına teslim etmedikçe hiç kimse erişim sağlayamaz.


güzel makale. Ancak özel alan kullanıcılar içindir.
tschiela

Teşekkürler - doğru, o zaman başka bir çözüm için gitmelisiniz, sertifikaları dağıtmak bir acı olacaktır.
19x13, ExxKA
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.