关于jsp:如何在浏览器选项卡中区分会话?

How to differ sessions in browser-tabs?

在使用JSP和servlet的Java实现的Web应用程序中,如果在用户会话中存储信息,则从同一浏览器中的所有选项卡共享该信息。如何在浏览器选项卡中区分会话?在本例中:

1
2
3
4
5
6
7
8
9
10
11
12
13
<%@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),在输入中键入您的名称并提交表单。然后在同一个浏览器中打开一个新选项卡,然后您可以在新选项卡上看到您的名称(从会话中获取)。小心浏览器缓存,有时似乎不会发生,但它在缓存中,刷新第二个选项卡。

谢谢。


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

Data stored using sessionStorage do not persist across browser tabs,
even if two tabs both contain webpages from the same domain origin. In
other words, data inside sessionStorage is confined to not just the
domain and directory of the invoking page, but the browser tab in
which the page is contained in. Contrast that to session cookies,
which do persist data from tab to tab.


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

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

你到底想干什么?为什么您希望选项卡有单独的会话?也许有一种方法可以实现你的目标,而不需要使用会话?

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


你不应该这样做。如果你想做这样的事情,要么你需要强迫用户使用你的应用程序的单一实例,通过即时写URL,使用一个类似sessionid(而不是sessionid,它不会工作)的ID,并在每个URL中传递它。

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


window.name的javascript属性是唯一能够在标签活动中保持不变的属性,但是可以保持独立(而不是URL的浪费)。


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

使用localstorage(或等效工具)和html5存储事件检测新浏览器选项卡何时切换了哪个用户处于活动状态。当发生这种情况时,创建一个重影覆盖,并显示一条消息,说明您不能使用当前窗口(或者暂时禁用该窗口,您可能不希望它如此明显)。当窗口重新聚焦时,发送一个Ajax请求,将用户重新登录。

对此方法的一个警告是:除非在此之前手动进行Ajax重新登录调用,否则在没有焦点的窗口中不能发生任何正常的Ajax调用(即,依赖于会话的调用)。因此,真正需要做的就是首先检查Ajax函数,以确保localStorage.currently登录到u useru id==window.yourappNamespace.useru id中,如果没有,则首先通过Ajax登录。

另一个是竞争条件:如果您能够快速切换窗口以使其混淆,那么您可能会以relogin1->relogin2->ajax1->ajax2序列结束,而ajax1是在错误的会话下进行的。解决这个问题的方法是将登录Ajax请求推送到数组上,然后存储,在发出新的登录请求之前,中止所有当前请求。

最后要注意的是窗口刷新。如果有人在Ajax登录请求处于活动状态但未完成时刷新窗口,则该窗口将以错误的人的名义刷新。在这种情况下,您可以使用非标准的beforeunload事件警告用户可能发生的混淆,并要求他们单击Cancel,同时重新发出Ajax登录请求。然后他们唯一能阻止它的方法是在请求完成之前单击OK(或者意外地点击Enter/Spacebar,因为OK是——不幸的是——在本例中是——默认的)。还有其他方法来处理这种情况,如检测F5和Ctrl+R/Alt+R按下,在大多数情况下都可以工作,但可能会被用户键盘快捷键阻止。重新配置或替代操作系统使用。然而,在现实中,这有点像是一个边缘案例,最坏的情况也不会那么糟糕:在一个荣誉系统配置中,你会以错误的身份登录(但是你可以通过用颜色、样式、突出显示的名称等个性化页面来明确这一点);在一个密码配置中,责任是开启的。最后一个输入密码以退出或共享其会话的人,或者如果此人实际上是当前用户,则不会有违反。

但最终,每个选项卡应用程序只有一个用户(希望)可以正常工作,而不必设置配置文件、使用IE或重写URL。不过,请确保在登录到特定选项卡的每个选项卡中都能明显看到…


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

所以解是随机子域。

1
2
3
23423.abc.com
242234.abc.com
235643.abc.com

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


我在这里说实话。…上面的一切可能是真的,也可能不是真的,但看起来太复杂了,或者不知道服务器端正在使用哪个选项卡。

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

奥卡姆的方法是:(不,我不是奥卡姆,他死于1347年)

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

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

3)在focus(和/或mouseover)函数中,设置一个包含window.name的cookie。

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

客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//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)

1
2
3
4
5
6
7
8
9
10
11
12
13
//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];

完成。


在javascript中,如何从同一个cookiedbased sessionid下的另一个浏览器窗口中唯一地标识一个浏览器窗口

基本上使用window.name。如果未设置,请将其设置为唯一值并使用它。它在属于同一会话的各个选项卡之间是不同的。


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

虽然这主要针对JSF,但是您可以从以下位置了解一些想法:http://docs.jboss.org/seam/latest/reference/en us/html_single/d0e3620


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


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


我最近用cookies开发了一个解决这个问题的方法。这是我的解决方案的链接。我还包含了使用ASP.NET的解决方案的示例代码,如果需要的话,您应该能够将其适配到JSP或servelets。

https://sites.google.com/site/saritechworld/track-client-windows


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


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

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

https://developer.mozilla.org/en-us/docs/web/guide/api/dom/storage

希望这有帮助。


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

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


1
How to differ sessions in browser-tabs?

在浏览器选项卡中不同会话的最简单方法是不允许特定域设置cookie。这样,您就可以从单独的选项卡中拥有单独的会话。假设您不允许来自此域的cookie:www.xyz.com。打开选项卡1,登录并开始浏览。然后打开选项卡2,您可以以相同用户或不同用户的身份登录;无论哪种方式,您都将有一个与选项卡1不同的会话。等等。

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


你需要这么做

1-存储帐户列表的cookie

2-可选存储默认cookie

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

4-输入URL表示帐户索引,如果不是,则选择默认的索引。像google mail domain.com/0/some-url>>0这里代表帐户索引另外,您可能需要知道如何使用urlwrite

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

当做


我看到许多实现都有客户端更改来操作会话ID cookie。但是,一般会话ID cookie应该是HTTP5,所以Java脚本不能访问,否则可能导致XSS的会话劫持。


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

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

为了使此工作,每一页必须包括一个表单:

1
2
3
4
5
6
<form method="post" action="/handler">

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

</form>

你身边的每一个动作,包括导航,都会将表单发回(根据情况设置action)。对于"不安全"请求,您可以包含另一个参数,例如包含要提交的数据的JSON值:

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

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

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

  • 不依赖javascript或HTML5。
  • 本质上防止CSRF。
  • 不依赖饼干,所以可以防止卷毛狗。
  • 不易受到会话固定的影响。
  • 可以防止使用后退按钮,当您希望用户通过您的站点遵循一个设置的路径时,这是可取的(这意味着逻辑错误,有时可能会受到无序请求的攻击,可以避免)。

一些缺点:

  • 可能需要"后退"按钮功能。
  • 缓存不是很有效,因为每个操作都是一个日志。

此处提供更多信息。


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

  • 我给窗口分配了一个名称。这个名称与连接资源相同。
  • 加上存储在cookie中用于附加连接的1到rid。
  • 我创建了一个函数来捕获所有的xmlout响应,并以JSON格式将sid和rid分配给cookie。我为每个window.name都这样做。

这里的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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);
    }

希望能帮到你


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

在阅读了这些答案之后,特别是迈克尔·博格沃德给出的答案之后,我意识到需要存在的工作流程:

  • 如果用户导航到登录屏幕,请检查现有会话。如果存在,则绕过登录屏幕并将其发送到欢迎屏幕。
  • 如果用户(在我的例子中)导航到注册屏幕,请检查现有会话。如果存在,请告知用户您将要注销该会话。如果他们同意,请注销并开始注册。
  • 这将解决用户在会话中看到"其他用户"数据的问题。他们在会话中没有看到"另一个用户的"数据,而是看到了他们打开的唯一会话中的数据。显然,这会导致一些有趣的数据,因为某些操作会覆盖某些会话数据,而不是其他数据,因此您可以在单个会话中组合数据。

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

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

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