通过 Project Silk Widget 访问配置数据





0/5 (0投票)
通过 Project Silk Widget 访问配置数据
配置文件很有用。它们提供了一种将敏感数据隔离在单个位置的方法,该位置可以免受应用程序的侵害。在 ASP.NET 中,DPAPI 等工具允许您加密配置的各个部分,以避免泄露敏感的连接字符串和设置。在本文中,我将展示如何构建一个(使用 Project Silk 小部件结构)从小部件拉取配置数据的实用程序。我还将解释一些安全技巧,以帮助限制对客户端和服务器上配置文件中数据的访问。
首先,您应该查看 Project Silk 文档,因为 Project Silk 是我们的基础。我们将构建一个实用程序小部件,这意味着该小部件仅用于处理数据,而不是呈现界面。您可以通过两种方式构建此小部件:
- 在服务器上动态生成脚本。此方法使用 ASP.NET 处理程序生成与配置值配合使用所需的 JavaScript。值将嵌入到脚本中,因此您无需往返服务器即可检索它们。您可以将 JavaScript 构建为一系列带有值的变量,或者构建一个在应用程序中引用的对象。
- 构建一个通过 Ajax 调用提供配置的服务。此方法可以使用 Project Silk 的 Data Manager 来简化返回配置对象的流程。您可以在服务器上创建配置对象(并返回 JSON 对象),或者在客户端创建 JSON 对象并用数据填充它。
有了这些选项和不同的实现,您就有很多选择。选择哪种技术取决于您的编码标准和开发需求。在服务器上动态构建 JavaScript 允许您构建一个包含所有所需数据的单个组件。通过使用 StringBuilder(或类似对象),您可以构建一个简单的变量集,其中包含来自配置文件中的所有值。此方法的缺点是您无法获得 Visual Studio(或您的 IDE)可以提供的优势。IntelliSense 和 IDE 调试等工具将不可用,但 Internet Explorer 10 开发人员工具等浏览器集成的各种工具可能就是您所需要的。
使用与 Web 服务通信的 JavaScript 组件是解决客户端系统在没有服务器协助的情况下无法从配置文件检索数据的问题的另一种方法。这就是本文将要描述的模型。此方法的缺点是设计复杂,但您可以像编写 JavaScript 一样编写 JavaScript,并利用 IDE 的强大功能。此方法还可以为下一位开发人员提供更易于阅读的代码,这对于在团队环境中构建软件的开发人员来说是一个优势。
从安全开始
与任何项目一样,首先要考虑应用程序资产的风险。在这种情况下,资产就是配置设置。配置数据可以是无害的,也可以是敏感的信息,这取决于它的用途和目的。您需要确定:
- 客户端需要哪些配置设置?
- 为什么您需要它们?
- 泄露这些数据的风险是什么?
请记住,客户端计算机对您的应用程序来说是一个非常不友好的环境。一旦数据到达客户端,就可以对其进行操纵、滥用和分析,以提取其中可用的任何恶意信息。这就引出了另一个安全原则:切勿在客户端存储敏感信息。这个警告通常是指 ViewState 和 HiddenFields 等对象,但同样适用于我将演示的配置小部件。
下一个安全挑战是让配置小部件看不到被视为敏感的设置。您可以通过将配置数据请求限制为已知的安全数据来做到这一点。例如,如果您有一个名为“DocPath
”的设置,它存储应用程序上传的文件保存到服务器硬盘驱动器上的文件夹的完整路径,您不希望这些数据泄露到客户端。拥有一个 Web 应用程序具有写入权限的文件夹的完整路径,在不法分子手中可能会造成极大的危害。另一方面,“DefaultAddress
”这样的设置,它只提供一个默认字符串地址,用于在未提供地址时进行事务处理,暴露它不成问题。
最后,您需要确保请求和响应都经过约束和清理过程。来自调用组件(例如 jQuery 小部件或 JavaScript 组件)的请求必须受到约束,使其成为已识别、已批准的配置设置。在服务器端,您需要验证请求是否已批准(换句话说,您会进行两次验证——一次在客户端,一次在服务器端),并拒绝任何无效输入。在从配置文件检索数据后,您可以清理输出,以确保它不包含任何恶意内容。
设计
使用 Project Silk Data Manager 作为模型,我们的 Configuration Manager 小部件将通过客户端代码和服务器端 Web 服务提供对配置文件的访问。图 1 中的数据流描述了小部件或 JavaScript 对象如何访问配置文件。
当小部件或 JavaScript 对象请求配置设置时,请求首先经过约束过程,以限制客户端可以从服务请求的内容。接下来,我们检查配置值是否已填充,如果已填充,则将其返回给调用者。如果未填充,我们将使用 DataManager 的 SendRequest
方法连接到配置 Web 服务。该服务将清理我们的请求,以限制进入系统的恶意内容量,然后检索相应的配置值。然后验证此值,以确保配置文件未受到损害,然后将其传递给代理对象并返回给客户端。一旦执行返回到客户端,我们就会将其放入我们的缓存对象中以供以后使用。最后,我们将数据返回给调用者。这个过程设计简单,在每个边界都有对数据的各种检查,通过限制进入配置小部件的内容,清理进入服务的内容,以及验证来自配置文件的内容。
开发
让我们从 Web 服务(服务器组件)开始。对于本文,我将在 ASP.NET 中构建它,但这些概念可以应用于任何服务器端开发语言。首先创建一个基本的 ASP.NET Web Forms 项目。您需要访问服务中的一个类,因此请添加一个 App_Code 文件夹。对于非 ASP.NET 开发人员,App_Code 文件夹存储可供 Web 应用程序编译和使用的代码对象。对于这个项目,我们需要一个 Scripts 文件夹、一个 Services 文件夹和一个 Default.aspx 页面。如果您正在构建一个 ASP.NET 4.0 Web Forms 项目,您的解决方案应该看起来像图 2。请注意,应用程序中使用的所有文件都位于我们将引用的位置。
配置服务
Configuration Manager 的第一部分是 Configuration Service。此服务是一个服务器端 Web(或 REST、WCF 等)服务,它使用两种方法来提供配置信息。第一种方法将整个配置作为单个对象返回,第二种方法返回一个单独的配置名称-值对。为简单起见,创建一个类来保存配置对象的名称-值对,并使用适当的属性对其进行装饰以启用 JSON 序列化。在您的 App_Code 文件夹中创建一个新类,并将其命名为 ConstrainedConfiguration.cs。将图 3 中显示的 add 代码添加到文件中。
using System.Runtime.Serialization; namespace sj.models { [DataContract] public class ConstrainedConfiguration { #regionPublic Properties [DataMember] public string Name { get; set; } [DataMember] public string Value { get; set; } #endregion #regionConstructor public ConstrainedConfiguration() { } #endregion } }
现在让我们设置配置文件,以便有一些我们可以使用的设置。将以下设置和值添加到您的配置文件(ASP.NET 的 web.config)
<appSettings> <add key="Setting1" value="This is setting 1"/> <add key="Setting2" value="This is setting 2"/> <add key="Setting3" value="This is setting 3"/> <add key="Setting4" value="This is setting 4"/> <add key="Setting5" value="This is setting 5"/> <add key="Setting6" value="This is setting 6"/> <add key="clientSideApprovedSettings" value="Setting1,Setting2,Setting3"/> </appSettings>
clientSideApprovedSettings
设置是一个设置键列表,可以通过我们的 Configuration Service 向客户端提供。
准备好类并设置好配置文件后,您就可以创建返回 ConstrainedConfiguration
给客户端的服务了。在 Services 文件夹中,创建 ConfigurationService.asmx,这也会在 App_Code 文件夹中创建 ConfigurationService.cs。这是 ConfigurationService Web 服务的代码隐藏文件,我们将在其中编写所有代码。
我们的 Web 服务需要两个方法,一个返回所有已批准的配置,另一个返回单个设置。第一个方法是 GetConfiguration
,如图 4 所示。它向客户端返回 ConstrainedConfiguration
对象的集合。请注意,该方法装饰有 ScriptMethod
属性,该属性将响应格式设置为 JSON。这会将对象序列化并作为 JSON 而不是 XML 返回。
[WebMethod] [ScriptMethod(ResponseFormat=ResponseFormat.Json)] public sj.models.ConstrainedConfiguration[] GetConfiguration() { var settings = System.Configuration.ConfigurationManager.AppSettings; string[] approvedSettings = settings[_approvedSettingsName].Split(','); if (approvedSettings.Length > 0) { sj.models.ConstrainedConfiguration[] ccs = new sj.models.ConstrainedConfiguration[approvedSettings.Length]; for(int i = 0; i < approvedSettings.Length; i++) { sj.models.ConstrainedConfiguration cc = new sj.models.ConstrainedConfiguration(); cc.Name = Microsoft.Security.Application.Encoder.JavaScriptEncode(approvedSettings[i]); cc.Value = Microsoft.Security.Application.Encoder.JavaScriptEncode(settings[approvedSettings[i]]); ccs[i] = cc; } return ccs; } else throw new Exception("Approved settings must be supplied."); } }
首先,我们加载设置以便访问它们,而无需每次都指定完整的命名空间。接下来,我们加载 clientSideApprovedSettings
并按分隔值的逗号分割值。为确保我们不假设 clientSideApprovedSettings
中的值,我们会检查以确保字段名称数组中有值。如果没有,我们会抛出一个异常,由调用服务的客户端代码捕获。如果我们有值,我们将实例化 ConstrainedConfigurations
数组,使用已批准字段的长度作为我们新数组的长度。我们循环遍历每个已批准的设置,并创建一个 ConstrainedConfiguration
对象,存储应用程序设置中的名称和值。请注意,我使用来自 AntiXSS 库 的 JavaScriptEncoder 来编码设置。这样做可以编码 AppSettings 的输出,以限制进入应用程序的恶意代码。您可能会问为什么我没有使用 HTML 编码。答案是数据不会在 HTML 上下文中被使用。我们将把这些数据作为 JavaScript 中的代码对象存储。编码时,请始终注意您将如何使用数据。不要针对您未使用的上下文进行编码。
第二个方法 GetConfigurationSetting
,如图 5 所示,非常相似,但它将检索限制为单个值。
[WebMethod] [ScriptMethod(ResponseFormat = ResponseFormat.Json)] public sj.models.ConstrainedConfiguration GetConfigurationSetting(int settingId) { var settings = System.Configuration.ConfigurationManager.AppSettings; string[] approvedFields = settings[_approvedSettingsName].Split(','); sj.models.ConstrainedConfiguration cc = new sj.models.ConstrainedConfiguration(); if (approvedFields.Contains(settings.Keys[settingId].ToLower())) { cc.Name = Microsoft.Security.Application.Encoder.JavaScriptEncode(settings.Keys[settingId]); cc.Value = Microsoft.Security.Application.Encoder.JavaScriptEncode(settings[settingId]); } return cc; }
Configuration Manager
我们的服务器端组件已准备就绪。现在我们需要构建客户端组件。我使用 Project Silk 的 DataManager 和 DataStore
对象作为 ConfigurationManager 和 ConfigurationStore
对象的概念模型。ConfigurationManager
对象负责加载配置信息,而 ConfigurationStore
是存储该信息的存储库。让我们从 ConfigurationStore
开始,因为它几乎镜像了 Project Silk 的 DataStore。
您首先声明将保存我们配置信息的对象。
_config: {},
此对象将存储通过 ConfigurationManager
检索到的配置设置。接下来,您需要两个方法来从 _config
对象中获取数据。
get: function (token) { return this._config[token]; }, getConfiguration: function () { return this._config; },
get 方法返回一个特定的配置设置,而 getConfiguration
方法返回配置集合。除了 get 方法之外,还有 set 方法。
set: function (token, value) { // Store the data this._config[token] = value; }, clear: function (token) { this._config[token] = undefined; }, clearAll: function () { this._config = {}; },
最后,您需要添加一个 hasData 方法,该方法检查 _config 对象中的对象数量,并返回一个布尔值,指示长度是否大于 0。
hasData: function () { if ({} != this._config) return true; else return false; }
ConfigurationManager
对象填充存储在 ConfigurationStore
中的配置对象。它有两个方法:loadConfiguration
和 loadConfigurationSettting
。loadConfiguration
方法(见图 6)填充 ConfigurationStore
中的整个配置。此方法提供了一个回调参数,可以在单页应用程序(如 HTML5 应用程序)中使用。
loadConfiguration: function (callback) { ///<summary>Returns the populated configuration object</summary> ///<param type="object">callback to execute after the configuration has returned to the client</param> var that = this; if (!sj.configurationStore.hasData()) { sj.dataManager.sendRequest({ url: "/services/ConfigurationService.asmx/GetConfiguration", type: "POST", dataType: "json", contentType: "application/json; charset=utf-8", success: function (results) { $.each(results.d, function (i, element) { sj.configurationStore.set(element.Name, element.Value); }); if (undefined !== callback) callback(sj.configurationStore.getConfiguration()); }, error: function (ex) { throw ex.responseText; } }); } else { if (undefined !== callback) callback(sj.configurationStore.getConfiguration()); } },
首先,我们将 *this* 放入一个名为 *that* 的变量中。此步骤是 Project Silk 中的常见做法,即使 *this* 的含义发生变化,它也能使 *this*(小部件)可供引用。例如,如果您需要在 Ajax 调用中的 error 函数中引用小部件的值,您将无法使用 *this* 来引用小部件。在 error 函数中,*this* 是 Ajax 调用而不是小部件。即使我们稍后不使用 *that*,我们也有它以备将来可能实现。
为了利用已加载到内存中的配置数据,我们检查 sj.configurationStore.hasData()
以查看当前配置中是否有任何内容。目前,hasData
只检查 _config 对象是否不是一个空对象,但您可以增强此方法以包含一个检查以匹配预期的参数计数,检查每个设置是否有值,依此类推。使用此代码作为您自己项目的跳板,并根据需要进行自定义。如果配置对象已填充,我们从 ConfigurationStore
中返回配置对象。如果配置对象未填充,我们会填充它并通过传递配置对象来执行回调参数。使用回调参数允许您在配置对象加载后对其进行处理。
有时,您不需要所有配置信息。有时,您只需要页面的一两个值。考虑一个多页面界面(*page* 指的是单独的 ASPX 文件,而不是 jQuery Mobile 页面),您不需要在每个页面上都使用所有配置设置。您可以使用 loadConfigurationSettting
方法来按需获取您需要的内容,如图 7 所示。
loadConfigurationSetting: function (options) { var that = this; var currentOptions = $.extend({}, options, that._defaultLoadConfigSettingOptions); var _config = sj.configurationStore.getConfiguration() if (currentOptions.useCache && sj.configurationStore.hasData()) { var _hasSetting = false; for (var property in _config) { if (options.name == property) { if (undefined !== options.callback) options.callback({ "Name": property, "Value": _config[property] }); _hasSetting = true; } } if (_hasSetting) return; else { this._populateSetting(options); } } else { this._populateSetting(options); } },
请注意,currentOptions
是通过使用 $.extend jQuery
函数将提供给方法的选项与 _defaultLoadConfigSettingOptions
变量合并而创建的。这确保提供了所有必需的选项属性。然后我们检查值是否已加载到 ConfigurationStore
中。如果是,并且 useCache
设置为 true,我们则从中返回该值。提供 useCache
选项允许您在需要时强制刷新数据。配置数据可能更改不频繁,因此不太可能需要此选项,但它仍然可用。如果尝试从现有 ConfigurationStore
中拉取但找不到该值,我们将调用 _populateSetting
方法(如果 ConfigurationStore
为空或我们将 useCache
设置为 false 时也会调用此方法)。populateSetting
方法,如图 8 所示,是一个非常简单的 Ajax 调用,它使用提供给它的回调。
_populateSetting: function (options) { sj.dataManager.sendRequest({ url: "/services/ConfigurationService.asmx/GetConfigurationSetting", type: "POST", dataType: "json", data: "{keyName:\"" + options.name + "\"}", contentType: "application/json; charset=utf-8", success: function (results) { sj.configurationStore.set(results.d.Name, results.d.Value); if (undefined !== options.callback) options.callback({ "Name": results.d.Name, "Value": results.d.Value }); }, error: function (ex) { throw ex.responseText; } }); }
将所有内容整合在一起,我们就可以为我们的项目创建 sj.config.js 脚本文件了。此小部件的一些使用示例和完整代码可以在 http://code.msdn.microsoft.com/Script-Junkie-Configuration-543ece24 找到。
已批准设置的配置小部件
在本文中,我使用了 Project Silk 设计来设置一个从应用程序服务器端拉取配置数据的小部件。使用此小部件和 Web 服务,您可以创建客户端和服务器之间的桥梁,从而使 HTML5 应用程序能够访问服务器组件。虽然我使用了这种方法来访问配置数据,但您可以将其改编为将任何服务器库带到客户端。考虑一下客户端可能需要访问的服务器上的其他资源,例如文件流、服务器元数据等。在开始实验时,请始终记住,您绝不应向客户端提供敏感信息——验证来自应用程序外部的数据,并限制可以请求的内容为已知已批准的数据。通过结合风险管理、Project Silk 的设计以及一点创造力,您可以构建一个 JavaScript 小部件,将应用程序配置数据带到客户端供您的 HTML 5 应用使用。
本文由 Tim Kulp 撰写。在过去的十年中,Tim 一直使用 JavaScript、ASP.NET 和 C# 构建 Web 应用程序。现在,Tim 领导 FrontierMEDEX 的开发团队,构建在线医疗和安全情报工具。Tim 拥有 CISSP 和 CEH 认证,专注于安全软件开发。
在以下位置查找 Tim