ajax 跨域 session 及 spring boot分布式session

这里写图片描述

未跨域时Ajax请求不同的action时,session保持不变

这里写图片描述

跨域Ajax异步请求时,每次请求都是一个新的session

这里写图片描述

一些帖子供参考

http://www.cnblogs.com/interdrp/p/4056525.html 
http://blog.csdn.net/qq_29845761/article/details/51897705

同一个容器内的请求不会出现session失效,异步跨域访问则需要考虑session共享问题



(二)参考http://www.cnblogs.com/gdlin/p/6846638.html

情景:公司的一个网站有一个模块(测试模块)需要单独用另外的一个域名(www.xyz.com)去访问,即网站需要用两个不同的域名去访问,如首页(www.abc.com)和测试模块(www.xyz.com)

这时候就涉及到session跨域问题,因为域名不是父子关系,所以必须要实现完全跨域,想到了以下三个解决办法:

1.URL传参:测试模块访问的时候,地址www.xyz.com后把主域名的session通过参数的形式传递过去,如:www.xyz.com;jsessionid=7D4DED1F2DB5BC53961EFED18BCE7E30

2.SSO单点登录

3.利用jsonp的跨域特性,通过ajax进行session传递

第一种方案考虑URL传参数不美观以及URL需要分享给别人的时候就没法获取到session,所以放弃

第二种SSO单点登录方案应该在三种方案中最优的,因为系统架构问题,所以也放弃

我的项目中采用第1种方案,直接成功。

在本例中,对方的前提是 服务器相同,域名不同,且非父子域名cookie无法共享


(三)周边情况

http://blog.csdn.net/ahhsxy/article/details/7356128

这篇文章前提是   服务器相同,域名不同,父子域名,文章详细分析了sesseion本质及解决方案

文章首先:要在不同的二级域名共享session,只需要把JESSIONID写进共享Cookie就行了。

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

所以:在所有sesseion的地方:

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,重置cokkie的domain为父域名,从根源上消除二级域名的cokkie,返回到客户端的cokkie只有一级域名的

本人认为,此法开销挺大,先要找到所有session的地方,如果有落网之鱼,出现了一个二级域名的cokkie,tomcat会以最佳匹配方式选中二级域名的session,多域名session即刻失效,比如:

访问B.xx.com,取得xx.com的cokkie——访问A.xx.com的函数a(此函数需要验证身份),以一级域名xx.com验证身份成功——访问A.qq.com的函数b(此函数b未处理session的域名,且无需验证身份),虽然验证身份失败,但是名义上请求也成功了-因为b未对身份做权限拦截,且额外生成一个A.xx.com的cokkie,污染了整个跨域名session系统——再回来访问访问A.xx.com的函数a,由于本地cokkie存在两个,浏览器会将2个都放在request_header中发送请求,tomcat以最佳匹配原则取了A.xx.com的cokkie,此cokkie是tomcat在访问函数b时自动生成的session,里面无相关权限信息,至此客户端在不知不觉中验证身份失败了,虽然两次访问同一个请求函数a,结果却莫名不同

后话:当然也可以在HttpSessionListener中统一处理


作者也表示不想使用单点登录,所以最终采用修改tomcat配置搞定





(四)基于redis的单点登录

http://blog.csdn.net/Angry_Mills/article/details/73866332

先简单说一下单点登录

把登录的部分单独拿出来作为一个项目,专门用来登录。

当我想访问某个子项目或者模块的时候,会先请求登录的部分,如果登录过了,就不需要再登录了。

这个和单独项目时,把userId放到session中道理是一样的

因为多个子项目在不同的tomcat无法实现session共享,这时我们可以利用cookie


多说几句关于cookie和session的区别

cookie是针对于浏览器的,session是针对服务器的。

cookie的产生会有JSessionId,也就是session的唯一凭证。

cookie是当前浏览器对该父域名用作数据共享和记录的。      

session是在服务器上开辟了一块内存,用于当前session(同一JSessionId)用户的数据共享。

关于cookie和session的区别只是个人理解,有不同的看法可以提,多交流。


说一下流程:

用户在一个模块登录,会进到登录系统,先验证你的用户名和密码是否正确,如果正确。

会产生一个唯一票据,我们这里叫他token,把这个token放到redis中,存放方式是这样的。

redis.put(token,userId);//存储一条对应用户信息的token

redis,put("your setting",token);//再存储一条对应userId的唯一标识,your setting可以写成loginSys+userId

为什么要这么存,正常的思路是只存上面一条就行了,稍后解答。


我们把token返回,response.addCookie(new Cookie("my cookie",token));//这里的my cookie可以写成loingSysCookie

流程实现是这样的,需要设置过期时间等等参数,这里就不写了。


这时候只要是同一浏览器,就会带着这个cookie,访问该项目下的子项目(父级域名下的子级域名,比如www.baidu.com和mp3.baidu.com的关系),

进入登录系统,拿到token,进到redis中比对,是否有用户就可以了,如果有,就不需要再登录了。

下面说一下上面的疑问:

当我再一次登录系统的时候,先去找redis中的redis,get("your setting")是否为空,已经在别处登录过的用户,可以取到他的token,

redis.del(token),这样就实现了单点登录,同步登出。

之后再设置redis和cookie,方法同上。


很多没有redis中放第二条,就无法实现单点登录同步登出,虽然设置了过期时间,但用户体验和安全性还是差一点。



该法没有用session,仅用了cokkie,所以不存在tomcat的cokkie和自己生成的session cookie冲突问题


但仍有两个弊端:

1.如果不是父子域名就没办法了

2. 客户端禁用cookie


总结:

1.如果请求域名不同,存在父子关系

只能放弃session+cookie方式,因为一次登录要给父域名注册cookie,且还要考虑tomcat自动生成的二级域名cookie与手动注册的一级域名cookie冲突,每次覆盖sesseion(无谓的开销)以及spring等框架对session的封装;

可采用cokkie(或localstrage)+token(jsessionID),在请求时一起带上


2.如果请求域名不同,不存在父子关系

可采用localstrage+token(jsessionID),在请求时一起带上


3.ajax请求不会写入cokkie,也不会带上cokkie,所以只能在请求时捎带 jsessionid 或者token机制


4.如果请求域名相同,服务器不同

则直接在服务器端集成分布式session;

spring boot 分布式session(利用redis)方法参考:

http://www.cnblogs.com/mengmeng89012/p/5519698.html


这次带来的是spring boot + redis 实现session共享的教程。

 

在spring boot的文档中,告诉我们添加@EnableRedisHttpSession来开启spring session支持,配置如下:

Java代码  
  1. @Configuration  
  2. @EnableRedisHttpSession  
  3. public class RedisSessionConfig {  
  4. }  

而@EnableRedisHttpSession这个注解是由spring-session-data-redis提供的,所以在pom.xml文件中添加:

Java代码  
  1. <dependency>  
  2.         <groupId>org.springframework.boot</groupId>  
  3.         <artifactId>spring-boot-starter-redis</artifactId>  
  4. </dependency>  
  5. <dependency>  
  6.         <groupId>org.springframework.session</groupId>  
  7.         <artifactId>spring-session-data-redis</artifactId>  
  8. </dependency>  

 

 

接下来,则需要在application.properties中配置redis服务器的位置了,在这里,我们就用本机:

Java代码  
  1. spring.redis.host=localhost  
  2. spring.redis.port=6379  

这样以来,最简单的spring boot + redis实现session共享就完成了,下面进行下测试。

 

首先我们开启两个tomcat服务,端口分别为8080和9090,在application.properties中进行设置【下载地址】   

Java代码  
  1. server.port=8080  

 

接下来定义一个Controller: 

Java代码  
  1. @RestController  
  2. @RequestMapping(value = "/admin/v1")  
  3. public class QuickRun {  
  4.     @RequestMapping(value = "/first", method = RequestMethod.GET)  
  5.     public Map<String, Object> firstResp (HttpServletRequest request){  
  6.         Map<String, Object> map = new HashMap<>();  
  7.         request.getSession().setAttribute("request Url", request.getRequestURL());  
  8.         map.put("request Url", request.getRequestURL());  
  9.         return map;  
  10.     }  
  11.   
  12.     @RequestMapping(value = "/sessions", method = RequestMethod.GET)  
  13.     public Object sessions (HttpServletRequest request){  
  14.         Map<String, Object> map = new HashMap<>();  
  15.         map.put("sessionId", request.getSession().getId());  
  16.         map.put("message", request.getSession().getAttribute("map"));  
  17.         return map;  
  18.     }  
  19. }  

 

启动之后进行访问测试,首先访问8080端口的tomcat,返回 获取【下载地址】   

Java代码  
  1. {"request Url":"http://localhost:8080/admin/v1/first"}  

 接着,我们访问8080端口的sessions,返回:

Java代码  
  1. {"sessionId":"efcc85c0-9ad2-49a6-a38f-9004403776b5","message":"http://localhost:8080/admin/v1/first"}  

最后,再访问9090端口的sessions,返回:

Java代码  
  1. {"sessionId":"efcc85c0-9ad2-49a6-a38f-9004403776b5","message":"http://localhost:8080/admin/v1/first"}  

可见,8080与9090两个服务器返回结果一样,实现了session的共享

 

如果此时再访问9090端口的first的话,首先返回:

Java代码  
  1. {"request Url":"http://localhost:9090/admin/v1/first"}  

而两个服务器的sessions都是返回:

Java代码  
  1. {"sessionId":"efcc85c0-9ad2-49a6-a38f-9004403776b5","message":"http://localhost:9090/admin/v1/first"}  

 

通过spring boot + redis来实现session的共享非常简单,而且用处也极大,配合nginx进行负载均衡,便能实现分布式的应用了。

本次的redis并没有进行主从、读写分离等等配置(_(:з」∠)_其实是博主懒,还没尝试过.......)

而且,nginx的单点故障也是我们应用的障碍......以后可能会有对此次博客的改进版本,比如使用zookeeper进行负载均衡,敬请期待。


posted on 2017-09-26 13:28  silyvin  阅读(638)  评论(0编辑  收藏  举报