如何在浏览器标签中区分会话?


135

在使用JSP和Servlet以Java实现的Web应用程序中;如果我将信息存储在用户会话中,则此信息是从同一浏览器的所有选项卡共享的。如何在浏览器标签中区分会话?在此示例中:

<%@page language="java"%>
<%
String user = request.getParameter("user");
user = (user == null ? (String)session.getAttribute("SESSIONS_USER") : user);
session.setAttribute("SESSIONS_USER",user);
%>
<html><head></head><body>
<%=user %>
<form method="post">
User:<input name="user" value="">
<input type="submit" value="send">
</form>
</body></html>

将此代码复制到jsp页面(testpage.jsp)中,将此文件部署到服务器上Web应用程序的现有上下文中(我使用Apache Tomcat),然后使用正确的URL(localhost/context1/testpage.jsp)打开浏览器(FF,IE7或Opera ),键入输入您的姓名并提交表格。然后在同一浏览器中打开一个新选项卡,然后您可以在新选项卡上看到您的名字(从会话中获取)。注意浏览器缓存,有时似乎没有发生,但是它在缓存中,刷新第二个选项卡。

谢谢。



1
这是用户要做的事情:打开IE,单击“文件->新会话”
Stefan Steiger,

3
@Quandary,您的解决方案不是通用解决方案(在其他浏览器中不起作用),最重要的是,它不是用户友好的(用户不了解会话)。
Oriol Terradas,2011年

2
有些人似乎无法想象这是什么目的。问题域大多数是您要允许网站的不同“视图”的任何情况。一旦用户可以拥有一个以上的网站视图,他们不可避免地会花很长时间(或无意间尝试)同时访问两个不同的视图。示例包括:临时版本控制(切换到查看过去存在的某个网站);沙箱(对其他人看不到的网站进行更改);基于角色的视图(查看网站对特权较低的用户的外观);等
罗恩·伯克

Answers:


92

您可以使用HTML5 SessionStorage(window.sessionStorage)。您将生成一个随机ID,并按“浏览器”选项卡保存在会话存储中。然后,每个浏览器选项卡都有自己的ID。

即使两个标签都包含来自同一域来源的网页,使用sessionStorage存储的数据也不会在浏览器标签之间持久保存。换句话说,sessionStorage内部的数据不仅限于调用页面的域和目录,还限于页面所在的浏览器选项卡。将其与会话cookie进行对比,会话cookie确实会在每个选项卡之间保留数据。


7
来自文档:“使用sessionStorage进行存储的数据不会在浏览器选项卡之间持久化,即使两个选项卡都包含来自同一域来源的网页。换句话说,sessionStorage内部的数据不仅限于调用页面的域和目录,还包括页面所在的浏览器标签。将其与会话cookie进行对比,会话cookie确实会在标签之间保留数据。” 简单测试即可确认在Chrome和FF中的这种行为。
jswanson

21
请注意,使用Chrome的“复制标签”功能时,该ID将重复。通常这不是问题,但应仔细考虑。
JosiahDaniels

除非您“复制” Chrome或Firefox中的标签。在这种情况下,将复制SessionStorage。
Tiago Freitas Leal

@Gonzalo Gallotti:如果会话是服务器端的,那对我有什么帮助?)))
Stefan Steiger,

@StefanSteiger。然后,您可以通过Ajax调用发送BrowserTab ID(保存在会话存储中)。在服务器端,您将需要自定义逻辑,因为WebSession是相同的。但是您可以通过选项卡使用会话对象创建HashMap。
贡萨洛·加洛蒂

23

您必须认识到服务器端会话是HTTP的人工附加组件。由于HTTP是无状态的,因此服务器需要以某种方式识别请求属于它认识的特定用户并为其建立会话。有两种方法可以做到这一点:

  • 饼干。一种更干净,更流行的方法,但是这意味着一个用户的所有浏览器选项卡和窗口都共享该会话-IMO这实际上是可取的,而且让我登录每个新选项卡的网站让我非常恼火,因为我大量使用标签
  • URL重写。该网站上的任何URL都附加有会话ID。这是更多的工作(您必须在具有站点内部链接的任何地方都要做一些事情),但是可以通过不同的选项卡进行单独的会话,尽管通过链接打开的选项卡仍会共享该会话。这也意味着用户访问您的网站时始终必须登录。

您到底想做什么?您为什么要选项卡有单独的会话?也许有一种方法可以完全不使用会话来实现您的目标?

编辑: 对于测试,可以找到其他解决方案(例如在单独的VM上运行多个浏览器实例)。如果一个用户需要同时扮演不同的角色,则应在应用程序中处理“角色”概念,以便一个登录名可以具有多个角色。您必须决定使用URL重写还是仅接受当前情况是否更可接受,因为使用基于cookie的会话无法单独处理浏览器选项卡。


6
它是用于保留用户信息和环境的大型应用程序。当某人在“差异”选项卡中以不同用户身份登录,进行测试或扮演不同角色时,则他正在从选项卡中传递信息。
Oriol Terradas

关于原因,这只是一个很晚的附加内容:例如,因为我需要了解这一点才能了解我网站上的WHICH页面,所以用户的关注重点是,而不仅仅是他们从一个页面移到另一个页面(相反,他们从一个标签转到另一个标签)。
奥利弗·威廉姆斯

16

window.name Javascript属性是唯一可在选项卡活动中保留的东西,但可以保持独立(而不是URL信号)。


3
window.sessionStorage也是如此
felickz

3
当用户选择“在新选项卡中打开”时,会话存储会被复制到新选项卡中,请谨慎操作...希望我有详细信息的链接,但请参阅:bugzilla.mozilla.org/show_bug.cgi?
id=818389

2
请注意,当用户切换到同一选项卡中的其他页面时,您保存在window.name设置中的内容仍将在其他域中可用。
nakib

15

你不应该 如果您想做这样的事情,则需要通过即时编写URL来强制用户使用应用程序的单个实例,使用类似的sessionID(不是sessionid则行不通)id并将其传递到每个URL中。

我不知道为什么需要它,但是除非您需要制作一个完全不可用的应用程序,否则请不要这样做。


9
它是用于保留用户信息和环境的大型应用程序。当某人在“差异”选项卡中以不同用户身份登录,进行测试或扮演不同角色时,则他正在从选项卡中传递信息。
Oriol Terradas

38
我知道旧的答案,但是Google邮件允许您每个选项卡使用不同的帐户,而且它不是“完全不可用的应用程序”
George

2
@George,答案仍然正确,对于您的示例,您将在URL号中看到代表存储在cookie中的帐户的索引,google邮件仅存储所有cookie并根据URL选择一个。
Mhmd 2014年

5
不确定答案是否正确,您显然可以做到。Gmail做到了。至于它的执行方式,它可能并不优雅,但它确实有效,这很重要
乔治

2
旧的答案,但我可能要补充一点,Whatsapp和Telegram不允许您同时打开两个选项卡。这不会使它们不可用
Charlie

13

我想出了一个新的解决方案,该解决方案有一点点开销,但似乎可以作为原型使用。一个假设是,您可以在荣誉系统环境中登录,尽管可以通过在每次切换选项卡时重新请求密码来进行调整。

使用localStorage(或等效方法)和HTML5存储事件来检测新的浏览器选项卡何时切换了哪个用户处于活动状态。发生这种情况时,请创建一条带有消息的幻影覆盖,该消息表明您无法使用当前窗口(或者暂时禁用该窗口,您可能不希望它如此显眼。)当窗口重新获得焦点时,发送AJAX请求日志记录用户重新登录。

这种方法的一个警告:除非在没有焦点的窗口中发生任何普通的AJAX调用(即,取决于您的会话的调用)(例如,如果您在延迟后进行了调用),除非您可以在此之前手动进行AJAX重新登录调用。因此,实际上您需要做的就是先检查AJAX功能,以确保localStorage.currently_logged_in_user_id === window.yourAppNameSpace.user_id,如果没有,请首先通过AJAX登录。

另一个是竞争条件:如果您可以足够快地切换窗口以使其混淆,您可能会得到relogin1-> relogin2-> ajax1-> ajax2序列,而在错误的会话中创建了ajax1。通过将登录AJAX请求推送到一个数组上,然后进行存储并发出新的登录请求之前,中止所有当前请求,以解决此问题。

最后要注意的问题是窗口刷新。如果有人在您激活AJAX登录请求但未完成时刷新窗口,则会以错误的人的名义刷新窗口。在这种情况下,您可以使用非标准的beforeunload事件来警告用户有关潜在的混淆,并要求他们单击“取消”,同时重新发出AJAX登录请求。然后,他们可以执行此操作的唯一方法是在请求完成之前单击“确定”(或者不幸的是,默认情况下单击“确定”是因为“确定”是默认值。)还有其他方法可以处理这种情况,例如检测F5和Ctrl + R / Alt + R组合键,这在大多数情况下都可以使用,但是可能会因用户键盘快捷键重新配置或使用其他操作系统而受阻。但是,这实际上是一个边缘情况,最糟糕的情况永远不会那么糟:在荣誉系统配置中,您将以错误的身份登录(但您可以通过个性化颜色,样式,突出显示的名称,等等。); 在密码配置中,责任在于最后一个输入密码以注销或共享其会话的人,或者,如果该人实际上是当前用户,则没有违规行为。

但是最后,您有了一个“每个选项卡一个用户”的应用程序(希望)可以按预期方式运行,而不必设置概要文件,使用IE或重写URL。但是,请确保在登录到该特定选项卡的每个选项卡中都将其标明,...


6
+1是因为您花了时间认真考虑一下!:-)查看所有警告,您只是知道这是一个坏主意。我从这些东西中学到的东西是真正了解哪些数据应该进入会话,哪些数据不应该进入会话。
彼得

10

我们遇到了这个问题,我们很容易解决了。我的意思是简单,因为不涉及任何编程。我们想要做的是让一个用户登录到同一浏览器窗口中的多个帐户,而不会干扰会话。

因此,解决方案是随机子域。

23423.abc.com
242234.abc.com
235643.abc.com

因此,我们要求系统管理员为* .abc.com而不是abc.com配置SSL证书。然后,只需进行很少的代码更改,每次用户尝试登录时,他都会使用一个随机子域号登录到选项卡中。因此每个标签可以独立地拥有自己的会话。为了避免冲突,我们使用用户ID的哈希或md5开发了随机数。


3
这似乎是URL重写的明智选择。您最终在HTTP可见的位置获得了所需的变量(会话ID),但该变量不显眼。想到的是:1)显然需要在DNS上使用通配符; 2)您的整个网站必须避免将用户引导回(例如)“ www”子域的任何绝对URL,3)可能希望阻止网络抓取工具,但这可能是私人网络情况,因此可能已经完成。感谢您分享!
罗恩·伯克

@ user3534653:我喜欢这种方法。但是您说“不涉及编程”。然后,您继续说“代码更改很少”。那么,实际上,对代码进行少量更改是不是在编程?;)此外,如果您有多个具有规范链接的应用程序,并且该域是经过硬编码/配置的(规范地),则此方法可能不起作用。
Stefan Steiger,

5

我会诚实的。。。所有上述可能或不可能是真实的,但是这一切似乎WAY太复杂,或者不涉及知道什么标签正在使用服务器端。

有时我们需要使用Occam的剃刀。

这是Occam的方法:(不,我不是Occam,他于1347年去世)

1)在加载时为您的页面分配一个浏览器唯一ID。。。当且仅当窗口还没有ID时(因此使用前缀和检测)

2)在您拥有的每个页面上(使用全局文件等),只需将代码放在适当的位置即可检测焦点事件和/或鼠标悬停事件。(为了简化代码编写,我将在这部分中使用jquery)

3)在焦点(和/或鼠标悬停)功能中,设置一个带有window.name的cookie

4)当您需要读/写选项卡特定数据时,从服务器端读取该cookie值。

客户端:

//Events 
$(window).ready(function() {generateWindowID()});
$(window).focus(function() {setAppId()});
$(window).mouseover(function() {setAppId()});


function generateWindowID()
{
    //first see if the name is already set, if not, set it.
    if (se_appframe().name.indexOf("SEAppId") == -1){
            "window.name = 'SEAppId' + (new Date()).getTime()
    }
    setAppId()
}

function setAppId()
{
    //generate the cookie
    strCookie = 'seAppId=' + se_appframe().name + ';';
    strCookie += ' path=/';

    if (window.location.protocol.toLowerCase() == 'https:'){
        strCookie += ' secure;';
    }

    document.cookie = strCookie;
}

服务器端(C#-例如)

//variable name 
string varname = "";
HttpCookie aCookie = Request.Cookies["seAppId"];
if(aCookie != null) {
     varname  = Request.Cookies["seAppId"].Value + "_";
}
varname += "_mySessionVariable";

//write session data 
Session[varname] = "ABC123";

//readsession data 
String myVariable = Session[varname];

做完了


1
我真的很喜欢您的简化答案和Occam的剃须刀参考资料。只是想仔细检查一下该解决方案是否仍在为您工作。
杰夫·马里诺

1
值得注意的是,如果刷新页面应保持其会话状态,则此方法将无效。在使用window.sessionStorage的另一个答案中,另一种建议的方法对我来说似乎更合理。
rosenfeld

@quijibo仍然可以在我的应用程序中完美运行,是的。至于刷新页面,对于某些浏览器来说可能是正确的,使用window.sessionStorage可能会解决问题,没有尝试过
Mike

3
函数“ se_appframe()”是什么?
AndreMiranda '16

什么是se_appframe
Kiquenet

2

从单个页面开始时,您可以使用链接重写将唯一标识符附加到所有URL(例如index.html / jsp / whatever)。浏览器将对所有选项卡使用相同的cookie,因此您放置在cookie中的所有内容都不是唯一的。


2
我认为重写链接来编码会话是一个乏味且容易出错的事情。我的应用程序很大,有很多链接,我需要一个透明的解决方案或易于实现。
Oriol Terradas

2

我认为您可能想要的是保持选项卡之间的导航状态,而不是专门为每个选项卡创建单个会话。这正是Seam框架通过其对话范围/上下文实现的。它们的实现依赖于以下事实:会话ID随每个请求传播,并在服务器端创建会话的概念,这是介于会话和请求之间的。它允许导航流控制和状态管理。

尽管这主要是针对JSF的,但请查看一下并检查一下是否可以从中获得一些启发:http : //docs.jboss.org/seam/latest/reference/zh-CN/html_single/#d0e3620



1

另一种可行的方法是创建一个唯一的窗口ID,并将此值与会话ID一起存储在数据库表中。我经常使用的窗口ID是integer(now)。当窗口打开并刷新,重新加载或提交给自身时,将在打开窗口并将其重新分配给同一窗口时创建此值。窗口值(输入)使用链接保存在本地表中。当需要一个值时,它是基于窗口ID /会话ID链接从数据库表中获取的。尽管此方法需要本地数据库,但实际上是万无一失的。数据库表的使用对我来说很容易,但是我看不出为什么本地数组不能很好地工作。




1

注意:这里的解决方案需要在应用程序设计阶段完成。以后很难对此进行设计。

使用隐藏字段来传递会话标识符

为此,每个页面必须包含一个表格:

<form method="post" action="/handler">

  <input type="hidden" name="sessionId" value="123456890123456890ABCDEF01" />
  <input type="hidden" name="action" value="" />

</form>

您这边的每项操作(包括导航)都会将表单过帐回退(action适当设置)。对于“不安全”请求,您可以包括另一个参数,例如包含要提交的数据的JSON值:

<input type="hidden" name="action" value="completeCheckout" />
<input type="hidden" name="data" value='{ "cardNumber" : "4111111111111111", ... ' />

由于没有cookie,因此每个选项卡都是独立的,并且不会在同一浏览器中了解其他会话。

有很多优点,尤其是在安全性方面:

  • 不依赖JavaScript或HTML5。
  • 内在地抵御CSRF
  • 不依赖Cookie,因此可以防止POODLE
  • 不易受到会话固定的影响
  • 可以防止使用后退按钮,当您希望用户遵循您网站上的设置路径时,这是理想的选择(这意味着可以防止有时可能由于乱序请求而攻击的逻辑错误)。

一些缺点:

  • 可能需要返回按钮功能。
  • 缓存不是很有效,因为每个操作都是POST。

更多信息在这里


1

我通过以下方式解决了这个问题:

  • 我已经为窗口分配了一个名称,该名称与连接资源相同。
  • 加1清除存储在cookie中的用于连接的附件。
  • 我创建了一个函数来捕获所有xmloutput响应,并以json格式将sid和rid分配给cookie。我为每个window.name执行此操作。

这里的代码:

var deferred = $q.defer(),
        self = this,
        onConnect = function(status){
          if (status === Strophe.Status.CONNECTING) {
            deferred.notify({status: 'connecting'});
          } else if (status === Strophe.Status.CONNFAIL) {
            self.connected = false;
            deferred.notify({status: 'fail'});
          } else if (status === Strophe.Status.DISCONNECTING) {
            deferred.notify({status: 'disconnecting'});
          } else if (status === Strophe.Status.DISCONNECTED) {
            self.connected = false;
            deferred.notify({status: 'disconnected'});
          } else if (status === Strophe.Status.CONNECTED) {
            self.connection.send($pres().tree());
            self.connected = true;
            deferred.resolve({status: 'connected'});
          } else if (status === Strophe.Status.ATTACHED) {
            deferred.resolve({status: 'attached'});
            self.connected = true;
          }
        },
        output = function(data){
          if (self.connected){
            var rid = $(data).attr('rid'),
                sid = $(data).attr('sid'),
                storage = {};

            if (localStorageService.cookie.get('day_bind')){
              storage = localStorageService.cookie.get('day_bind');
            }else{
              storage = {};
            }
            storage[$window.name] = sid + '-' + rid;
            localStorageService.cookie.set('day_bind', angular.toJson(storage));
          }
        };
    if ($window.name){
      var storage = localStorageService.cookie.get('day_bind'),
          value = storage[$window.name].split('-')
          sid = value[0],
          rid = value[1];
      self.connection = new Strophe.Connection(BoshService);
      self.connection.xmlOutput = output;
      self.connection.attach('bosh@' + BoshDomain + '/' + $window.name, sid, parseInt(rid, 10) + 1, onConnect);
    }else{
      $window.name = 'web_' + (new Date()).getTime();
      self.connection = new Strophe.Connection(BoshService);
      self.connection.xmlOutput = output;
      self.connection.connect('bosh@' + BoshDomain + '/' + $window.name, '123456', onConnect);
    }

希望对你有帮助


0

我一直在阅读这篇文章,因为我认为我想做同样的事情。我正在处理的应用程序也有类似情况。实际上,这不仅仅是测试实用性。

阅读了这些答案之后,尤其是迈克尔·伯格沃德(Michael Borgwardt)给出的答案,我意识到需要存在的工作流程:

  1. 如果用户导航到登录屏幕,请检查现有会话。如果存在,请绕过登录屏幕,然后将其发送到欢迎屏幕。
  2. 如果用户(就我而言)导航至注册屏幕,请检查现有会话。如果存在,请让用户知道您将注销该会话。如果他们同意,请注销并开始注册。

这将解决用户在其会话中看到“另一个用户”数据的问题。他们并没有在会话中真正看到“另一个用户”的数据,而是从唯一打开的会话中真正地看到了数据。显然,这会导致产生一些有趣的数据,因为某些操作会覆盖某些会话数据,而不会覆盖其他会话数据,因此您在该单个会话中拥有数据组合。

现在,解决测试问题。唯一可行的方法是利用预处理程序指令来确定是否应使用无cookie会话。通过为特定环境构建特定配置,我可以对环境及其用途进行一些假设。从技术上讲,这使我可以同时登录两个用户,并且测试人员可以从同一浏览器会话中测试多个方案,而无需注销任何服务器会话。

但是,这种方法有一些严重的警告。尤其重要的是,测试人员所测试的不是生产中要运行的内容。

所以我想我不得不说,这最终是一个坏主意。


0

如果尚未设置timeStamp,则将其存储在window.sessionStorage中。这将为每个标签提供唯一的值(即使网址相同)

http://www.javascriptkit.com/javatutors/domstorage.shtml

https://developer.mozilla.org/zh-CN/docs/Web/Guide/API/DOM/Storage

希望这可以帮助。


1
这可能还可以,但从理论上讲是不安全的。某些操作系统(例如Windows)的时间戳粒度非常粗,这意味着您可能会多次查询时间戳并返回相同的值。此外,如果在崩溃后重新打开浏览器,并且浏览器立即重新打开所有选项卡,则它们可能会获得相同的时间戳。
吉利2014年

0
How to differ sessions in browser-tabs?

在浏览器选项卡中区分会话的最直接方法是禁止您的特定域设置Cookie。这样,您可以从单独的选项卡中进行单独的会话。假设您不允许来自以下域的Cookie:www.xyz.com。您打开选项卡1,登录并开始浏览。然后打开选项卡2,您可以以同一用户或其他用户身份登录。无论哪种方式,您都将有一个与选项卡1分开的会话。依此类推。

但是,当您可以控制客户端时,这当然是可能的。否则,应采用此处规定的解决方案。


0

你将需要做

1-存储帐户列表的Cookie

2-可选存储默认的cookie

3-为每个帐户存储其索引,例如acc1,acc2

4-在URL中放一些代表帐户索引,如果没有,您将选择默认的帐户,例如google mail domain.com/0/some-url >> 0在这里代表帐户索引,您可能还需要知道如何使用urlwrite

5-当选择一个cookie时,根据您的URL路径选择它代表帐户索引

问候


0

我看到许多实现都有客户端更改以操纵会话ID Cookie的实现。但一般而言,会话ID cookie应该为HttpOnly,因此Java脚本无法访问,否则可能会通过XSS导致会话劫持


0

如果是因为每个选项卡将在您的应用程序中运行不同的流,并且混合这两种流都会导致问题,那么最好“区域化”您的会话对象,以便每个流将使用会话的不同区域

可以简单地实现此区域,因为每个流具有不同的前缀,否则会话对象将持有多个映射(每个流一个),并且您可以使用这些映射代替会话属性,尽管最好是扩展会话类和改用它。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.