137

在使用 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.jsplocalhost/context1/testpage.jsp输入您的姓名并提交表格。然后在同一浏览器中打开一个新选项卡,然后您可以在新选项卡上看到您的姓名(从会话中获取)。小心浏览器缓存,有时似乎没有发生,但它在缓存中,刷新第二个选项卡。

谢谢。

4

20 回答 20

96

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

使用 sessionStorage 存储的数据不会跨浏览器选项卡保留,即使两个选项卡都包含来自同一域来源的网页。换句话说,sessionStorage 中的数据不仅限于调用页面的域和目录,还包括页面所在的浏览器选项卡。与会话 cookie 相比,会话 cookie 会在选项卡之间保存数据。

于 2012-08-02T18:39:35.937 回答
25

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

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

你到底想做什么?为什么你希望标签有单独的会话?也许有一种方法可以在不使用会话的情况下实现您的目标?

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

于 2008-12-15T15:40:57.590 回答
16

window.name Javascript 属性是唯一会在选项卡活动中持续存在的东西,但可以保持独立(而不是 URL 胡言乱语)。

于 2011-06-20T20:51:30.573 回答
15

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

我不知道你为什么需要它,但除非你需要制作一个完全无法使用的应用程序,否则不要这样做。

于 2008-12-15T15:35:51.983 回答
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 按下,这在大多数情况下都有效,但可能会因用户键盘快捷键重新配置或替代操作系统使用而受阻。然而,这在现实中有点极端,最坏的情况永远不会那么糟糕:在荣誉系统配置中,您会以错误的人身份登录(但您可以通过使用颜色、样式、突出显示的名称来个性化页面来明确这种情况, ETC。); 在密码配置中,责任在于最后一个输入密码以注销或共享会话的人,或者如果此人实际上是当前用户,则没有违规行为。

但最终,您将拥有一个每个选项卡一个用户的应用程序,该应用程序(希望)只是按应有的方式运行,而不必设置配置文件、使用 IE 或重写 URL。确保在登录到该特定选项卡的每个选项卡中都很明显,但是...

于 2009-07-15T20:53:51.033 回答
10

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

所以解决方案是随机子域。

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

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

于 2014-04-15T06:17:13.597 回答
8

我在这里说实话。. . 以上所有内容可能正确,也可能不正确,但这一切似乎都复杂了,或者不知道服务器端正在使用哪个选项卡。

有时我们需要应用奥卡姆剃刀。

这是奥卡姆的方法:(不,我不是奥卡姆,他死于 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];

完毕。

于 2013-10-15T14:42:26.953 回答
2

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

于 2008-12-15T15:15:13.117 回答
2

在 javascript 中,如何从另一个基于 cookied 的 sessionId 唯一地识别一个浏览器窗口

本质上使用window.name。如果未设置,请将其设置为唯一值并使用它。属于同一会话的选项卡会有所不同。

于 2012-10-05T07:09:25.903 回答
2

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

虽然这主要针对 JSF,但请查看并检查是否可以从中获取一些想法: http: //docs.jboss.org/seam/latest/reference/en-US/html_single/#d0e3620

于 2012-08-02T18:51:46.437 回答
2

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

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

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

<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。

更多信息在这里

于 2015-06-08T14:31:24.990 回答
1

另一种可行的方法是创建一个唯一的窗口 ID,并将此值与会话 ID 一起存储在数据库表中。我经常使用的窗口 id 是整数(现在)。如果窗口被刷新、重新加载或提交给自身,则在打开窗口并重新分配给同一窗口时创建此值。使用链接将窗口值(输入)保存在本地表中。当需要一个值时,根据窗口id/会话id链接从数据库表中获取。虽然这种方法需要本地数据库,但它实际上是万无一失的。数据库表的使用对我来说很容易,但我看不出本地数组不能正常工作的原因。

于 2009-09-21T17:09:46.713 回答
1

Spring Session 支持同一浏览器中的多个会话查看示例和实现细节 http://docs.spring.io/spring-session/docs/current/reference/html5/guides/users.html

于 2015-02-20T07:45:54.753 回答
1

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

  • 我已经为窗口分配了一个名称,这个名称与连接资源相同。
  • 加 1 以摆脱存储在 cookie 中的附加连接。
  • 我创建了一个函数来捕获所有 xmloutput 响应并将 sid 和 rid 分配给 json 格式的 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);
    }

希望对你有帮助

于 2015-12-08T19:30:11.080 回答
0

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

在阅读了这些答案,尤其是 Michael Borgwardt 给出的答案后,我意识到需要存在的工作流程:

  1. 如果用户导航到登录屏幕,请检查现有会话。如果存在绕过登录屏幕并将它们发送到欢迎屏幕。
  2. 如果用户(在我的情况下)导航到注册屏幕,请检查现有会话。如果存在,请让用户知道您将注销该会话。如果他们同意,请退出并开始注册。

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

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

但是,这种方法有一些严重的警告。其中最重要的一点是,测试人员正在测试的东西并不是要在生产中运行的东西。

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

于 2013-07-18T17:40:46.493 回答
0
How to differ sessions in browser-tabs?

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

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

于 2013-11-22T08:47:32.553 回答
0

我看到许多实现都有客户端更改来操作会话 id cookie。但一般会话 id cookie 应该是 HttpOnly 以便 java-script 无法访问,否则可能导致通过 XSS 的会话劫持

于 2015-03-16T05:23:34.587 回答
0

如果尚未设置时间戳,则将其存储在 window.sessionStorage 中。这将为每个选项卡提供一个唯一值(即使 URL 相同)

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

https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Storage

希望这可以帮助。

于 2013-10-05T20:54:04.613 回答
0

你需要做

1-为帐户列表存储一个cookie

2-可选存储一个cookie作为默认值

3- 为每个帐户存储它的索引,如 acc1、acc2

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

5-选择cookie时,根据你的urlpath选择它代表帐户索引

问候

于 2014-10-25T01:02:24.430 回答
0

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

该区域可以简单地实现为每个流具有不同的前缀,或者会话对象将包含多个映射(每个流一个),并且您使用这些映射而不是会话属性,最好的办法是扩展您的会话类和改用它。

于 2016-04-19T08:04:59.273 回答