Как реализовать безопасный REST API с узлом.js
Я начинаю планировать REST API с узла.js, express и mongodb. API предоставляет данные для веб-сайта (публичной и частной области) и, возможно, позже для мобильного приложения. Интерфейс будет разработан с AngularJS.
в течение нескольких дней я много читал о защите API REST, но я не добираюсь до окончательного решения. Насколько я понимаю, это использовать HTTPS для обеспечения базовой безопасности. Но как я могу защитить API в этом случае использования:
только посетители/пользователи веб-сайт / приложение могут получать данные для публичной области веб-сайта/приложения
только аутентифицированные и авторизованные пользователи могут получать данные для частной области (и только данные, где пользователю предоставлены разрешения)
на данный момент я думаю о том, чтобы только разрешить пользователям с активным сеансом использовать API. Для авторизации пользователей я буду использовать паспорт, а для разрешения мне нужно что-то реализовать для себя. Все на вершине ПРОТОКОЛ HTTPS.
может кто-нибудь предоставить некоторые лучшие практики или опыт? Есть ли недостаток в моей "архитектуре"?
5 ответов:
У меня была та же проблема, которую вы описываете. Веб-сайт, который я создаю, можно получить с мобильного телефона и из браузера, поэтому мне нужен api, чтобы позволить пользователям регистрироваться, входить в систему и выполнять некоторые конкретные задачи. Кроме того, мне нужно поддерживать масштабируемость, один и тот же код, работающий на разных процессах/машинах.
потому что пользователи могут создавать ресурсы (ака POST/PUT действия) вам нужно защитить свой api. Вы можете использовать oauth или вы можете построить свое собственное решение, но имейте в виду, что все решения могут быть нарушены, если пароль действительно легко обнаружить. Основная идея состоит в том, чтобы аутентифицировать пользователей, используя имя пользователя, пароль и токен, он же apitoken. Этот apitoken может быть создан с помощью node-uuid и пароль может быть хэширован с помощью pbkdf2
затем вам нужно сохранить сеанс где-то. Если вы сохраните его в памяти в простом объекте, если вы убьете сервер и перезагрузите его снова, сеанс будет уничтожен. Кроме того, это не масштабируемый. Если вы используете haproxy для балансировки нагрузки между машинами или просто используете workers, это состояние сеанса будет сохранено в одном процессе, поэтому, если один и тот же пользователь перенаправлен на другой процесс/машину, ему нужно будет снова пройти проверку подлинности. Поэтому вам нужно хранить сеанс в общем месте. Обычно это делается с помощью Redis.
когда пользователь аутентифицируется (имя пользователя+пароль+apitoken), создайте другой токен для сеанса, aka accesstoken. Опять же, с узлом-uuid. Отправьте пользователю accesstoken и идентификатор пользователя. Идентификатор пользователя (ключ) и accesstoken (значение) хранятся в redis с истекшим временем, например 1h.
теперь каждый раз, когда пользователь выполняет какую-либо операцию с помощью REST api, ему нужно будет отправить идентификатор пользователя и accesstoken.
Если вы разрешите пользователям регистрироваться с помощью REST api, вам нужно будет создать учетную запись администратора с администратором apitoken и сохранить их в мобильном приложении (зашифровать имя пользователя+пароль+apitoken), потому что новые пользователи не будет апитокена, когда они зарегистрируются.
веб также использует этот api, но вам не нужно использовать apitokens. Вы можете использовать express с хранилищем redis или использовать тот же метод, описанный выше, но минуя проверку apitoken и возвращая пользователю идентификатор пользователя+accesstoken в файле cookie.
Если у вас есть личные области, сравните имя пользователя с разрешенными пользователями при их аутентификации. Вы также можете применить роли к пользователи.
резюме:
альтернативой без apitoken было бы использовать HTTPS и отправлять имя пользователя и пароль в заголовке авторизации и кэшировать имя пользователя в redis.
Я хотел бы внести этот код в качестве структурного решения поставленного вопроса, в соответствии (я надеюсь) с принятым ответом. (Вы можете очень легко настроить его).
// ------------------------------------------------------ // 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"
Я просто законченный пример приложения, которое делает это в довольно простой, но ясный путь. Он использует Мангуст с mongodb для хранения пользователей и паспорта для управления auth.
есть много вопросов по поводу отдыха двиг вот так. Они наиболее актуальны для вашего вопроса:
в основном вам нужно выбрать между использованием ключей API (наименее безопасный, поскольку ключ может быть обнаружен несанкционированным пользователем), ключом приложения и комбинацией токенов (средний) или полной реализацией OAuth (наиболее безопасный).
Если вы хотите иметь полностью заблокированную область вашего веб-приложения, к которой могут получить доступ только администраторы из вашей компании, то авторизация SSL может быть для вас. Это гарантирует, что никто не сможет подключиться к экземпляру сервера, если у них нет авторизованного сертификата, установленного в их браузере. На прошлой неделе я написал статью о том, как настроить сервер: статьи
Это одна из самых безопасных настроек, которые вы найдете, поскольку их нет имя пользователя / пароли участвуют, поэтому никто не может получить доступ, если один из ваших пользователей не передаст ключевые файлы потенциальному хакеру.