15

假设您正在提交一个表单,这会影响您的数据库(添加记录/删除它们/更新它们),这就是您的请求的样子:

发布/应用程序/动作=更新

现在,假设您已完成更新,因此您希望将用户带到主页。

Response.sendRedirect /application/action=home

这工作得非常好。用户在 POST 之后会收到重定向,因此即使用户尝试通过按 F5 来刷新页面,你也很好。但是,如果您这样做,这将不起作用:

requestDispatcher.forward(/application/action=home)

鉴于在完成更新后您必须显示不同类型的错误/成功消息的情况下,您很可能在 POST 之后进行转发。在这种情况下,如何避免更新操作发生两次?

我觉得很有趣的是,许多安全网站(银行)/支付网关倾向于通过在屏幕上放置文本来通知用户,例如“请不要按返回/刷新按钮”。

有没有更好的方法来处理这个?除了要求用户不要按下这些按钮?当我上次检查时,有一个叫做“垂直响应缓存”的东西。一个过滤器,它将识别您的请求在会话中的唯一性,并在请求重复时尝试发送缓存的响应。有没有更简单的方法来解决这个经典问题?

这是我正在谈论的垂直响应缓存解决方案的链接:http ://www.fingo.info/en/articles/_1.html 。但是,我不确定这到底有多好。

4

6 回答 6

12

是的,我认为您应该在 POST 之后重定向,API 请求除外。如果不这样做,您不仅要担心当用户使用后退按钮时会收到重复的 POST,而且当用户尝试使用后退按钮时,浏览器也会给用户带来烦人的对话框。

Response.sendRedirect 在实践中有效,但从技术上讲,这是为此目的发送错误的 HTTP 响应代码。sendRedirect 发送 302,但用于将 POST 转换为 GET 的正确代码是 303。(但是,如果大多数浏览器在响应 POST 时收到 302,则它们会将 302 视为 303)

通常,您希望重定向将用户发送到将显示其更改效果的任何视图。例如,如果他们编辑一个小部件,他们应该被重定向到该小部件的视图。如果他们删除了一个小部件,他们应该被重定向到该小部件存在时会出现的视图(可能是小部件列表)。

有时,最好有一条状态消息来进一步说明发生了动作的事实。一个简单的方法是为您的视图设置一个通用参数,当设置该参数时,将显示操作完成消息。例如:

/widget?id=12345&msg=Widget+modified.

这里的“msg”参数包含消息“Widget modified”。这种方法的一个缺点是恶意网站可能会给您的用户带来令人困惑/误导的消息。例如:

/account?msg=Foo+Corp.+hates+you.

如果您真的担心这一点,您可以将消息的过期签名作为附加参数包含在内。如果签名无效或已过期,则根本不显示该消息。

于 2009-07-05T04:49:19.633 回答
2

解决在 POST 到 GET 重定向后向用户显示状态消息问题的最佳解决方案是使用用户会话。

如何

将属性添加到用户会话,其值为要显示的消息集。例如。

userSession.put("success_messages", new HashSet<String>(){"Success", "Check your account balance"});
userSession.put("warning_messages", new HashSet<String>(){"Your account balance is low. Recharge immediately"});

并有一个过滤器扫描用户会话以获取这些特定属性并输出消息。过滤器应该在读取一次后删除属性,因为状态消息通常只显示一次。

于 2011-01-12T08:35:48.367 回答
1

我的一个想法是将唯一 ID(可能是随机字符串)作为隐藏的表单字段嵌入到 POST 提交的表单中。ID 字符串可以作为“事务 ID”放入数据库中。现在,当您去更新数据库时,首先检查是否存在具有提交事务ID的现有记录,如果有,则假设它是重复的并且不要更改数据库。

当然,正如我所说,这只是一个想法。我不知道在实践中实际使用了哪些方法。(我怀疑很多不太重要的网站只是忽略了这个问题,并希望他们的用户会聪明……如果我见过一个失败的命题;-)

编辑:正如评论中指出的那样,在数据库中存储事务 ID 可能会占用大量空间,但如果这是一个问题,您可以保留过去 5 分钟/1 小时内处理的所有事务 ID 的内存缓存/1天/随便。除非您遇到坚定的黑客,否则这应该可行...

于 2009-07-05T02:53:18.353 回答
1

我觉得很有趣的是,许多安全网站(银行)/支付网关倾向于通过在屏幕上放置文本来通知用户,例如“请不要按返回/刷新按钮”。

有些人发现“禁用此关键页面上的所有返回、刷新事件”会更好;我不确定这是否好。

但是您解决的解决方案“垂直响应缓存”听起来不错

于 2009-07-05T05:17:00.410 回答
1

它有点不明显,但是:

  • 在用户会话中创建一个键控对象。
  • 该值是结果的 Request + java Future
  • 通过客户端重定向立即返回。
  • 在处理客户端重定向时,让工作线程生成答案。

因此,当客户端浏览器完成重定向、获取新页面的图像等时......结果正在等待用户。

另一种方法是让用户痛苦地意识到数据库花费了多长时间。

安全更新(2011 年 1 月 24 日):

密钥很容易受到攻击,因为它是对客户端的响应的一部分,所以

  1. 生成随机密钥
  2. 使用用户的会话 ID 作为盐来创建 SHA-1
  3. 将随机键和 SHA-1 存储在数据库中,以 (, ) 作为主键。(在 RANDOMKEY 上没有单独的索引。
  4. 使用 RANDOMKEY 和 SHA-1 作为数据库查找。
  5. 不要存储会话 ID(避免将多个条目关联到同一用户的隐私问题)
  6. 2-3天后过期结果。(允许每日批处理作业进行清理并避免为半持久的用户会话产生问题)

这种方法要求任何黑客都知道会话 id 和随机密钥。

这种方法可能看起来有点矫枉过正,但重定向强化机制可用于密码重置等情况。

于 2009-07-05T07:58:29.930 回答
0

如果您正在使用 java 服务器端脚本并且还使用 struts 2,那么您可以参考这个关于使用 token 的链接。

http://www.xinotes.org/notes/note/369/

应为初始页面渲染生成一个令牌并将其保存在会话中,当请求与令牌第一次一起提交时,在 struts 操作中运行一个线程名称作为令牌 id 的线程并运行客户端拥有的任何逻辑请求,当客户端再次提交相同的请求时,检查线程是否仍在运行(thread.getcurrentthread().interrupted)如果仍在运行,则发送客户端重定向 503。

请看struts 2code的ExecuteAndWaitInterceptor,这个逻辑结合token有助于快速点击

于 2011-05-05T14:04:14.057 回答