最近玩了下使用的websocket技术做消息推送,过程中遇到了session的问题,问题描述如下:
用户登录的时,将用户信息保存到session中,用户主页通过ws协议的方式访问服务器,访问地址:ws://127.0.0.1:8080/webSocketServer",websocket拦截器获取用户seesion的时候,发现session为null。
尝试调试过多次,session获取始终未空,问题出在哪呢?
我们来看一下session的概要:
Session概要
Session 是用于保持状态的基于 Web 服务器的方法,在 Web 服务器上保持用户的状态信息供在任何时间从任何页访问。Session 允许通过将对象存储在 Web 服务器的内存中在整个用户会话过程中保持任何对象。当我们使用用户名和密码登陆网站,系统会首先验证当前登陆用户是否合法,当合法后将用户名等相关信息保存在Session 中。登陆后点击进入某功能页面时,系统也会去判断当前你是否有访问权限,判断的方式是验证Session 中的内容是否正确。
Session是客户端与服务器端建立的会话,总是放在服务器上的,服务器会为每次会话建立一个sessionId,每个客户会跟一个sessionID对应。并不是关闭浏览器就结束了本次会话,通常是用户执行“退出”操作或者会话超时时才会结束。
session代表的是一个回话,在WEB开发中,服务器可以为每个用户浏览器创建一个会话对象(session对象),注意:一个浏览器独占一个session对象(默认情况下)。因此,在需要保存用户数据时,服务器程序可以把用户数据写到用户浏览器独占的session中,当用户使用浏览器访问其它程序时,其它程序可以从用户的session中取出该用户的数据,为用户服务。
服务器是如何实现一个session为一个用户浏览器服务的?
服务器创建session出来后,会把session的id号,以cookie的形式回写给客户机,这样,只要客户机的浏览器不关,再去访问服务器时,都会带着session的id号去,服务器发现客户机浏览器带session id过来了,就会使用内存中与之对应的session为之服务。
来看一下session为null的实际案例:
服务端设置session的代码:
@RequestMapping("") public String index(HttpServletRequest httpRequest, String nickname) { HttpSession httpSession = httpRequest.getSession(); System.out.println("###跳转进入到了首页###"); User user = new User(); SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateString = formatter.format(new Date()); user.setId(dateString + "-" + nickname); user.setNickname(nickname); // 登录操作 // 判断是否是一个已经登录的用户,没有则登录 if (null != httpSession.getAttribute("loginUser")) { // 清除旧的用户 httpSession.removeAttribute("loginUser"); } // 将用户放入session httpSession.setAttribute("loginUser", user); return "redirect:/index/mainpage"; }
前端sebsocket获取session的代码:
var url = "ws://127.0.0.1:8080/"+ "webSocketServer"; ws = websocket.util.getWs(url); websocket.util.onopen(ws);
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception { System.out.println("握手之前····"); if (request instanceof ServletServerHttpRequest) { ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request; HttpSession session = servletRequest.getServletRequest().getSession(false); //如果用户已经登录,允许聊天 if(session !=null && session.getAttribute("loginUser")!=null){ //获取登录的用户 User loginUser=(User)session.getAttribute("loginUser") ; //将用户放入socket处理器的会话(WebSocketSession)中 attributes.put("loginUser", loginUser); System.out.println("Websocket:用户[ID:" + (loginUser.getId() + ",Name:"+loginUser.getNickname()+"]要建立连接")); }else{ //用户没有登录,拒绝聊天 //握手失败! System.out.println("--------------握手已失败..."); return false; } } return super.beforeHandshake(request, response, wsHandler, attributes); }
通过beforeHandShake方法获取session为null,发现使用ip的方式,不是同一个sessionId,得到的session自然为null,改为如下方式进行请求就可以了:
<% String path = request.getContextPath(); String basePath = request.getServerName() + ":" + request.getServerPort() + path + "/"; String baseUrlPath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; %>
var url = "ws://" + path + "webSocketServer"; ws = websocket.util.getWs(url); websocket.util.onopen(ws);
目前很多项目采用的是前后台分离的情况,关于前后端分离的session情况,笔人也测试过,目前前后台分离分2种情况部署:
一、在同一个服务器(tomcat)下部署,同源
服务A:服务端代码:
@RequestMapping(value = "test", method = RequestMethod.GET) @ResponseBody public String test(HttpServletRequest request, HttpServletResponse httpResponse) { /*httpResponse.setHeader("Access-Control-Allow-Credentials", "true"); httpResponse.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:8080"); httpResponse.setHeader("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With"); */ //判断,如果没有session,则跳到登录页面 HttpSession session = request.getSession(); if(null != session.getAttribute("loginUser")){ User user = (User) session.getAttribute("loginUser"); System.out.println("session 不为空"); return "session 不为空 nicknage :" + user.getNickname(); }else{ System.out.println("session ---------------------------- null"); return "session ---------------------------- null"; } }
客户端A:前端代码:
<head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>主界面</title> <script type="text/javascript" src="js/jquery-1.11.1.js"></script> <!-- js code --> <script type="text/javascript"> function test() { var a_cross_domain_url = "http://10.162.11.18:8080/spring-websocket/index/test"; alert(a_cross_domain_url); $.ajax({ url: a_cross_domain_url, // 将XHR对象的withCredentials设为true xhrFields: { withCredentials: false }, success: function(data){ alert(data); console.log(data); } }); } function testAave() { var a_cross_domain_url = "http://10.162.11.18:8080/spring-websocket/index/saveSession"; $.ajax({ url: a_cross_domain_url, // 将XHR对象的withCredentials设为true xhrFields: { withCredentials: false }, success: function(data){ alert(data); console.log(data); } }); } </script> </head> <body> <!-- 测试cession跨域问题 --> <br /><br /><br /> <input type="button" onclick="test();" value="test crus" /> <br /><br /><br /> <input type="button" onclick="testAave();" value="save session" /> </body> </html>
因为是同源的,所以不会存在跨域的问题,session也能正常的存和取!
二、在不同的服务器下部署,不同源
通过如上的方式访问肯定会遇到同源的问题,解决同源的问题可以看我这前写过的文章:ajax解决跨域问题,这样之后session也是能正常存和取的。