openwhisk
在许多情况下,例如企业对消费者的应用程序,最好让用户自己注册,然后让管理员批准或拒绝该帐户或特定帐户的权限。 在本教程中,我教您如何实现这样的系统。 这主要是防止黑客通过暴力手段进入系统的一种手段。
本教程中的应用程序基于IBM无服务器架构OpenWhisk。 与需要持续运行的进程的普通Node.js应用程序相比,无服务器架构仅在需要时才运行该进程,从而降低了成本。
构建应用程序所需的条件
- 一个免费的Bluemix帐户
- JavaScript和HTML知识
“本教程显示了一个非常简单的应用程序,该应用程序允许用户自己注册,然后让管理员批准或拒绝他们的请求。 ”
步骤1. Cloudant数据库
OpenWhisk的设计假定应用程序所需的任何信息都存储在其他位置,例如,在Cloudant数据库中。 以下是创建新数据库的步骤:
- 登录到Bluemix控制台。
- 点击左上方的菜单图标,然后点击数据与分析 。
- 单击创建数据和分析服务 。
- 单击Cloudant NoSQL DB 。
- 将服务命名为“
Acct-approval-db
”,然后单击“ 创建” 。 - 转到服务。
- 单击服务凭据 ,然后单击新建凭据 。
- 点击添加 。
- 单击查看凭据,然后将URL值复制到文本文件中。 URL编码主机名,端口,用户ID和密码。
- 单击管理 ,然后单击启动 。
- 单击左侧的数据库 (如有必要,使用<->图标查看各个图标的含义)。
- 单击创建数据库并命名您的数据库帐户。
步骤2.自注册操作
大多数功能需要两个OpenWhisk操作。 第一个向用户提供要填写的表单,第二个向用户填写表单后做出响应。这是您的操作方式:
- 单击左上方的菜单图标,然后单击“ 功能” 。
- 单击开始创建 。
- 单击创建动作 。
- 单击创建新程序包 。
- 将软件包命名为“
Acct-approval
”,然后单击“ 创建” 。 - 将动作命名为“
self-reg-form
”,然后点击创建 。 不必担心将其作为网络操作启用; API定义将在以后执行。 - 将源代码替换为我的自我注册表单脚本中的代码。
- 单击左上方的管理以返回到操作管理页面。
- 单击创建动作 。
- 将新操作命名为“
self-reg-handler
”,选择包Acct-approval ,然后单击Create 。 - 将源代码替换为我的请求表单处理程序脚本中的代码。
- 单击左上方的管理以返回到操作管理页面。
- 点击左侧栏中的API ,然后点击创建托管API 。
- 将API命名为“
acct-approval
”。 您无需担心指定基本路径。 - 单击创建操作 。
- 创建以下两个操作:
参数 第一次操作 第二次手术 路径 / self_reg_form / self_reg_handler 动词 得到 开机自检 包含动作的包裹 认可 认可 行动 自我调节形式 自我调节者 响应内容类型 文字/ HTML 文字/ HTML - 向下滚动并单击保存并公开 。
- 复制API的路由。
- 浏览到<您的URL> / self_reg_form。 (查看我的表格 。)
自我注册表格
自我注册表单JavaScript代码非常不言自明,尽管其中有两部分可能需要进行一些讨论:
- 该操作将HTML参数发送给OpenWhisk一个参数(名为“
html
”)。 在这里,此参数是模板文字 (请参见下面的“相关主题”),它是一个以反引号(`)开头和结尾的字符串。 这种字符串可以包含多行,这使HTML更具可读性。return { html: ` <html> . . . </html> ` };
- 该操作使用具有默认主题的Bootstrap 3.3外观。 本教程中的所有其他操作也都使用它。
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css"> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
自我注册处理程序
表单处理程序更加复杂。 首先,它需要建立数据库连接。 您需要将cloudantUrl
值替换为数据库的那个。
// Replace with your value
var cloudantUrl = "https://<<redacted>>-b18c-93f1b9986506-bluemix.cloudant.com";
最好将连接数据库的命令放在main
功能之外。 执行动作后,OpenWhisk系统会将进程保持运行几分钟,直到需要资源为止。 如果在此期间有另一个调用来,OpenWhisk只会使用新参数重新运行main
。 您可以将连接作为全局变量并仅创建一次,而不必每次都创建与数据库的新连接。
var cloudant = require("cloudant")(cloudantUrl);
var mydb = cloudant.db.use("accounts");
main
函数从参数params
的表单接收值。 但是它不能立即响应,因为它需要与数据库通信,这是一个异步过程。 当OpenWhisk操作需要执行这样的异步过程时,它们将返回Promise对象 (请参见下面的“相关主题”)。
Promise对象的构造函数接收一个参数,一个函数。 该函数接收两个回调函数,一个回调函数成功调用,另一个调用失败。 该功能在某些时候由OpenWhisk系统异步执行。 在某些回调的结尾,它将调用success
函数或failure
函数,并且该操作将终止。
return new Promise(function(success, failure) {
.
.
.
// Return the response
success({html: reqStatus(body.status, params.uid)});
}); // mydb.get
}); // Promise object
Promise功能要做的第一件事是检查是否已经有该名称的帐户。 如果是这样,则回调返回适当的响应。
mydb.get(params.uid, function(err, body) {
.
.
.
// Return the response
success({html: reqStatus(body.status, params.uid)});
}); // mydb.get
适当的响应由reqStatus
函数确定。 这是一个非常简单的函数,它使用statusResponse
表确定要提供的响应。 此函数使用模板文字的另一个功能。 您可以在模板文字中使用语法${< expression >}
,并将其替换为表达式结果。
// Table of statuses and their messages
statusResponse = {
"unapproved": "Your request is waiting, please have some patience",
"approved": "Good news, we approved your request",
"rejected": '<b style="color:red">Rejected </b>. ' +
'We don\'t want your business',
"submitted": "Your request has just been submitted," +
" it is awaiting approval"
};
reqStatus = function(status, user) {
return `
<html>
.
.
. <body>
<p>Hello, ${user}. ${statusResponse[status]}</p>
</body>
</html>
`;
}
mydb.get
有两种可能的方式返回错误。 首先是如果没有这样的帐户,这就是您所期望的。 另一个是如果确实存在错误情况。 如果错误代码为404,则表明未找到用户。 在这种情况下,您将为新用户创建一个条目。 新条目的标识符是用户ID。 if
语句以返回退出函数而结束(这是必需的,因为success
调用在回调中)。
// No user yet, register the entry
if (err != null && err.statusCode == 404) {
mydb.insert( {
_id: params.uid,
pwd: saltHashPassword(params.pwd),
status: "unapproved"
}, // Data to insert
function() {
success({html: reqStatus("submitted", params.uid)})
}); // mydb.insert
return ;
} // No user yet
存储未加密的密码是一种不好的做法。 此操作使用带有盐的哈希,这是行业标准。 您可以在“ 使用NodeJS crypto的Salt Hash密码 ”中阅读有关内容。
步骤3.批准动作
下一步是创建批准用户的操作。 使用Create 03_approval.js创建一个名为admin-approval-form的新操作。 创建一个名为“ /admin_approval_form
”的新API操作,该操作通过运行操作来响应GET
请求。
您可以转到URL进行查看(请参阅我的版本 )。 您将看到一个UID列表,每个都有两个按钮:一个要批准,一个要拒绝。
![](https://i-blog.csdnimg.cn/blog_migrate/0e2c700dd09e3e2db5dcfeaf273c76ae.png)
它是如何工作的?
此功能只有一个操作和一个网页。 如果使用适当的参数调用,它将修改数据库条目以批准或拒绝请求。 无论哪种方式,它都会用新的请求表进行响应。
重要的是只有授权的管理员才能批准或拒绝请求。 为了简化软件,我在编写该应用程序时假设一次只有一个管理员处于活动状态,而他只需几分钟即可做出决定。 这样可以确保该表与接收决策的表来自同一应用程序运行。
有了这些假设,跟踪两个关键就足够了。 旧密钥是上次调用main
密钥,它出现在任何授权请求中。 新密钥是您要返回给用户的网页上的密钥,因此它出现在对下一个呼叫的授权请求中。
var oldKey, newKey = genRandomString(15);
function main(params) {
oldKey = newKey;
newKey = genRandomString(15);
.
.
.
}
可以在Promise对象中使用此功能。 它返回带有新用户列表的网页。
var responseFunc = function(success, failure) {
.find
方法使您可以对Cloudant数据库进行查询。 在这种情况下,请选择状态字段等于unapproved
文档。 请参阅Bluemix Docs网站上的Cloudant Query部分,以获取有关构建更复杂查询的帮助。
mydb.find({
"selector": {
"status": "unapproved"
}
},
回调函数接收两个参数,错误条件(如果有的话)和与选择器匹配的条目列表。 用户标识符是列表中的结构_id(这是Cloudant中文档标识符的保留名称)中的参数之一。 map方法(请参见下面的“相关主题”)在每个条目上运行一个函数。 第一次调用从mydb.find
提供的数据中创建用户标识符列表。 第二个将用户标识符列表转换为HTML表格行列表,并带有必要的按钮来批准和拒绝请求。
function(err, data) {
var userList = data.docs.map(function(item) {
return item["_id"]
});
var rowsList = userList.map(function(user) {
return `
<tr>
<td>${user}</td>
这些按钮实际上是指向同一页面的链接,但带有用于批准或拒绝请求的参数。 有三个参数:
-
key
:现在是新密钥(当收到请求时将是旧密钥)。 -
action
:操作(approve
或reject
)。 -
uid
:用户标识符。 该值来自用户,因此可能包括代码注入。 为确保您没有任何此类恶意用户,请使用querystring模块的querystring.escape(str))
函数。 (请参阅下面的“相关主题”。)
<td>
<a href="admin_approval_form?key=${newKey}&action=approve&uid=${qs.escape(user)}">
<button class="btn btn-success">Approve</button>
</a>
</td>
<td>
<a href="admin_approval_form?key=${newKey}&action=reject&uid=${qs.escape(user)}">
<button class="btn btn-danger">Deny</button>
</a>
</td>
</tr> `
});
最后,检查是否有任何行。 如果不是,则返回一条消息。 如果存在,则使用reduce
方法将它们串联。 (请参阅下面的“相关主题”。)
var rows;
// No data is a special case
if (rowsList.length > 0)
rows = rowsList.reduce(function(a, b) {return a+b;});
else
rows = '<tr><th colspan="3">No users to approve</th></tr>';
现在,所有行都作为一个字符串,您可以将该字符串嵌入到HTML文档中,并在调用success
函数时将其作为html参数发送。 所有这些都发生在responseFunc
定义内的mydb.find
函数的回调中。
var html = `<html>
.
.
.
<table class="table">
<tr>
<th>User Name</th>
<td colspan="2"></td>
</tr>
${rows}
</table>
.
.
.
</html> `;
success({html: html});
}); // mydb.find
}; // responseFunc
如果没有密钥,或者该密钥不是正确的旧密钥,则没有请求进行处理。 在这种情况下,您刚定义的请求函数是Promise对象的正确函数。
// If we are not authenticated, then there is no action to take
if (params.key != oldKey)
return new Promise(responseFunc);
如果您超过了这一点,则说明密钥是正确的。 在这种情况下,有必要满足该请求。 为此,返回一个Promise对象,该对象的函数首先修改状态,然后运行响应函数,该函数返回带有未决请求和用于处理请求的按钮的表。
if (params.action == "approve")
return new Promise(function(success, failure) {
modifyStatus(params.uid, "approved", function() {
responseFunc(success, failure);
}); // modify status
}); // return new Promise
modifyStatus
函数检索uid的完整条目,修改状态,写入新条目,并在写入新状态后的callback
中调用调用responseFunc
的callback
函数。
var modifyStatus = function(uid, newStatus, callback) {
mydb.get(uid, function(err, body) {
body.status = newStatus;
mydb.insert(body, function(err, data) {
callback();
}); // mydb.insert
}); // mydb.get
};
步骤4.登录操作
使用类似于请求的两个动作(表单和处理程序)来实现登录。 创建这两个:
动作名称 | 动作码 | API路径 | API动词 |
---|---|---|---|
登录表单 | 这里 | /登录 | 得到 |
登录表单处理程序 | 这里 | / login_handler | 开机自检 |
登录表单仅返回固定HTML,因此不言自明。 登录表单处理程序更为复杂,但是大多数使用与其他操作相同的构造。 唯一的新内容是检查密码,这在“ 使用NodeJS crypto的Salt Hash密码”中进行了说明。
结论
本教程显示了一个非常简单的应用程序,该应用程序允许用户自己注册,然后让管理员批准或拒绝他们的请求。 在现实生活中的应用程序中,将要求用户在注册之前要求其提供其他信息,并且其中的某些信息可能会先经过验证,然后再由管理员查看。 获得允许重设密码的其他信息尤其重要。
openwhisk