【问题标题】:How to restrict my app to a single browser tab?如何将我的应用程序限制为单个浏览器选项卡?
【发布时间】:2011-12-12 06:22:04
【问题描述】:

坦率地说,这只是在 v1.0 中造成了太多麻烦,因为它需要三个表单提交,$_SESSION 会话数据包含所有中间内容 - 只是让用户开始操作,然后打开第二个选项卡并执行第二个操作,践踏会话数据。

我怀疑这是恶意的(但不能打折扣)。更有可能用户开始操作,被打断,忘记他们开始或找不到原始选项卡所以重新开始(然后找到原始选项卡并尝试第二次完成操作)。

由于我使用 PHP 进行编码,我可以在表单提交时检测到会话数据的存在(如果用户打开另一个选项卡,我将如何使用 JS 来做到这一点——我想我需要 Ajax——对吧?)。

所以,每次我开始操作时,我都会检查会话数据中的标志,如果设置了,我会重新加载“对不起,戴夫。恐怕我做不到”页面,否则我设置标志并继续(记得在操作结束时清除它)。

我想这会起作用,但是:
1) 将浏览器应用程序限制为单个选项卡/实例是否可以接受?
2) 我应该尝试在 v2.0 中允许多个实例吗?

还有其他的cmet、帮助或建议吗?

【问题讨论】:

  • 如果您遇到此问题,您可能还会遇到用户单击浏览器的后退按钮然后与上一页交互的问题,即使没有多个选项卡也是如此。你不能假设$_SESSION 总是与浏览器同步。

标签: php javascript session


【解决方案1】:

更好的设计是避免在会话中存储用户交互状态。将它放在隐藏的表单字段或其他东西中,以便每个客户端请求都带有其关联状态。如果您担心用户篡改它,请使用 HMAC 来防止这种情况发生,如果其中包含用户不应该看到的内容,可能会对其进行加密。

只有应该在标签之间共享的状态——比如用户的登录身份,或者像购物车之类的东西——应该存储在会话中。

【讨论】:

    【解决方案2】:

    您最多可以在会话文件中保留一个“最后请求的页面”列表,并带有标志以指示如果它是这些关键表单标志之一,则不应允许用户将其移开。因此,如果您使用的是 form.php 并且是 no-move-off,那么任何加载的新页面都应该显示“中止或关闭窗口”选项。

    您无法阻止用户打开另一个标签/窗口,但您可以阻止他们在其他窗口/标签中移动到您网站的其他位置。

    但是,请考虑这是非常糟糕的用户体验。想象一下,如果亚马逊将您困在购物车页面中,并且在您不必实际购买东西的情况下永远不会让您进入另一个页面。考虑更新您的代码以允许多个不同的窗口使用相同的表单。

    【讨论】:

    • 顺便说一句,其他人如何解决这个问题?由于会话数据是泛浏览器的,如果用户在同一个浏览器的两个选项卡或窗口中并行执行相同的多阶段操作——它们共享相同的会话数据,会发生什么?
    • @Mawg:只需将中间数据存储在表单的隐藏字段中即可。 (见Wyzard's answer
    【解决方案3】:

    由于每个浏览器都支持选项卡式浏览,因此尝试将浏览限制为单个选项卡将是一种糟糕的用户体验(那么您不妨制作一个桌面应用程序)。

    解决此问题的一种方法是在表单中添加一个 CSRF 令牌(作为隐藏变量),该令牌将与请求一起提交。

    CSRF reference

    生成令牌的方法有很多,但本质上是你:

    1. 创建令牌
    2. 存储在您的$_SESSION
    3. <input type="hidden" name="{token name}" value="{token value}" />输出表格

    然后,当表单提交时,您检查$_REQUEST['{token name}'] == $_SESSION[{token name}]`。

    如果该令牌不同,您就知道它不是您最初生成的表单,因此可以忽略该请求,直到真正的表单带有正确的令牌。

    一件事:如果攻击者能够弄清楚您是如何生成 CSRF 令牌的,那么他们就可以伪造请求。

    【讨论】:

    • 看来我的问题用错了。浏览器可以打开任意数量的选项卡 - 但只有一个可以是我的
    【解决方案4】:

    在我登录后添加了以下脚本(比如dashboard.php)

    <script>
    $(document).ready(function()
    {
        $("a").attr("target", "");
        if(typeof(Storage)              !== "undefined") 
        {
            sessionStorage.pagecount    =   1;
            var randomVal               =   Math.floor((Math.random() * 10000000) + 1); 
            window.name                 =   randomVal;
            var url                     =   "url to update the value in db(say random_value)";
            $.post(url, function (data, url)
            {
            });
        } 
        else 
        {
            var url                     =   "url to remove random_value";           
            $.post(url, function (data, url)
            {
                sessionStorage.removeItem('pagecount');
                sessionStorage.clear();
                window.location         =   'logout.php';
            });
        }    
    });
    </script>
    

    在我其余页面的页眉中添加了以下脚本 - 'random_value' 来自该用户的数据库

    <script>
    $(document).ready(function()
    {       
        $("a").attr("target", "_self");
    
        if(typeof(Storage)                      !== "undefined") 
        {
            if (sessionStorage.pagecount) 
            {
                if('<?=$random_value?>'         ==  window.name)
                {
                    sessionStorage.pagecount    =   Number(sessionStorage.pagecount) + 1;
                }
                else
                {
                    var url                     =   "url to remove random_value";           
                    $.post(url, function (data, url)
                    {
                        sessionStorage.removeItem('pagecount');
                        sessionStorage.clear();
                        window.location         =   'logout.php';
                    });
    
                }               
            } 
            else 
            {           
                var url                         =   "url to remove random_value";           
                $.post(url, function (data, url)
                {
                    sessionStorage.removeItem('pagecount');
                    sessionStorage.clear();
                    window.location             =   'logout.php';
                });
            }
        } 
        else 
        {   
            var url                             =   "url to remove random_value";                   
            $.post(url, function (data, url)
            {
                sessionStorage.removeItem('pagecount');
                sessionStorage.clear();
                window.location                 =   'logout.php';
            });
        }   
    });
    </script>
    

    【讨论】:

      【解决方案5】:

      如果我现在这样做,我可能会编写一个单页 AngularJs 应用程序(尽管任何形式的 Js 都可以)。

      在启动时,在本地存储中查找标志。如果设置,拒绝启动,并带有适当的消息,否则设置标志并运行应用程序。

      当然,恶意用户可以绕过它,因为它不是服务器端检查,但我会拒绝支持。

      【讨论】:

        猜你喜欢
        • 2018-07-14
        • 2014-04-16
        • 2010-09-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-12-12
        • 2011-02-11
        相关资源
        最近更新 更多