在页面间传递数据的最佳实践

64

问题

我们在多个项目中重复使用的堆栈中,将太多数据放入会话以在页面之间传递数据。从理论上讲,这很好,因为它可以防止篡改、重放攻击等问题,但实际上它带来的问题也同样多。

会话丢失本身就是一个问题,虽然通过实现会话状态服务器(或使用 SQL Server)可以大部分解决。更重要的是,使“后退”按钮正确工作比较棘手,而且还需要额外的工作来创建这样一种情况:用户可以在三个选项卡中打开相同的屏幕以处理不同的记录。

这只是冰山一角。

对于大多数这些问题都有解决方法,但当我努力解决这些问题时,所有这些摩擦力让我感到:使用会话在页面之间传递数据是错误的方向。

我真正想做的是制定一种最佳实践,使我的团队可以始终使用它来传递页面之间的数据,并为新应用程序替换当前依赖于会话的堆栈关键部分。

如果最终解决方案不需要大量的样板编程代码,那就更好了。

提出的解决方案

会话

如上所述,过度依赖会话看起来像是一个不错的主意,但它会破坏“后退”按钮并引起一些其他问题。

虽然可能有方法解决所有这些问题,但似乎需要额外的工作。

使用会话的一个非常好的地方在于,数据篡改不是问题。与通过未加密的查询字符串传递所有内容相比,你最终编写的防护代码要少得多。

跨页发布

实际上我几乎没有考虑过这个选项。我对其导致页面之间耦合过紧的方式有意见——如果我开始做 PreviousPage.FindControl("SomeTextBox"),如果我想从另一个可能没有名为 SomeTextBox 的控件的页面访问该页面,则此方法会造成维护问题。

看起来在其他方面也有限制。例如,我可能想通过链接进入页面。

查询字符串

目前我正在倾向于使用这种策略,就像古代一样。但是我可能希望我的查询字符串被加密以使它更难被篡改,并且我也想解决重放攻击的问题。

在Rolla的 4 个家伙网站上,有一篇文章讨论了这个问题

然而,应该可以创建一个 HttpModule 来处理所有这些内容,并从页面中删除所有加密代码。的确,Mads Kristensen 发布了一篇文章,其中介绍了如何创建一个 HttpModule 来进行查询字符串加密。他的文章提供了详细信息。 然而,评论中表明它在极为常见的情况下存在问题。

其他选项

当然,这不是所有选项的详尽说明,而只是我正在考虑的主要选项。 这个链接 包含了一个更完整的列表。我没有提到的那些选项,如 Cookies 和缓存,不适合于在页面之间传递数据。

总结

那么,你是如何处理页面之间传递数据的问题的呢?你必须解决哪些隐藏的问题,并且是否有现成的工具可以完美地解决它们?是否认为已经找到了一个完全令人满意的解决方案?

提前致谢!

更新: 为了避免我表述不够清晰,'传递页面间数据'指的是例如从 CustomerSearch.aspx 页面传递 CustomerID 键到 Customers.aspx 页面,在该页面中打开客户并进行编辑。

6个回答

42

首先,你所处理的问题涉及在无状态环境中处理状态。你遇到的困难并不新鲜,这可能是使Web开发比Windows开发或可执行文件的开发更加困难的原因之一。

在Web开发中,据我所知,你有五个选择来处理特定于用户的状态,这些解决方案可以相互结合使用。你会发现没有一种解决方案适用于所有情况。因此,你需要确定何时使用每种解决方案:

  • 查询字符串 - 查询字符串适用于传递数据指针(例如主键值)或状态值。即使加密,查询字符串本身也不应被视为安全,因为会存在重放攻击的问题。此外,某些浏览器对URL长度有限制。然而,查询字符串具有一些优点,例如可以将其作为书签和电子邮件发送给其他人,并且如果不与其他任何内容一起使用,则具有固有的无状态性。

  • Cookie - Cookie 适用于存储非常少量的特定用户信息。问题在于,cookie 在达到大小限制后会截断数据,因此在cookie 中放置自定义数据时必须小心。此外,用户可以删除 cookie 或停止使用它们(尽管这也将防止使用标准 Session)。与查询字符串类似,除非数据很小,否则 cookie 更适合用作数据指针。

  • 表单数据 - 表单数据可以包含大量信息,但代价是提交时间和在某些情况下重新加载时间。ASP.NET 的 ViewState 使用隐藏的表单变量来维护信息。使用类似 ViewState 的方法在页面之间传递数据具有一个优点,即可以更好地处理后退按钮,但可能会创建巨大的页面,从而降低用户体验。一般来说,ASP.NET 模型不支持跨页发布(尽管这是可能的),而是依赖于返回到同一页并从那里导航到下一页。

  • 会话(Session) - 会话适用于与用户正在进行的过程或一般设置相关的信息。你可以将相当多的信息存储在会话中,但代价是服务器内存或从数据库加载的时间。概念上,会话通过一次性从内存或状态服务器加载用户的整个数据集来工作。这意味着,如果你有一个非常大的数据集,你可能不希望将其放入会话中。会话可能会导致一些后退按钮问题,必须权衡用户实际想要实现的目标。总的来说,你会发现后退按钮可能是 web 开发人员的梦魇。

  • 数据库(Database) - 最后一个解决方案(也可以与其他解决方案结合使用)是将信息存储在数据库中的适当架构中,并使用一个列指示项目的状态。例如,如果你正在处理订单的创建,则可以在Order表中存储订单,并使用一个“state”列确定它是否是真实订单。你会在查询字符串或会话中存储订单标识符。网站将继续将数据写入表格以更新各个部分和子项,直到最终用户能够声明他们已经完成,并且订单的状态被标记为是真实订单。在报告和查询中,这可能会变得复杂,因为它们都需要区分“真实”项目和正在进行的项目。

在你后面提到的链接中,提到了应用缓存(Application Cache)。我不认为这是用户特定的,因为它是应用程序范围内的。 (显然可以将其强行塞入成为用户特定,但我也不建议这样做)。我从未尝试过将数据存储在HttpContext之外,并将其传递给处理程序或模块,但我对它与上述解决方案没有任何区别持怀疑态度。

总的来说,并没有一种解决方案能够解决所有问题。最好的方法是在每个页面上假设用户可能从任何地方导航到该页面(而不是假设他们通过另一个页面上的链接到达该页面)。如果这样做,返回按钮问题就变得更容易处理了(虽然仍然很麻烦)。在我的开发中,我广泛使用前四个解决方案,并在必要时采用最后一个解决方案。


嗨,Thomas,感谢你的回答。我接受了sw12345的答案,因为我不是很想了解各种选项的优缺点,而是想要一些高级见解,了解可以帮助解决这个问题的工具,特别是可以帮助我透明地自动化查询字符串加密的工具。 - Brian MacKay

9
好的,首先我想强调一下:对于刚开始接触此类问题的人来说,Thomas提供了迄今为止最准确和全面的答案。但我的回答并不完全一样,我是从“业务开发者”的角度出发的。我们都知道,有时候花费大量资金重写已经存在并且“有效”的东西是不可行的,至少不可能一次性完成。有时候最好实现一个解决方案,让您能够逐步迁移到更好的替代方案。

我唯一想补充的是客户端JavaScript状态。在我们工作的地方,我们发现客户越来越期望“Web 2.0”类型的应用程序。我们还发现这些类型的应用程序通常会带来更高的用户满意度。通过一些练习,并借助一些非常好的JavaScript库,比如jQuery(我们甚至开始使用GWT,发现它非常棒),与基于JSON的REST服务进行通信可以变得轻而易举。这种方法还提供了一种非常好的方式,可以开始向基于SOA的体系结构和UI与业务逻辑的清晰分离转移。

但我跑题了。

听起来你已经拥有一个应用程序,并且已经超出了ASP.NET内置会话状态管理的限制。所以...这是我的建议(假设您已经尝试过ASP.NET的外部进程会话管理,它比内部/本地会话管理具有更好的可扩展性,而且听起来您已经尝试过了):NCache。

NCache为您提供了一个“插入即用”的替代方案,用于ASP.NET的会话管理选项。它非常容易实现,并且可以“缓解”您的应用程序,让您顺利度过难关——而不需要立即对现有代码库进行重构。

您可以利用额外的时间和资金,通过专注于具有即时业务价值的新开发方式(例如其他答案或我的答案中提供的任何替代方案),开始减少技术债务。

以上是我的想法。


sw12345:嗨,sw,感谢你周到的回答。我接受了它,因为这确实是我正在寻找的答案类型——Thomas对各种策略的概述很全面,但我已经知道那些东西,并在我的实际问题中提到了很多。我想要更高级的策略来研究。谢谢! - Brian MacKay

8
几个月后,我想更新一下这个问题,并分享我最终采用的技术,因为它效果非常好。在尝试了更复杂的会话状态处理(导致许多返回按钮等功能失效)之后,我最终编写了自己的代码来处理加密查询字符串。这是一个巨大的胜利——我所有的问题场景(返回按钮、同时打开多个选项卡、丢失会话状态等)都得到了解决,而且复杂度很小,因为使用方法非常熟悉。尽管仍然不能解决所有问题,但我认为它对于你遇到的90%的情况都是有益的。 详情 我构建了一个名为CorePage的类,它继承自Page。它有名为SecureRequest和SecureRedirect的方法。
所以你可能会调用:
 SecureRedirect(String.Format("Orders.aspx?ClientID={0}&OrderID={1}, ClientID, OrderID)

CorePage解析出查询字符串并将其加密为名为CoreSecure的查询字符串变量。因此,实际请求看起来像这样:

Orders.aspx?CoreSecure=1IHXaPzUCYrdmWPkkkuThEes%2fIs4l6grKaznFGAeDDI%3d

如果可用,当前已登录的用户ID将添加到加密密钥中,因此重放攻击不是太大的问题。

从那里,您可以调用:

X = SecureRequest("ClientID")

结论

一切都无缝运作,使用熟悉的语法。

在过去的几个月中,我还将这个代码适应了边缘情况,例如触发下载的超链接 - 有时你需要在客户端生成一个具有安全 QueryString 的超链接。这非常有效。

如果您想看到这段代码,请告诉我,我会把它放在某个地方。

最后一点想法:接受自己的答案而不是其他人在这里发布的非常周到的帖子,感觉很奇怪,但这确实似乎是解决我的问题的终极答案。感谢每个帮助我达到目标的人。


2
就像注释加密本身并不能真正解决在 QueryString 中传递 ID 的安全问题,它只是使得篡改变得更加困难。在一个安全的应用程序中,您往往会在会话中拥有某种身份,比如用户名称。然后,如果您有一个页面 Orders.aspx?OrderId=1,在您的较低层(服务/业务)中,您总是可以放入验证逻辑,以确保通过此 ID 检索到的订单实际上属于已登录到应用程序的用户。 - e36M3
@e36M3: 很好的观点。这个工具并不规定UserID如何存储,它只是在请求和重定向之上的一层。因此,它肯定可以这样使用,你只需要将UserID存储在会话中并编写验证代码即可。我现在确实在做验证部分,尽管我必须承认我将UserID存储在加密cookie中,这并不完美。但我想尝试不依赖会话。而且这些都不是高调项目。总有改进的空间。 - Brian MacKay
@JorgeCode:好的,我会做到的。 :) - Brian MacKay

5
经过上述场景和答案的分析以及参考该链接 数据传递方法,我的最终建议如下:

COOKIES 用于:

  • 加密[userId]
  • 加密[productId]
  • 加密[xyzIds...]
  • 加密[其他等等...]

DATABASE 用于:

  • 使用COOKIE ID提取数据集
  • 使用COOKIE ID提取数据表
  • 使用COOKIE ID提取所有其他大块数据
我的建议还取决于以下统计数据和此链接的详细信息 数据传递方法

输入图像描述


3

我从不会这么做。在数据库中存储所有会话数据并根据用户的cookie进行加载,我从未遇到任何问题。就目前而言,这是一种会话,但我仍然掌控它。不要将您的会话数据交给Web服务器...

通过一些努力,您可以支持子会话,并允许在不同的选项卡/窗口中进行多任务处理。


2
@Fosco - 你的类在做什么,普通的会话类没有做到的?ASP.NET的会话允许你将其持久化到数据库中,但如果你不想这样做,你可以将其持久化在Web服务器上的内存中,并获得更好的性能。 - Thomas
1
@Fosco- 顺便说一下,如果你自己编写会话类,所有关于后退按钮和将大量数据存入会话的问题仍然存在。 - Thomas
1
@Thomas - 这个问题中没有提到返回按钮,我不确定是否能够完全解决。过去,我曾经实现了一次性表单令牌来消除任何重复提交,如果有帮助的话...至于大量数据,我必须不同意你的看法。 - Fosco
4
@Fosco - “返回按钮”在原帖的第二段提到。如果您将大量数据存储在会话服务器中,与使用自己的会话类存储到数据库并将大量数据存储在会话中有什么不同?有理由自己开发,例如ASP和可能的PHP(不确定),但是如果正确使用,则ASP.NET的Session与ViewState实际上是一个相当好的开箱即用组合。 - Thomas
1
@Thomas 我很好奇你是如何处理页面间的数据传递的,也许你可以发表一篇答案?我已经将问题重新命名为关于页面间数据传递最佳实践的问题,因为这才是我真正想要的。 - Brian MacKay
显示剩余4条评论

2
作为起点,我建议将关键数据元素(例如客户ID)放入查询字符串以进行处理。这样可以轻松跟踪/过滤这些元素的错误数据,并且还允许与电子邮件或其他相关站点/应用程序集成。
在先前的应用程序中,查看涉及员工或请求记录的唯一方法是登录应用程序,搜索员工或搜索最近的记录以找到相关记录。当其他部门的人员出于审计目的需要查看记录时,这变得麻烦且浪费时间。
在重写中,我通过基本URL“ViewEmployee.aspx?Id=XXX”和“ViewRequest.aspx?Id=XXX”提供了员工ID和请求ID。该应用程序设置为A)过滤掉错误的ID并B)在允许用户访问这些页面之前对其进行身份验证和授权。这使主要应用程序用户能够向审计员发送带有URL的简单电子邮件。当他们非常匆忙、处于批量处理时间时,他们可以简单地单击URL列表并执行适当的处理。
其他会话相关数据,例如修改日期和维护用户与应用程序的交互“状态”,会更加复杂,但希望这为您提供了一个起点。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接