如何使用node.js实现安全的REST API
我开始用node.js,express和mongodb来规划一个REST API。 该API提供了一个网站(公共和私人领域)的数据,也许以后是一个移动应用程序。 前端将与AngularJS一起开发。
有几天我读了很多关于保护REST API的内容,但是我没有得到最终的解决scheme。 据我所知是使用HTTPS来提供基本的安全性。 但是我怎么能保护这个用例中的API:
-
只有网站/应用程序的访问者/用户才能获取网站/应用程序的公共区域的数据
-
只有经过身份validation和授权的用户才能获取私有区域的数据(并且只有用户授予权限的数据)
目前我想只允许有活动会话的用户使用API。 为了授权用户,我将使用护照并获得许可,我需要为自己实施一些东西。 全部位于HTTPS的顶部。
有人可以提供一些最佳实践或经验吗? 我的“架构”是否缺乏?
我遇到了同样的问题。 我正在build设的网站可以从手机和浏览器访问,所以我需要一个API来允许用户注册,login和做一些特定的任务。 此外,我需要支持可伸缩性,在不同的进程/机器上运行相同的代码。
因为用户可以创build资源(aka POST / PUT操作),所以你需要保护你的api。 你可以使用oauth,或者你可以build立你自己的解决scheme,但要记住,如果密码真的很容易被发现,所有的解决scheme都可以被破坏。 基本的想法是使用用户名,密码和令牌来authentication用户,也就是apitoken。 这个apitoken可以使用node-uuid生成,密码可以使用pbkdf2进行散列
然后,您需要将会话保存在某个地方。 如果将它保存在一个普通对象的内存中,如果杀死服务器并重新启动,会话将被破坏。 而且,这不是可扩展的。 如果您使用haproxy在机器之间进行负载均衡,或者只是简单地使用worker,则会话状态将存储在一个进程中,因此如果同一用户被redirect到另一个进程/机器,则需要重新进行身份validation。 因此,您需要将会话存储在一个普通的地方。 这通常使用redis完成。
当用户通过身份validation(用户名+密码+ apitoken)为会话生成另一个令牌时,又名accesstoken。 再次,使用node-uuid。 发送给用户accesstoken和userid。 userid(key)和accesstoken(value)存储在redis中,并且过期时间,例如1h。
现在,每当用户使用其余的api执行任何操作时,都需要发送用户标识和访问权限。
如果您允许用户使用其余的api进行注册,则需要创build一个包含pipe理员apitoken的pipe理员帐户,并将其存储在移动应用程序中(encryption用户名+密码+ apitoken),因为新用户不会在使用apitoken他们报名参加。
networking也使用这个API,但你不需要使用apitokens。 您可以使用快递与Redis商店或使用上述相同的技术,但绕过apitoken检查并返回给用户在cookie中的userid + accesstoken。
如果你有私人领域比较用户名和允许的用户进行身份validation。 您也可以将angular色应用于用户。
概要:
没有apitoken的替代scheme是使用HTTPS并在Authorization标头中发送用户名和密码,并在redis中caching用户名。
我希望提供这个代码作为所提出的问题的一个结构性解决scheme,根据(我希望是)接受的答案。 (你可以很容易地定制它)。
// ------------------------------------------------------ // 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来testing:
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"
我刚刚完成了一个示例应用程序,这是一个非常基本的,但清晰的方式。 它使用mongoose和mongodb来存储用户和身份validationpipe理的护照。
在这里有很多关于RESTauthentication模式的问题。 这些与您的问题最相关:
- 保护我的Node.js应用程序的REST API?
- RESTfulauthentication
基本上,您需要select使用API密钥(最不安全,因为密钥可能会被未经授权的用户发现),应用程序密钥和令牌组合(中等)或完整的OAuth实现(最安全)之间进行select。
如果您想要完全locking您的Web应用程序的locking区域,并且只能由贵公司的pipe理员访问,则可能需要SSL授权。 它将确保没有人能够连接到服务器实例,除非他们在浏览器中安装了授权证书。 上周我写了一篇关于如何设置服务器的文章 : 文章
这是最安全的设置之一,因为没有用户名/密码,所以没有人可以获得访问权限,除非你的用户把密钥文件交给潜在的黑客。