Session &Cookie
概念:
由于Http是无状态的,即一次http请求,服务端给出响应后,即断开服务,下次请求需要重建链接,在这两次的交互中,服务器是不知道这两次的客户端信息是否相同,为了跟踪用户的会话,提出了session和cookie机制。
Cookie通过客户端来记录用户信息。
Session通过服务端来记录用户信息。
Cookie:实际上是一小段文本信息,每次客户端访问时都加上这段信息,这样服务器就能拿到request中的信息,进行解析,然后确认用户身份。
Session:客户端首次访问服务端时,服务端会生成用户的一些信息,用hashMap进行保存,同时会返回一个sessionId,以cookie的形式作为response返回给客户端,在之后的会话期间,客户端的请求加上sessionId即可,服务端拿到request之后,会根据sessionId,去hashMap中找有没有httpSession,有的话就拿出来使用。
具体介绍:
Cookie属性:
Name:cookie的名称
Value:cookie的值,一般需要加密
maxAge:失效时间,负数表示临时cookie,关闭浏览器即失效;0表示删除该cookie,正数表示在maxAge秒之后失效。
Secure:是否使用安全传输协议。如https,ssl等
Path:cookie的路径,必须要以“/”结束。如设置为“/root/”,则只有contextPath为"/root"的程序可以访问Cookie,如果设为"/",则代表本域下的contextPath都可访问该Cookie。
Domain:域,必须以"."开头,如".jd.com",则所有"jd.com"结尾的域名服务都可以访问该cookie。如果跨域,比如360buy.com的旧接口就无法解析从.jd.com过来的请求。需要服务端进行特殊处理
Comment:备注
Version:版本
如:
Cookie生命周期:
主要是根据expire/max_age来判断,过期则失效。我们现在是用持久化cookie来解决会话问题,服务端保存了cookie用户的上下文对象,里面有cookie的创建时间,超时时间等,会对cookie的过期时间有个控制,拦截器来控制,比如用户请求后,服务端拿到cookie后,判断超时时间,如果剩余1/3的时间,可以重新刷新超时时间。
public String intercept(ActionInvocation invocation) throws Exception { //如果登录成功,则继续调用,否则返回 //判断是否配置了cookie的cookie名称 ActionContext actionContext = invocation.getInvocationContext(); HttpServletRequest request = (HttpServletRequest) actionContext.get(StrutsStatics.HTTP_REQUEST); HttpServletResponse response = (HttpServletResponse) actionContext.get(StrutsStatics.HTTP_RESPONSE); String value = cookieUtils.getCookieValue(request, loginCookieKey); if (StringUtils.isNotBlank(value)) {//能取到值 LoginContext context = getLoginContext(value); if (context != null) {//又能解出来 long current = System.currentTimeMillis(); long created = context.getCreated(); long expires = context.getExpires(); long timeout = expires == 0 ? sessionTimeout * 1000 : expires - created;//如果没有设置过期时间,则使用默认的 if (current - created < timeout) { //如果没有过期 LoginContext.setLoginContext(context); if ((current - created) * rate > timeout) {//如果剩下的时间只有2/3,就需要重新派发cookie log.error("session cookie[" + loginCookieKey + "] rewrite!"); //写最后一次访问的cookie context.setCreated(current); if (expires != 0) { context.setTimeout(timeout); } cookieUtils.setCookie(response, loginCookieKey, context.toCookieValue()); } //如果没过期,则继续调用 return invocation.invoke(); } else { log.error("session cookie[" + loginCookieKey + "] is valid!"); //超时后,要清空 ,返回客户端 cookieUtils.invalidate(request, response); } } else { log.error("session cookie[" + loginCookieKey + "] is error!"); } } |
Session生命周期:
为提高效率,服务器一般把session放入内存。每个用户都会有独立的session。如果session过于复杂,大量用户访问时就会内存溢出,所以,session应越小越好。Session是用户第一次访问服务器的时候创建的。用户关闭浏览器后session事实上并没有消失,还在内存中,一直等到过期才会消失。平时所说的消失,其实是指用户关闭浏览器重新打开后重新生成了新的session而已。Session生成后,只要用户继续访问,服务器就会更新session的最后访问时间,并维护该session。
Session属性:
setAttribute:map设值
getAttribute:获取value
getId():获取sessionid
Session共享:
1. 单独的session应用服务器
2. Session保存到缓存服务器中。
3. 基于NFS(Net File System),只需将共享目录服务器mount到各频道服务器的本地session目录即可。缺点是NFS依托于复杂的安全机制和文件系统,因此并发效率不高,尤其对于session这类高并发读写的小文件,会由于共享目录服务器io过高,拖垮web应用程序执行效率。
4. 基于数据库session共享。
5. 基于cookie的session共享: 将用户的session信息机密,序列化,统一放在根域名下。利用浏览器访问该根域名下的所有二级域名站点时,会传递与之域名对应的所有cookie内容的特性,从而事项用户的cookie化session在多服务间的共享。该方案不需要额外的服务器资源,但受到http协议头长度的限制,所以cookie在传递的时候要小,而且还要有有效的加密措施。这种策略优势比较明显,目前公司采用的就是这种方式。
Cookie模拟session的实例
1. 客户端获取RSA公钥
public String execute() { try { log.info("-----------存入公钥开始----------"); KeyPair keyPair = RSABuilder.generateKeyPair(); PrivateKey privateKey = keyPair.getPrivate(); this.privateKeyStr = Base64.encodeBytes(privateKey.getEncoded()); PublicKey publicKey = keyPair.getPublic(); this.publicKeyStr = Base64.encodeBytes(RSABuilder.confuse(publicKey.getEncoded())); returnResult.setCode(ResultCode.SUCCESS.getCode()); returnResult.setMessage(this.publicKeyStr);//把公钥返回给客户端 StringBuilder key = new StringBuilder(); key.append(c.getUuid()); if(StringUtils.isNotBlank(c.getAsid())){ key.append(c.getAsid()); } cacheUtils.set(key.toString(), 60 * 5, this.privateKeyStr); log.info("uuid:" + c.getUuid() + "|publicKey:" + publicKeyStr + "|privateKey:" + privateKeyStr); } catch (Exception e) { log.error(e); } return SUCCESS; } |
2. 客户端获取DES私钥
RsaDecoder rsaDecoder = RsaDecoder.getInstance(privateKey); String desKey = rsaDecoder.dencrypt(envelopeKey);//解密客户端传过来的随机数 log.info("uuid:" + c.getUuid() + "|dencryptContent:" + desKey); HttpServletResponse response = (HttpServletResponse) ActionContext.getContext().get(StrutsStatics.HTTP_RESPONSE); JdAppLoginContext context = JdAppLoginContext.getLoginContext(); if (context == null) { context = new JdAppLoginContext(); } String sessionKey = DESCoder.initKey(); log.info("uuid:" + c.getUuid() + "sessionKey:" + sessionKey); context.setSessionKey(sessionKey); JdAppLoginContext.setLoginContext(context); loginCookieUtil.setTwoDomainCookie(request, response, loginCookieKey, context.toCookieValue()); returnResult.setCode(ResultCode.SUCCESS.getCode()); //服务端生成一个sessionkey,用客户端传过来的key把这个sessionkey加密,然后发给客户端,之后客户端用这个sessionkey进行正文加密,服务端再解开就行了 String message = DesUtil.encrypt(sessionKey, desKey); returnResult.setMessage(message); |
3. 加密登录信息进行登录,成功后写cookie
if (loginAndRegisterResult.isSuccess()) { HttpServletResponse response = (HttpServletResponse) ActionContext.getContext().get(StrutsStatics.HTTP_RESPONSE); JdAppLoginContext context = JdAppLoginContext.getLoginContext(); if (context == null) { context = new JdAppLoginContext(); } context.setPin(loginAndRegisterResult.getUserPin()); //设置pin JdAppLoginContext.setLoginContext(context); String cookieValue = context.toCookieValue(); //用新的包装方法设置cookie,保证如果是360buy请求的就设为360buy的domain,如果是jd请求的,就设为jd的domain loginCookieUtil.setTwoDomainCookie(request,response, loginCookieKey, cookieValue); //写cookie的同时写入响应体中 loginAndRegisterResult.setAppLogin(cookieValue); //设置响应体,让wap网关不代理 response.setContentType("application/json;charset=utf-8;X-Wap-Proxy-Cookie=none"); //写入日志: loginAnalysisService.insert(request, "音乐登录", loginAndRegisterResult.getUserPin()); } |
4. 用户下次访问时,先进拦截器,拿到客户端cookie,解密,看能否解开,如果能解开说明已经登录,如果解不开说明没登陆。
String value = loginCookieUtil.getCookieValue(request, loginCookieKey); log.error("**************************** 在HttpServletRequest中拿cookie-----"+"loginCookieKey等于"+loginCookieKey+" 结果"+value); if (StringUtils.isNotBlank(value)) {//能取到值 LoginContext context = getLoginContext(value); long current = System.currentTimeMillis(); long created = context.getCreated(); long expires = context.getExpires(); long timeout = expires == 0 ? sessionTimeout * 1000 : expires - created;//如果没有设置过期时间,则使用默认的 if (current - created < timeout) { //如果没有过期 LoginContext.setLoginContext(context); if ((current - created) * rate > timeout) {//如果剩下的时间只有2/3,就需要重新派发cookie log.error("session cookie[" + loginCookieKey + "] rewrite!"); //写最后一次访问的cookie context.setCreated(current); if (expires != 0) { context.setTimeout(timeout); } loginCookieUtil.setCookie(response, loginCookieKey, context.toCookieValue()); } //如果没过期,则继续调用 return invocation.invoke(); } else { log.error("session cookie[" + loginCookieKey + "] is valid!"); //超时后,要清空 ,返回客户端 loginCookieUtil.invalidate(request, response); } } |
跟踪客户状态的方法:
1. 建立含有跟踪数据的隐藏字段,即hidden域
2. 重写包含额外参数的URL
3. 使用持续的Cookie
4. 使用Session
区别:
1. Cookie是保存在客户端的,session是保存在服务端的。
2. Cookie不是很安全,可以分析本地的cookie进行cookie欺骗。
3. Session保存在服务器上,如果过期时间较长,会占用服务器资源。考虑减轻服务器性能,可以考虑Cookie。
4. 用Session的话,需要考虑session共享问题,如分布式缓存,单独的session服务器等。