ibm cloud
在上一篇文章(“ 将您自己的授权代理添加到第三方应用程序 ”)中,您学习了如何在用户的浏览器和您无法控制的第三方应用程序之间添加授权代理。 该代理的一个问题是它没有与用户通信的方法。 因此,当用户无权执行某项操作时,尝试将失败,而不会通知用户出了什么问题以及如何批准该操作。
在本文中,您将学习如何解决此问题。 我向您展示了如何向用户发送一个小的脚本,该脚本每秒询问代理是否有任何消息。 代理收到消息后,该消息将覆盖屏幕,直到被确认为止。
构建应用程序所需的条件
- IBM Cloud帐户。 ( 免费试用IBM Cloud。)
- 了解HTML和JavaScript。
- 了解MEAN应用程序堆栈(至少Node.js和Express)。
- 可以将Node.js应用程序上载到IBM Cloud的开发环境,例如Eclipse 。
“在本文中,您将学习代理如何产生弹出消息以与用户通信。 您向用户发送一个小脚本,该脚本每秒询问一次代理是否有任何消息。 收到消息后,它将覆盖屏幕,直到确认消息为止。 ”
向用户发送消息
向用户发送消息在服务器端和客户端都需要一些代码。 要遵循以下步骤,请浏览至https://github.com/qbzzt/authz-proxy2/blob/master/app.js,以读取经过修改的(来自上一篇文章 )app.js。
服务器端
app.js应用程序中需要进行一些更改,分别用于cookie会话标识符,存储消息和发送消息。
Cookie会话标识符
- 在服务器端,第一个要求是能够区分不同的会话。 区分会话的最简单方法是使用随机生成的cookie。 Cookie的名称是一个常量,因此我遵循所有使用大写字母命名的惯例。
// The name of the cookie to use for sessions var SESS_COOKIE = "authproxy_session_cookie";
- 在cookie中存储会话ID需要两个软件包:
- cookie-parser ,用于了解从浏览器返回的cookie。
- node-uuid ,它创建唯一的标识符。
// Need UUIDs for session identification var uuid = require("node-uuid");
这两个软件包都需要添加到packages.json中。 - 出于演示目的,应用程序每10秒向创建的最后一个会话发送一条消息。 为此,有必要跟踪应用程序发送的最后一个会话cookie。
var lastUuid = "0"; // if you get getMsg.html, set a cookie app.get('/getMsg.html', /* @callback */ function (req, res, next) { lastUuid = uuid.v4();
- 与读取cookie值相反,设置cookie不需要任何特殊的程序包。
res.cookie(SESS_COOKIE, lastUuid);
- getMsg.html文件存储在静态目录中,此调用后是指定该目录文件的调用,并且将按原样提供该文件。 对
next()
的调用告诉框架继续进行处理,因此将通过目录提供该服务,但使用您刚刚设置的cookie。next(); });
储存讯息
- 当应用程序需要向用户发送消息时,消息必须在某处等待,直到浏览器代码要求它为止。 消息以会话ID为键存储在哈希数组中。
// The messages to send sessions var sessionMsg = {}; var sendSessionMsg = function(id, html) { sessionMsg[id] = {text: html}; };
- 出于演示目的,每几秒钟发送一次带时间戳的消息很有用。 该调用每10秒执行一次。
setInterval(function() {sendSessionMsg(lastUuid, "M<b>ess</b>age " + new Date());}, 10000);
传送讯息
最后,当浏览器要求输入消息(如果有)时,请读取Cookie以标识会话并进行响应。 删除邮件,因为您只需要发送一次。
// If there is a message for the session, respond with it
app.get("/msg", require("cookie-parser")(), function (req, res) {
var id = req.cookies[SESS_COOKIE];
var msg = sessionMsg[id];
if (typeof msg === "undefined") {
res.send(""); // No message
} else {
sessionMsg[id] = undefined;
res.send(msg.text);
}
});
请注意,此技术会阻止某些原本会代理到服务器的URL。 由于您要添加特定服务的授权,因此很容易确保您阻止的URL未被该应用程序使用。 另一方面,更通用的代理将必须具有更复杂的解决方案。
客户端
在客户端,您可以在public / getMsg.html上看到一个演示。 本演示使用Bootstrap和Angular库。 它包括两部分:弹出消息(仅在出现消息时显示)和iframe(显示实际代理)。
在GitHub上显示getMsg.html,然后继续。
弹出消息
-
ng-show
属性定义了显示标签的条件。 在这种情况下,条件是消息不为空。ng-click
属性是ng-click
标签(在本例中为显示消息的整个部分)时执行的命令。 浏览器删除该消息,然后返回到隐藏状态。<div class="fill-window overlay" ng-show="msg !== ''" ng-click="msg = ''">
- 消息由服务器以HTML格式提供,以允许链接和其他形式的丰富内容。 但是,显示从Internet获得HTML是有风险的。 这就是您如何获得跨站点脚本攻击。 Angular允许您执行此操作,但是您必须非常明确并执行以下步骤(如源代码所示):
- 导入清理库。
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-sanitize.js"></script>
- 声明模块使用该库。
var myApp = angular.module("myApp", ["ngSanitize"]);
- 将
$sce
作为参数添加到控制器功能。myApp.controller("myCtrl", function($scope, $http, $sce) {
- 为
$sce.trustAsHtml
创建范围变量。 此步骤允许从控制器创建函数外部调用$sce.trustAsHtml
。$scope.trust = $sce.trustAsHtml;
- 使用该范围变量创建一个值以传递给
ng-bind-html
属性。 这是弹出div元素内的唯一HTML。
<p ng-bind-html="trust(msg)"></p> </div>
- 导入清理库。
代理iframe
代理非常简单。 这只是嵌入实际代理的iframe元素。 因为代理是同一应用程序的一部分,并且来自同一域,所以不需要跨域请求。
<iframe src="./index.html" class="fill-window">
</iframe>
CSS类
样式表包括两个类: fill-window
和overlay
。
-
fill-window
类指定标记应填充整个窗口。 该位置在窗口内是绝对的,并在左上角指定一个位置。 高度和宽度指定内容要填充整个窗口。.fill-window { position: absolute; top: 0; left: 0; height: 100%; width: 100%; display: inline-block; }
-
overlay
类指定弹出消息主要是不透明的(60%),并在黑色背景上显示大的白色字母。z-index
属性将叠加层放置在大多数其他内容的前面。.overlay { background-color: rgba(0, 0, 0, 0.6); z-index: 9999; color: white; font-size: 50px; display: inline-block; text-align: center; }
取得讯息
- 除非您实际上可以从服务器检索消息,否则以上所有都是无用的。 要获取它们,浏览器端代码使用
$http
Angular函数。$scope.getMsg = function() {
- 使用参数调用
$http
函数以从我们的服务器获取/msg
。 Cookie不需要包含任何内容,因为它是自动发送的。$http({ method: "GET", url: "/msg"
-
$http
函数返回一个具有then
函数的对象。 此函数具有两个函数,一个函数在操作成功时调用,另一个函数未成功调用。}).then( function(response) {
- 如果HTTP请求成功,并且您收到了一条真实的消息(不仅是一个空字符串),请添加它并在message变量中添加新行。 如果在删除旧邮件之前收到新邮件,则可以显示多封邮件。
if (response.data !== "") $scope.msg += response.data + "<br />"; },
- 错误功能仅显示调试的响应文本。 在生产应用程序中,这可能会被重试多次,然后返回用户友好的错误消息。
function(response) { console.log("Error " + response.statusText); } ); };
-
setInterval
函数运行一个函数,在这种情况下,该函数每千毫秒(换句话说,每秒)重复一次获取消息的函数。 这是浏览器代码实际检查消息的方式。// Look for a message every second setInterval($scope.getMsg, 1000);
与代理集成
要与代理集成,请修改检查授权的调用。
- 首先,通过添加以下内容来确保在函数本身之前对cookie进行了解析:
require("cookie-parser")(),
// Authorize some requests, reject others app.get("/transaction/:amt/:debit/:credit", require("cookie-parser")(), function(req, res, next) {
- 然后,当授权被拒绝时,使用
sendSessionMsg
发送消息,然后使用res.send
响应HTTP请求。if (amt >= 10000) {// Send the user a message that this is unauthorized sendSessionMsg(req.cookies[SESS_COOKIE], "Over the authorized limit"); // res.send closes the connection res.send(""); }
- 您可以在http://authz-proxy2.mybluemix.net/proxyMsg.html上查看代理的行为。 尝试禁止转移后,该应用程序不会显示任何帐户。 尝试转移10,000,然后看到以下消息“超出授权限制”。
- 尝试直接运行事务(例如, http://authz-proxy2.mybluemix.net/transaction/10/bank-account/bank-account )以了解原因。 服务器发回新的帐户,类型和余额列表。 但是,当代理响应授权失败时,它只会发送一个空字符串。 要验证授权失败,请注意, 9,999美元的交易返回一个帐户列表,而10,000美元的交易则没有。 ( 注意:这两个事务链接可能无法从已经登录到应用程序的浏览器中正常工作。)
- 有几种可能的方法可以解决此问题,但是最简单的方法是将浏览器重定向到不执行任何操作的事务。
res.redirect("/transaction/0/a/a");
例如,您可以将0美元从一个不存在的帐户转移到同一帐户。 您可以在http://authz-proxy2.mybluemix.net/proxyMsg2.html上看到此功能。 为了显示问题和解决方案,该应用程序有两个proxyMsg文件。 为了使两者区别开来,第二个会话的cookie较长:
if (req.cookies[SESS_COOKIE].length === 36) // res.send closes the connection res.send(""); else res.redirect("/transaction/0/a/a");
完善的授权政策
用户收到的消息是HTML,这是有意选择的。 使用HTML,您可以包含指向该过程的说明以及不允许执行某些操作的原因的链接。 您还可以包括指向用户界面的链接,以启动批准过程,无论是针对操作本身还是允许用户将来无需执行批准过程即可执行类似的操作。
附加功能
本文显示了一个示例程序,旨在展示一种特定的技术。 在生产环境中使用时,有些功能可以改进此技术。
应用程序cookie
代理缺少的一项功能是对cookie的支持。 有两种支持cookie的方法。 一种是简单地中继它们,将cookie从服务器HTTP响应发送到浏览器,将cookie从浏览器请求发送到服务器。 另一种是将它们存储在代理服务器上的“ cookie罐”中,而不要将其传递给浏览器。 第二种方法需要较少的带宽,但是如果浏览器上JavaScript代码需要访问cookie,则失败。
支持绝对链接
在大多数情况下,代理不必更改HTML中的任何链接。 一个例外是链接是绝对的,并且编码原始服务器的名称。 通常,您可以修改代理中的功能以进行搜索并替换为HTML(可能还有JavaScript)。
在更复杂的情况下,或者如果需要更改目录名称,则可以使用IBM Security Access Manager for Web使用的相同技术。 在IBM Redpaper的 “ 用于电子商务的IBMTivoli®Access Manager:连接和链接 ”中解释了这些技术(使用产品的旧名称)。
强制使用代理
当前,用户可以通过直接转到后端服务来解决授权问题。 在第三方应用程序中,您可以按照以下步骤禁用后端服务:
- 将应用程序上的用户密码更改为随机字符串,并将用户名保留为数据库表中的新映射。
- 在用户访问代理时对用户进行身份验证(如“ 将Google reCAPTCHA添加到您的IBM Cloud Node.js应用程序 ”中所述;请参阅标题为“使用IBM Cloud单点登录服务结合reCAPTCHA”一节)。
- 当用户登录到代理时,请使用数据库中的密码将用户登录到应用程序。 这样,用户将不知道其密码,因此如果没有代理就无法登录自己。 但是,您还需要禁用密码重置机制。
结论
通过本文以及“ 将您自己的授权代理添加到第三方应用程序 ”,您现在可以构建与用户进行通信的授权代理,解释否定授权决定的原因以及如何撤消这些决定。
ibm cloud