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中放第二条,就无法实现单点登录同步登出,虽然设置了过期时间,但用户体验和安全性还是差一点。
但仍有两个弊端:
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支持,配置如下:
- @Configuration
- @EnableRedisHttpSession
- public class RedisSessionConfig {
- }
而@EnableRedisHttpSession这个注解是由spring-session-data-redis提供的,所以在pom.xml文件中添加:
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-redis</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.session</groupId>
- <artifactId>spring-session-data-redis</artifactId>
- </dependency>
接下来,则需要在application.properties中配置redis服务器的位置了,在这里,我们就用本机:
- spring.redis.host=localhost
- spring.redis.port=6379
这样以来,最简单的spring boot + redis实现session共享就完成了,下面进行下测试。
首先我们开启两个tomcat服务,端口分别为8080和9090,在application.properties中进行设置【下载地址】 :
- server.port=8080
接下来定义一个Controller:
- @RestController
- @RequestMapping(value = "/admin/v1")
- public class QuickRun {
- @RequestMapping(value = "/first", method = RequestMethod.GET)
- public Map<String, Object> firstResp (HttpServletRequest request){
- Map<String, Object> map = new HashMap<>();
- request.getSession().setAttribute("request Url", request.getRequestURL());
- map.put("request Url", request.getRequestURL());
- return map;
- }
- @RequestMapping(value = "/sessions", method = RequestMethod.GET)
- public Object sessions (HttpServletRequest request){
- Map<String, Object> map = new HashMap<>();
- map.put("sessionId", request.getSession().getId());
- map.put("message", request.getSession().getAttribute("map"));
- return map;
- }
- }
启动之后进行访问测试,首先访问8080端口的tomcat,返回 获取【下载地址】 :
- {"request Url":"http://localhost:8080/admin/v1/first"}
接着,我们访问8080端口的sessions,返回:
- {"sessionId":"efcc85c0-9ad2-49a6-a38f-9004403776b5","message":"http://localhost:8080/admin/v1/first"}
最后,再访问9090端口的sessions,返回:
- {"sessionId":"efcc85c0-9ad2-49a6-a38f-9004403776b5","message":"http://localhost:8080/admin/v1/first"}
可见,8080与9090两个服务器返回结果一样,实现了session的共享
如果此时再访问9090端口的first的话,首先返回:
- {"request Url":"http://localhost:9090/admin/v1/first"}
而两个服务器的sessions都是返回:
- {"sessionId":"efcc85c0-9ad2-49a6-a38f-9004403776b5","message":"http://localhost:9090/admin/v1/first"}
通过spring boot + redis来实现session的共享非常简单,而且用处也极大,配合nginx进行负载均衡,便能实现分布式的应用了。
本次的redis并没有进行主从、读写分离等等配置(_(:з」∠)_其实是博主懒,还没尝试过.......)
而且,nginx的单点故障也是我们应用的障碍......以后可能会有对此次博客的改进版本,比如使用zookeeper进行负载均衡,敬请期待。