Як реалізувати захищений API REST за допомогою node.js


204

Я починаю планувати API REST з node.js, express та mongodb. API надає дані для веб-сайту (публічної та приватної) та, можливо, пізніше мобільного додатка. Фронтенд буде розроблений за допомогою AngularJS.

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

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

  • Лише автентифіковані та авторизовані користувачі можуть отримувати дані для приватної області (і лише дані, коли користувач надав дозволи)

На даний момент я думаю лише дозволити користувачам, які мають активний сеанс, використовувати API. Для авторизації користувачів я буду використовувати паспорт і для дозволу мені потрібно щось реалізувати для себе. Усі на вершині HTTPS.

Чи може хтось надати найкращу практику чи досвід? Чи не вистачає моєї «архітектури»?


2
Я здогадуюсь, API можна використовувати лише з наданого вами фронтену? У такому випадку використання сеансу для забезпечення дійсності користувача здається хорошим рішенням. Для отримання дозволів ви можете подивитися ролі вузлів .
robertklep

2
Що ви нарешті зробили для цього? Будь-який код пластини котла (клієнт сервера / мобільного додатка), яким ви можете поділитися?
Morteza Shahriari Nia

Відповіді:


175

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

Оскільки користувачі можуть СТВОРИТИ ресурси (також дії POST / PUT), вам потрібно захистити свій api. Ви можете використовувати oauth або ви можете створити власне рішення, але майте на увазі, що всі рішення можуть бути зламані, якщо пароль виявити дійсно легко. Основна ідея полягає в аутентифікації користувачів, використовуючи ім'я користувача, пароль і маркер, також відомий як apitoken. Цей apitoken можна генерувати за допомогою node-uuid, а пароль можна хешировать за допомогою pbkdf2

Потім вам потрібно десь зберегти сеанс. Якщо ви збережете його в пам'яті в простому об'єкті, якщо ви вб'єте сервер і перезавантажите його, сеанс буде знищений. Також це не масштабується. Якщо ви використовуєте haproxy для завантаження балансу між машинами або просто використовуєте працівників, цей стан сеансу буде зберігатися в єдиному процесі, тому, якщо той самий користувач буде перенаправлений на інший процес / машину, йому потрібно буде ще раз перевірити автентифікацію. Тому потрібно зберігати сеанс у загальному місці. Зазвичай це робиться за допомогою redis.

Після автентифікації користувача (ім'я користувача + пароль + apitoken) генеруйте ще один маркер для сеансу, aka accesstoken. Знову з вузлом-uuid. Надішліть користувачеві доступ до доступу та userid. Userid (ключ) та accesstoken (значення) зберігаються в редакційному режимі та закінчують час, наприклад, 1 год.

Тепер, кожен раз, коли користувач виконує будь-яку операцію, використовуючи решту api, йому потрібно буде надіслати користувальницький номер і доступ до доступу.

Якщо ви дозволите користувачам реєструватися за допомогою решти api, вам потрібно буде створити обліковий запис адміністратора з адміністратором apitoken і зберігати їх у мобільному додатку (шифрувати ім’я користувача + пароль + apitoken), оскільки нові користувачі не матимуть apitoken, коли вони підписуються.

В Інтернеті також використовується ця програма, але не потрібно використовувати апітоки. Ви можете використовувати express із магазином redis або використовувати ту саму методику, яку описано вище, але обминаючи чек apitoken і повертаючи користувачеві userid + accesstoken у файлі cookie.

Якщо у вас є приватні райони, порівнюйте ім’я користувача з дозволеними користувачами під час їх автентифікації. Ви також можете застосовувати ролі до користувачів.

Підсумок:

діаграма послідовності

Альтернативою без apitoken було б використовувати HTTPS та надсилати ім'я користувача та пароль у заголовку Авторизації та кешувати ім'я користувача в redis.


1
Я також використовую mongodb, але керувати досить легко, якщо ви збережете сеанс (доступ до доступу) за допомогою redis (використовуйте атомні операції). Apitoken генерується на сервері, коли користувач створює обліковий запис і відправляє його назад користувачеві. Потім, коли користувач хоче пройти автентифікацію, він повинен надіслати ім'я користувача + пароль + apitoken (помістити їх у телі http). Майте на увазі, що HTTP не шифрує тіло, тому пароль та апітокени можна обнюхати. Використовуйте HTTPS, якщо це стосується вас.
Габріель Лламас

1
який сенс у використанні apitoken? це "вторинний" пароль?
Сальваторелаб

2
@TheBronx У apitoken є 2 випадки використання: 1) за допомогою apitoken ви можете контролювати доступ користувачів до вашої системи, а також можете відстежувати та будувати статистику кожного користувача. 2) Це додатковий захід безпеки, "вторинний" пароль.
Габріель Лламас

1
Чому слід надсилати ідентифікатор користувача знову і знову після успішної автентифікації. Маркер повинен бути єдиним секретом, який потрібно виконувати дзвінки API.
Аксель Наполітано

1
Ідея маркера - окрім зловживання ним для відстеження активності користувачів - полягає в тому, що користувачеві в ідеалі не потрібні ім’я користувача та пароль для використання програми: Маркер - це унікальний ключ доступу. Це дозволяє користувачам у будь-який час скидати будь-яку клавішу, яка впливає лише на додаток, але не на обліковий запис користувача. Для веб-сервісу маркер досить непристойний - тому початковий вхід для сеансу - це місце, де користувач отримує цей маркер - для "звичайного" клієнта ab, маркер не проблема: введіть його один раз, і ви майже готові ;)
Аксель Наполітано

22

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

// ------------------------------------------------------
// 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" 

Дякую за цей зразок дуже корисною, проте я намагаюся дотримуватися цього, і коли я підключаюсь, щоб увійти до нього, кажучи це: curl: (51) SSL: назва предмета сертифіката 'xxxx' не відповідає цільовому імені хоста 'xxx.net'. Я жорстко кодував мій / etc / hosts, щоб дозволити з'єднання https на одній машині
mastervv

11

Я щойно закінчив зразок програми, який робить це досить базовим, але чітким способом. Він використовує мангуст з mongodb для зберігання користувачів та паспорта для управління авторами.

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


7
Ви використовуєте файл cookie для захисту api. Я не думаю, що це правильно.
Вінс Юань

9

Тут багато запитань щодо авторських моделей REST. Це найбільш відповідні для вашого питання:

В основному вам потрібно вибрати між використанням ключів API (найменш захищеним, оскільки ключ може бути виявлений несанкціонованим користувачем), ключем програми та комбінацією токенів (середній) або повною реалізацією OAuth (найбільш захищеною).


Я багато читав про oauth 1.0 та oauth 2.0, і обидві версії здаються не дуже безпечними. У Вікіпедії написано, що в oauth 1.0 є деякі витоки безпеки. Також я знайшов статтю про те, що про одного з основних розробників залишають команду, оскільки oauth 2.0 - це не захистити.
tschiela

12
@tschiela Тут слід додати посилання на все, що ви цитуєте.
mikemaccana

3

Якщо ви хочете захистити свою програму, тоді вам слід обов'язково почати використовувати HTTPS замість HTTP , це забезпечує створення захищеного каналу між вами та користувачами, що запобіжить нюхати дані, що надсилаються назад і назад користувачам, і допоможе зберегти дані обмінялися конфіденційними.

Ви можете використовувати JWTs (JSON Web Tokens) для захисту API RESTful , це має багато переваг у порівнянні з сеансами на сервері, переваги в основному:

1- Більш масштабований, оскільки ваші сервери API не повинні підтримувати сеанси для кожного користувача (що може бути великим тягарем, коли у вас багато сеансів)

2- JWT є автономними та мають претензії, які визначають роль користувача, наприклад, & що він може отримати та видав на дату та дату закінчення терміну дії (після чого JWT не буде дійсним)

3- Легше керувати через балансири завантаження і якщо у вас є кілька серверів API, оскільки вам не доведеться ділитися даними сеансу і не налаштовувати сервер для маршрутизації сеансу на один і той же сервер, коли запит із JWT потрапляє на будь-який сервер, його можна автентифікувати & уповноважений

4- Менший тиск на вашу БД, а також вам не доведеться постійно зберігати та отримувати ідентифікатори та дані сеансу для кожного запиту

5- JWT не можуть бути підроблені, якщо ви використовуєте сильний ключ для підписання JWT, тому ви можете довіряти претензії в JWT, що надсилаються із запитом, не перевіряючи сеанс користувача та чи він дозволений чи ні ви можете просто перевірити JWT & тоді ви все готові знати, хто і що може робити цей користувач.

Багато бібліотек пропонують прості способи створення та перевірки JWT в більшості мов програмування, наприклад: у node.js однією з найпопулярніших є jsonwebtoken

Оскільки API REST, як правило, спрямований на те, щоб сервер не мав стан, тому JWT більше сумісні з цією концепцією, оскільки кожен запит надсилається з токеном авторизації, який міститься в самостійному (JWT) без того, щоб сервер повинен відслідковувати сеанс користувача порівняно з сеансами, які роблять серверний стан, щоб він запам'ятовував користувача та його роль, однак сесії також широко використовуються та мають свої плюси, які ви можете шукати, якщо хочете.

Важливо відзначити, що вам потрібно надійно доставити JWT клієнту за допомогою HTTPS і зберегти його в безпечному місці (наприклад, у локальному сховищі).

Дізнатися більше про JWT можна за цим посиланням


1
Мені подобається ваша відповідь, яка здається найкращим оновленням цього старого питання. Я задав собі інше запитання на цю ж тему, і ви також можете бути корисними. => Stackoverflow.com/questions/58076644 / ...
pbonnefoi

Дякую, радий, що можу допомогти, я розміщую відповідь на ваше запитання
Ахмед Елкосі,

2

Якщо ви хочете мати повністю заблоковану область вашого веб-додатку, доступ до якої можуть отримати лише адміністратори вашої компанії, то авторизація SSL може бути для вас. Це гарантує, що ніхто не може встановити з'єднання з екземпляром сервера, якщо у них не встановлений авторизований сертифікат у своєму браузері. Минулого тижня я написав статтю про те, як налаштувати сервер: Article

Це одна з найбільш захищених налаштувань, яку ви знайдете, оскільки немає жодного імені користувача / паролів, тому ніхто не може отримати доступ, якщо один з ваших користувачів не передасть ключові файли потенційному хакеру.


приємна стаття. Але приватна зона призначена для користувачів.
tschiela

Спасибі - правильно, тоді вам слід піти на інше рішення, розповсюдження сертифікатів було б болем.
ExxKA
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.