转载自:http://blog.csdn.net/ahhsxy/article/details/7356128

这里所说的跨域,是指跨二级域名,而且这些域名对应的应用都在同一个app上, 比如我有以下3个域名:

www.vinceruan.info

blog.vinceruan.info

bbs.vinceruan.info

我要在这三个域名直接共享cookie或者共享session,如何实现呢?在tomcat下又如何实现呢?



首先我们来了解下cookie,顾名思义,小甜心,少食即可,多吃无益。cookie是通过浏览器保存在客户端的临时数据,一般这些数据对安全的要求不高,虽然可以通过加密存放和SSL方式提交的方式加强cookie的安全,但不代表cookie就是百分之百安全的。再者,写入太多的cookie会造成数据管理的不可控,所以建议尽量少写cookie,像网易、新浪这些大型互联网公司,如果观察一下他们的站点,你会发觉他们会在浏览器写下大量的cookie,不过他们内部一般会有一套严格的cookie写入和清理的管理规定。比如网易邮件事业部就有这些规定。



cookie有两个很重要的属性:domain和path

domain告诉浏览器当前要添加的cookie的域名归属,如果没有明确指明则默认为当前域名,比如通过访问www.vinceruan.info添加的cookie的域名默认就是www.vinceruan.info,通过访问blog.vinceruan.info所生成的cookie的域名就是blog.vinceruan.info.

path告诉浏览器当前要添加的cookie的路径归属,如果没有明确指明则默认为当前路径,比如通过访问www.vinceruan.info/java/hotspot.html添加的cookie的默认路径就是/java/,通过blog.vinceruan.info/java/hotspot.html生成的cookie的路径也是/java/.



在清楚domain和path的生成规则后,我们需要知道浏览器在什么时候提交什么cookie到服务器,即浏览器是通过怎样的规则筛选cookie并提交到服务器的?

浏览器提交的cookie需要满足以下两点:

1.当前域名或者父域名下的cookie

而且

2.当前路径或父路径下的cookie

要满足以上两个条件的cookie才会被提交.举个例子:有4个cookie:

cookie1:[name=value, domain=.vinceruan.info path=/]

cookie2:[name=value, domain=blog.vinceruan.info path=/java/]

cookie3:[name=value, domain=www.vinceruan.info path=/]

cookie4:[name=value, domain=blog.vinceruan.info path=/]

当我访问blog.vinceruan.info时,

cookie1可以被提交,因为.vinceruan.info是blog.vinceruan.info的父域名. path路径也一致.

cookie2不能被提交,因为虽然domain是保持一致的,但是path不一致,当前访问的是/, 但是cookie2的path是/java/

cookie3不能被提交,因为虽然path是一致的,但是www.vinceruan.info不是blog.vinceruan.info的父域名.

cookie4可以被提交,因为domain和cookie都严格保持一致.



这里需要注意的是, 在浏览器看来.www.vinceruan.info不是blog.vinceruan.info的父域名,而vinceruan.info才是blog.vinceruan.info的父域名,www.vinceruan.info也算是一个二级域名(这点如果你提交过域名到DNS服务器商的应该会知道,一般我们需要显式提交www.vinceruan.info和vinceruan.info,
否则www.vinceruan.info==vinceruan.info是不成立的).

所以如果我们需要在所有二级域名下共享islogin=1的cookie,用java代码如下:

Cookie c = new Cookie("islogin","1");

c.setDomain(".vinceruan.info");//注意是以点号开头的.

c.setPath=("/");

response.addCookie(c);



如果要在所有的二级域名下的/java/路径下共享silogin=1的cookie,用java代码如下:

Cookie c = new Cookie("islogin","1");

c.setDomain(".vinceruan.info");//注意是以点号开头的.

c.setPath=("/java/");

response.addCookie(c);



ok, 跨域共享cookie就完成了,比较容易,但是跨域共享session呢?

首先我们需要了解session机制的原理, session都是保存在服务器端的,而http连接是无状态的,那么服务器是如何在多次独立的http连接中为你维持同一个session呢?

这依赖于一个叫JSESSIONID的参数,这个参数可以从cookie或url获得,你如果你打开firebug,同时你的firefox是没有禁用cookie的,那么你刷新几次页面,你会看到浏览器提交的cookie中有一个叫JSESSIONID的key/value,这个就是会话ID,在这里你可以做一个试验,我们都知道firefox和IE
的cookie是不共享的,因此你在firefox下登录了,你用IE打开同一个网址,页面会显示你还处在未登录的状态,但是如果我在firefox下已经登录,并且拿到JSESSIONID的值,再到IE中输入url,并且加入JSESSION=.....的参数,你会发现,IE下也会显示我已经登录了。

这说明,如果我通过网络截除到了别人的JESSIONID,我就可以在服务器的会话还没过期前访问该站,如果对方还是处于登录状态的,我就可以修改对方资料甚至密码,多么可怕,当然这一切都是理论上的。

好了,一切都显得很顺利,要在不同的二级域名共享session,只需要把JESSIONID写进共享Cookie就行了。



可是,真的有这么容易么?

不同语言的实现手法不一样,听PHP方面的高手说是很容易实现的,可是java就比较难了,首先我们知道了request.getSession(true)的原理。

我这里tomcat为例,因为Java servlet api只是一套servlet接口规范,不同的java web容器实现的具体细节可能不同。

在tomcat中,当我们调用request.getSession(true)时,如果实际上新建了一个session,那么就会往浏览器写入一个JSEESIONID的cookie.这个cookie的domain属性是跟你当前站的域名是严格保持一致的,即类似于www.vinceruan.info或 blog.vinceruan.info而不是.vinceruan.info,
所以如果我们在session创建时就写入一个共享的cookie就行了.



可是这还是有困难:

1.如果我们在访问www.vinceruan.info新建了一个session的同时, 我写入一个JSESSIONID=****的、domain是.vinceruan.info的cookie, 理论上blog.vinceruan.info 也可以访问到在www.vinceruan.info这边创建的session了,但是很遗憾,如果我在访问www.vinceruan.info前先访问了blog.vinceruan.info,
同时创建了属于blog.vinceruan.info的session,这是再去访问www.vinceruan.info站点,同时建立了另一个session,并且建立了共享的JSESSIONID,这时再回去访问blog.vinceruan.info时,你会看到浏览器提交了两个JSESSIONID的cookie,类似于:

JSESSIONID=EXAD2AD5FA4D1A5D4 path =/ domain=blog.vinceruan.info

JSESSIONID=254SD2S5S1D2S5DS5 path = / domain=.vinceruan.info

可以看出,后者才是我手工创建的,而前者是tomcat创建的。很遗憾,我手工创建的不生效,因为tomcat在绑定session时,采取严格匹配更加优先的原则,blog.vinceruan.info比.vinceruan.info更加匹配。

所以问题已经转化为无论是那个应用,我要想办法在第一个创建session的应用中同时写入共享的JSESSIONID,即是说,我在写JSESSIONID到某个域下时,不允许这个域及其子域存在JSESSIONID不同值的情况。怎么解决呢?或许可以通过以下代码解决:

public Session getSession(HttpServletRequest request, HttpServletResponse response){

      HttpSession session = request.getSession(false);

       if (session==null){

             session = request.getSession(true);

             String session_id = session.getId();

             Cookie c = new Cookie("JSESSIONID",session_id);

             c.setDomain(".vinceruan.info");

             c.setPath("/");

             response.addCookie();

        }

}

然后在所有应用中需要用到session的地方都调用这个方法,好像真的可以解决了,但是真的可以么?

还是有问题:

1.如果我已经往response里面输出body内容了,而这时调用这个方法请求session,且session是第一次被创建时会有问题。

2.对于我调用的第三方代码,比如spring,它们里面调用session的方式不是我能控制的。



好的,这个方法已经明显不行了。

还有一个HttpSessionListener,  它里面还有一个sessionCreated(SessionEvent event)的回调方法,无论是在什么代码中新建session,这个回调接口是一定会被调用的,但是,我拿不到HttpServletResponse句柄.所以这个方法还是不行.



我没有用SSO,是因为我这个应用的需求很简单,就是共享登录状态,而且就一个应用,只不过对应到不同的域名而已,所以没有必要用那么重量级的东西。



最后的修改是为tomcat增加一个Valve,同时注册到server.xml中去,这种方式也不太好,因为跟tomcat耦合了,但是也是目前可以想到的比较好的一个方法了。

首先新建一个类:

public class SessionCrossDomainValve extends ValveBase {



public SessionCrossDomainValve() {

super();

info = "com.jinfuzi.SessionCrossDomainValve";

}



@Override

public void invoke(Request request, Response response) throws IOException, ServletException {

request.getSession(true);

// replace any Tomcat-generated session cookies with our own

Cookie[] cookies = response.getCookies();

if (cookies != null) {

for (int i = 0; i < cookies.length; i++) {

Cookie cookie = cookies;

containerLog.debug("CrossSubdomainSessionValve: Cookie name is "

+ cookie.getName());

if (Globals.SESSION_COOKIE_NAME.equals(cookie.getName())) {

replaceCookie(request, response, cookie);

}

}

}

// process the next valve

getNext().invoke(request, response);

}



   @SuppressWarnings("unchecked")  

      protected void replaceCookie(Request request, Response response, Cookie cookie) {  

          //copy the existing session cookie, but use a different domain  

          Cookie newCookie = new Cookie(cookie.getName(), cookie.getValue());  

          if (cookie.getPath() != null)  

              newCookie.setPath(cookie.getPath());  

          newCookie.setDomain(getCookieDomain(request));  

          newCookie.setMaxAge(cookie.getMaxAge());  

          newCookie.setVersion(cookie.getVersion());  

          if (cookie.getComment() != null)  

              newCookie.setComment(cookie.getComment());  

          newCookie.setSecure(cookie.getSecure());  

    

          //if the response has already been committed, our replacement strategy will have no effect  

          if (response.isCommitted())  

              containerLog.error("CrossSubdomainSessionValve: response was already committed!");  

    

          //find the Set-Cookie header for the existing cookie and replace its value with new cookie  

          MimeHeaders headers = response.getCoyoteResponse().getMimeHeaders();  

          for (int i = 0, size = headers.size(); i < size; i++)  

          {  

              if (headers.getName(i).equals("Set-Cookie"))  

              {  

                  MessageBytes value = headers.getValue(i);  

                  if (value.indexOf(cookie.getName()) >= 0)  

                  {  

                      StringBuffer buffer = new StringBuffer();  

                      ServerCookie.appendCookieValue(buffer, newCookie.getVersion(), newCookie  

                              .getName(), newCookie.getValue(),  newCookie.getPath(), newCookie

                              .getDomain(), newCookie.getComment(), newCookie.getMaxAge(), newCookie  

                              .getSecure());  //如果是tomcat6.020,这里需要多加一个true.

                      containerLog.debug("CrossSubdomainSessionValve: old Set-Cookie value: " + value.toString());  

                      containerLog.debug("CrossSubdomainSessionValve: new Set-Cookie value: " + buffer);  

                      value.setString(buffer.toString());  

                  }  

              }  

          }  

      }

   

    protected String getCookieDomain(Request request) {

String cookieDomain = request.getServerName();

String[] parts = cookieDomain.split("\\.");

if (parts.length >= 2)

cookieDomain = parts[parts.length - 2] + "."

+ parts[parts.length - 1];

return "." + cookieDomain;

    }



public String toString() {

return ("CrossSubdomainSessionValve[container=" + container.getName() + ']');

}  

}

将这个类打包成jar包,放进{catalina_home}/lib下,并在server.xml中注册:

<Valve className="SessionCrossDomainValve"/>

跨域共享cookie和跨域共享session的更多相关文章

  1. [php-cookie] cookie 请求跨域,如何共享

    cookie 请求跨域的问题, 假设我有两个域名,一个 m.example.com,另一个是 www.example.com . 那么我需要如何设置 cookie 才可以在这两个域名都实现共享呢? / ...

  2. cookie用法小结 cookie.setPath 跨域共享

    1. JSP中Cookie的读写 Cookie的本质是一个键值对,当浏览器访问web服务器的时候写入在客户端机器上,里面记录一些信息.Cookie还有一些附加信息,比如域名.有效时间.注释等等. 下面 ...

  3. 跨域共享cookie

    1. JSP中Cookie的读写 Cookie的本质是一个键值对,当浏览器访问web服务器的时候写入在客户端机器上,里面记录一些信息.Cookie还有一些附加信息,比如域名.有效时间.注释等等. 下面 ...

  4. Asp.Net 跨域,Asp.Net MVC 跨域,Session共享,CORS,Asp.Net CORS,Asp.Net MVC CORS,MVC CORS

    比如 http://www.test.com 和 http://m.test.com 一.简单粗暴的方法 Web.Config <system.web> <!--其他配置 省略……- ...

  5. Cookie的跨域问题

    被误解的HttpCookie.Domain属性 有人说可以利用HttpCookie.Domain属性实现跨域访问,假如在A站(A.com)中写B站(B.com)的cookie,如下所示 这其实是错误的 ...

  6. web跨域及cookie相关知识总结

    原文:web跨域及cookie相关知识总结   之前对于跨域相关的知识一致都很零碎,正好现在的代码中用到了跨域相关的,现在来对这些知识做一个汇总整理,方便自己查看,说不定也可能对你有所帮助. 本篇主要 ...

  7. localStorage和cookie的跨域解决方案

    原文转自:点我 前言 localStorage和cookie大家都用过,我前面也有文章介绍过,跨域大家也都了解,我前面也有文章详细描述过.但是localStorage和cookie的跨域问题,好多小伙 ...

  8. PHP 通过设置P3P头来实现跨域访问COOKIE

    CentOS的系统(Linux 内核) 编辑HOST vi /etc/hosts 加入127.0.0.1 www.a.com127.0.0.1 www.b.com 首先:创建 a_setcookie. ...

  9. CP="CAO PSA OUR" 用P3P header解决iframe跨域访问cookie

    1.IE浏览器iframe跨域丢失Session问题 在开发中,我们经常会遇到使用Frame来工作,而且有时是为了跟其他网站集成,应用到多域的情况下,而Iframe是不能保存Session的因此,网上 ...

随机推荐

  1. python函数中的位置参数、默认参数、关键字参数、可变参数区别

    一.位置参数 调用函数时根据函数定义的参数位置来传递参数. #!/usr/bin/env python # coding=utf-8 def print_hello(name, sex): sex_d ...

  2. JavaScript筑基篇(三)->JS原型和原型链的理解

    删除理由:很久以前写的,当时理解不够深入,这样描述反而看起来更复杂了.因此就删掉,免得误人子弟! 可以看看另一篇文章:[如何继承Date对象?由一道题彻底弄懂JS继承.](http://www.cnb ...

  3. HBase 参考文档翻译之 Getting Started

    本篇是对HBase官方参考文档的大体翻译,介于本人英文水平实在有限,难免有纰漏之处.本篇不只是对官方文档的翻译,还加入了一些本人对HBase的理解.在翻译过程中,一些没有营养的废话,我就忽略了没有翻译 ...

  4. Web界面和Winform界面生成,代码生成工具

    在上面一篇随笔<代码生成工具之界面快速生成>介绍了代码生成工具Database2Sharp的界面生成操作,其中介绍了Web界面(包括列表界面.内容显示.内容编辑界面的生成,另外还介绍了Wi ...

  5. 移动端的picker参考vux

    参考vux移动端的ui组件,做了一个picker,测试在微信,uc主流浏览器能够正常工作.而在华为浏览器根本不能使用.而测试了vux的原有picker组件,发现在华为自带浏览器中,效果依然能够实现. ...

  6. 在Delphi中如何获得SQL中存储过程的返回值?

    示例存储过程:create procedure proc_loginusername varchar(20),password varchar(20)asdeclare @result intsele ...

  7. Delphi中Self和Sender的区别

    在事件处理程序参数表中,至少含有一个参数Sender,它代表触发事件处理程序的构件,如在上例中,Sender就指Button2,有了Sender参数,可以使多个构件共用相同的事件处理程序,如下例:   ...

  8. window.navigator.standalone 检测iOS WebApp是否运行在全屏模式

    iOS上的Safari浏览器可以让Web应用程序全屏显示,以取得类似本地应用的显示效果.但是这需要用户把Web应用程序的图标添加到主屏幕才可以.作为开发者,为了更好的显示效果,我们可能希望自己开发的W ...

  9. openstack之keystone部署

    前言 openstack更新频率是挺快的,每六个月更新一次(命名是是以A-Z的方式,Austin,Bexar...Newton).博主建议大家先可一种版本研究,等某一版本研究透彻了,在去研究新的版本. ...

  10. [洛谷P4111][HEOI2015]小Z的房间

    题目大意:有一个$n\times m$的房间,一些位置是房间,另一些位置是柱子,相邻两个房间之间有墙,问有多少种方案可以打通一些墙把所有房间连成一棵树,柱子不可以打通 题解:矩阵树定理,把房间当点,墙 ...