如何使用 node.js 实现安全的 REST API
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/15496915/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to implement a secure REST API with node.js
提问by tschiela
I start planning a REST API with node.js ,express and mongodb. The API provides data for a website (public and private area) and maybe later a mobile app. The frontend will be developed with AngularJS.
我开始计划使用 node.js、express 和 mongodb 的 REST API。该 API 为网站(公共和私人区域)以及以后的移动应用程序提供数据。前端将使用 AngularJS 开发。
For some days I read a lot about securing REST APIs, but I don't get to a final solution. As far as I understand is to use HTTPS to provide a basic security. But how I can protect the API in that use cases:
有几天我读了很多关于保护 REST API 的内容,但我没有找到最终的解决方案。据我了解是使用HTTPS来提供基本的安全性。但是我如何在这些用例中保护 API:
Only visitors/users of the website/app are allowed to get data for the public area of the website/app
Only authenticated and authorized users are allowed to get data for private area (and only data, where the user granted permissions)
只有网站/应用程序的访问者/用户才能获取网站/应用程序公共区域的数据
只有经过身份验证和授权的用户才能获取私有区域的数据(并且只有用户授予权限的数据)
At the moment I think about to only allow users with a active session to use the API. To authorize the users I will use passport and for permission I need to implement something for myself. All on the top of HTTPS.
目前我正在考虑只允许具有活动会话的用户使用 API。为了授权用户,我将使用护照,为了获得许可,我需要为自己实现一些东西。一切都在 HTTPS 之上。
Can somebody provide some best practice or experiences? Is there a lack in my “architecture”?
有人可以提供一些最佳实践或经验吗?我的“架构”是否缺乏?
采纳答案by Gabriel Llamas
I've had the same problem you describe. The web site I'm building can be accessed from a mobile phone and from the browser so I need an api to allow users to signup, login and do some specific tasks. Furthermore, I need to support scalability, the same code running on different processes/machines.
我遇到了你描述的同样问题。我正在构建的网站可以通过手机和浏览器访问,所以我需要一个 api 来允许用户注册、登录和执行一些特定任务。此外,我需要支持可扩展性,在不同的进程/机器上运行相同的代码。
Because users can CREATE resources (aka POST/PUT actions) you need to secure your api. You can use oauth or you can build your own solution but keep in mind that all the solutions can be broken if the password it's really easy to discover. The basic idea is to authenticate users using the username, password and a token, aka the apitoken. This apitoken can be generated using node-uuidand the password can be hashed using pbkdf2
因为用户可以创建资源(又名 POST/PUT 操作),所以您需要保护您的 api。您可以使用 oauth,也可以构建自己的解决方案,但请记住,如果密码真的很容易被发现,则所有解决方案都可能被破解。基本思想是使用用户名、密码和令牌(即 apitoken)对用户进行身份验证。这个 apitoken 可以使用node-uuid生成,密码可以使用pbkdf2散列
Then, you need to save the session somewhere. If you save it in memory in a plain object, if you kill the server and reboot it again the session will be destroyed. Also, this is not scalable. If you use haproxy to load balance between machines or if you simply use workers, this session state will be stored in a single process so if the same user is redirected to another process/machine it will need to authenticate again. Therefore you need to store the session in a common place. This is typically done using redis.
然后,您需要将会话保存在某处。如果你将它保存在一个普通对象的内存中,如果你杀死服务器并重新启动它,会话将被破坏。此外,这是不可扩展的。如果您使用 haproxy 在机器之间进行负载平衡,或者您只是使用 worker,则此会话状态将存储在单个进程中,因此如果同一用户被重定向到另一个进程/机器,则需要再次进行身份验证。因此,您需要将会话存储在一个公共位置。这通常是使用 redis 完成的。
When the user is authenticated (username+password+apitoken) generate another token for the session, aka accesstoken. Again, with node-uuid. Send to the user the accesstoken and the userid. The userid (key) and the accesstoken (value) are stored in redis with and expire time, e.g. 1h.
当用户通过身份验证(用户名+密码+apitoken)时,为会话生成另一个令牌,也就是访问令牌。同样,使用 node-uuid。将访问令牌和用户 ID 发送给用户。userid(key)和accesstoken(value)存储在redis中,过期时间为1h。
Now, every time the user does any operation using the rest api it will need to send the userid and the accesstoken.
现在,每次用户使用 rest api 执行任何操作时,都需要发送 userid 和 accesstoken。
If you allow the users to signup using the rest api, you'll need to create an admin account with an admin apitoken and store them in the mobile app (encrypt username+password+apitoken) because new users won't have an apitoken when they sign up.
如果您允许用户使用 rest api 进行注册,则需要使用管理员 apitoken 创建一个管理员帐户并将其存储在移动应用程序中(加密用户名+密码+apitoken),因为新用户在注册时将没有 apitoken他们注册。
The web also uses this api but you don't need to use apitokens. You can use express with a redis store or use the same technique described above but bypassing the apitoken check and returning to the user the userid+accesstoken in a cookie.
网络也使用这个api,但你不需要使用apitoken。您可以将 express 与 redis 存储一起使用,或者使用上述相同的技术,但绕过 apitoken 检查并将用户 ID+访问令牌在 cookie 中返回给用户。
If you have private areas compare the username with the allowed users when they authenticate. You can also apply roles to the users.
如果您有私人区域,请在他们进行身份验证时将用户名与允许的用户进行比较。您还可以将角色应用于用户。
Summary:
概括:


An alternative without apitoken would be to use HTTPS and to send the username and password in the Authorization header and cache the username in redis.
没有 apitoken 的另一种方法是使用 HTTPS 并在 Authorization 标头中发送用户名和密码,并将用户名缓存在 redis 中。
回答by cibercitizen1
I would like to contribute this code as an structural solution for the question posed, according (I hope so) to the accepted answer. (You can very easily customize it).
根据(我希望如此)接受的答案,我想贡献此代码作为所提出问题的结构性解决方案。(您可以非常轻松地自定义它)。
// ------------------------------------------------------
// 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);
This server can be tested with curl:
可以使用 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"
回答by clangager
I just finished a sample app that does this in a pretty basic, but clear way. It uses mongoose with mongodb to store users and passport for auth management.
我刚刚完成了一个示例应用程序,它以一种非常基本但清晰的方式执行此操作。它使用 mongoose 和 mongodb 来存储用户和护照以进行身份验证管理。
回答by Zim
There are many questions about REST auth patterns here on SO. These are the most relevant for your question:
这里有很多关于 REST 身份验证模式的问题。这些与您的问题最相关:
Basically you need to choose between using API keys (least secure as the key may be discovered by an unauthorized user), an app key and token combo (medium), or a full OAuth implementation (most secure).
基本上,您需要在使用 API 密钥(最不安全,因为密钥可能会被未经授权的用户发现)、应用程序密钥和令牌组合(中等)或完整的 OAuth 实现(最安全)之间进行选择。
回答by Ahmed Elkoussy
If you want to secure your application, then you should definitely start by using HTTPS instead of HTTP, this ensures a creating secure channel between you & the users that will prevent sniffing the data sent back & forth to the users & will help keep the data exchanged confidential.
如果您想保护您的应用程序,那么您绝对应该首先使用 HTTPS 而不是 HTTP,这可以确保在您和用户之间创建一个安全通道,以防止嗅探来回发送给用户的数据并有助于保留数据交换了机密。
You can use JWTs (JSON Web Tokens) to secure RESTful APIs, this has many benefits when compared to the server-side sessions, the benefits are mainly:
您可以使用 JWT (JSON Web Tokens) 来保护 RESTful API,这与服务器端会话相比有很多好处,主要有以下好处:
1- More scalable, as your API servers will not have to maintain sessions for each user (which can be a big burden when you have many sessions)
1- 更具可扩展性,因为您的 API 服务器不必为每个用户维护会话(当您有许多会话时,这可能是一个很大的负担)
2- JWTs are self contained & have the claims which define the user role for example & what he can access & issued at date & expiry date (after which JWT won't be valid)
2- JWT 是自包含的,并且具有定义用户角色的声明,例如他可以访问和发布的内容和到期日期(之后 JWT 将无效)
3- Easier to handle across load-balancers & if you have multiple API servers as you won't have to share session data nor configure server to route the session to same server, whenever a request with a JWT hit any server it can be authenticated & authorized
3- 更容易跨负载平衡器处理,如果您有多个 API 服务器,因为您不必共享会话数据,也不必配置服务器将会话路由到同一服务器,只要带有 JWT 的请求命中任何服务器,就可以对其进行身份验证& 授权
4- Less pressure on your DB as well as you won't have to constantly store & retrieve session id & data for each request
4- 减轻您的数据库压力,并且您不必为每个请求不断存储和检索会话 ID 和数据
5- The JWTs can't be tampered with if you use a strong key to sign the JWT, so you can trust the claims in the JWT that is sent with the request without having to check the user session & whether he is authorized or not, you can just check the JWT & then you are all set to know who & what this user can do.
5- 如果您使用强密钥对 JWT 进行签名,则 JWT 将不会被篡改,因此您可以信任随请求发送的 JWT 中的声明,而无需检查用户会话以及他是否获得授权,您只需检查 JWT,然后您就可以知道该用户可以做什么以及可以做什么。
Many libraries provide easy ways to create & validate JWTs in most programming languages, for example: in node.js one of the most popular is jsonwebtoken
许多库提供了在大多数编程语言中创建和验证 JWT 的简单方法,例如:在 node.js 中最流行的一种是jsonwebtoken
Since REST APIs generally aims to keep the server stateless, so JWTs are more compatible with that concept as each request is sent with Authorization token that is self contained (JWT)without the server having to keep track of user session compared to sessions which make the server stateful so that it remembers the user & his role, however, sessions are also widely used & have their pros, which you can search for if you want.
由于 REST API 通常旨在保持服务器无状态,因此 JWT 与该概念更兼容,因为每个请求都使用自包含(JWT) 的授权令牌发送,与会话相比,服务器不必跟踪用户会话。服务器是有状态的,以便它记住用户及其角色,但是,会话也被广泛使用并具有其优点,您可以根据需要进行搜索。
One important thing to note is that you have to securely deliver the JWT to the client using HTTPS & save it in a secure place (for example in local storage).
需要注意的一件重要事情是,您必须使用 HTTPS 将 JWT 安全地交付给客户端并将其保存在安全的地方(例如在本地存储中)。
You can learn more about JWTs from this link
您可以从此链接了解有关 JWT 的更多信息
回答by ExxKA
If you want to have a completely locked down area of your webapplication which can only be accessed by administrators from your company, then SSL authorization maybe for you. It will insure that no one can make a connection to the server instance unless they have an authorized certificate installed in their browser. Last week I wrote an article on how to setup the server: Article
如果您想拥有一个完全锁定的 Web 应用程序区域,该区域只能由您公司的管理员访问,那么 SSL 授权可能适合您。它将确保任何人都无法连接到服务器实例,除非他们在浏览器中安装了授权证书。上周我写了一篇关于如何设置服务器的文章:文章
This is one of the most secure setups you will find as there are no username/passwords involved so no one can gain access unless one of your users hands the key files to a potential hacker.
这是您会发现的最安全的设置之一,因为不涉及用户名/密码,因此除非您的用户之一将密钥文件交给潜在的黑客,否则任何人都无法访问。

