分享

Javaweb重要知识点总结(三)Cookie 和 Session

 zhulin1028 2022-03-31

1. Cookie 和Session 的区别

Cookie 是 web 服务器发送给浏览器的一块信息,浏览器会在本地一个文件中给每个 web 服务器存储 cookie。以后浏览器再给特定的 web 服务器发送请求时,同时会发送所有为该服务器存储的 cookie。

Session 是存储在 web 服务器端的一块信息。session 对象存储特定用户会话所需的属性及配置信息。当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。

 Cookie和session的 不 同 点 :

1、无论客户端做怎样的设置,session 都能够正常工作。当客户端禁用 cookie 时将无法使用 cookie。

2、在存储的数据量方面:session 能够存储任意的 java 对象,cookie 只能存储 String 类型的对象。

2. session 共享怎么做的(分布式如何实现 session 共享)?

问题描述:一个用户在登录成功以后会把用户信息存储在 session 当中,这时 session 所在服务器为 server1,那么用户在 session 失效之前如果再次使用 app,那么可能会被路由到 server2,这时问题来了,server 没有该用户的session,所以需要用户重新登录,这时的用户体验会非常不好,所以我们想如何实现多台 server 之间共享 session, 让用户状态得以保存。

1、服务器实现的 session 复制或 session 共享,这种类型的共享 session 是和服务器紧密相关的,比如 webSphere 或 JBOSS 在搭建集群时候可以配置实现 session 复制或 session 共享,但是这种方式有一个致命的缺点,就是不好扩展和移植,比如我们更换服务器,那么就要修改服务器配置。

2、利用成熟的技术做session 复制,比如12306 使用的gemfire,比如常见的内存数据库如redis 或memorycache, 这类方案虽然比较普适,但是严重依赖于第三方,这样当第三方服务器出现问题的时候,那么将是应用的灾难。

3、将 session 维护在客户端,很容易想到就是利用 cookie,但是客户端存在风险,数据不安全,而且可以存放的数据量比较小,所以将 session 维护在客户端还要对 session 中的信息加密。

我们实现的方案可以说是第二种方案和第三种方案的合体,可以利用 gemfire 实现 session 复制共享,还可以将session 维护在 redis 中实现 session 共享,同时可以将 session 维护在客户端的 cookie 中,但是前提是数据要加密。这三种方式可以迅速切换,而不影响应用正常执行。我们在实践中,首选 gemfire 或者 redis 作为 session 共享的载体, 一旦 session 不稳定出现问题的时候,可以紧急切换 cookie 维护session 作为备用,不影响应用提供服务。

这里主要讲解 redis 和 cookie 方案,gemfire 比较复杂大家可以自行查看 gemfire 工作原理。利用 redis 做

session 共享,首先需要与业务逻辑代码解耦,不然 session 共享将没有意义,其次支持动态切换到客户端 cookie 模式。redis 的方案是,重写服务器中的 HttpSession 和 HttpServletRequest,首先实现HttpSession 接口,重写session 的所有方法,将 session 以 hash 值的方式存在 redis 中,一个 session 的 key 就是 sessionID,setAtrribute 重写之后就是更新 redis 中的数据,getAttribute 重写之后就是获取 redis 中的数据,等等需要将 HttpSession 的接口一一实现。

实现了 HttpSesson,那么我们先将该 session 类叫做 MySession(当然实践中不是这么命名的),当 MySession 出现之后问题才开始,怎么能在不影响业务逻辑代码的情况下,还能让原本的 request.getSession()获取到的是

MySession,而不是服务器原生的session。这里,我决定重写服务器的HttpServletRequet,这里先称为MyRequest, 但是这可不是单纯的重写,我需要在原生的 request 基础上重写,于是我决定在 filter 中,实现 request 的偷梁换柱, 我的思路是这样的,MyRequest 的构建器,必须以 request 作为参数,于是我在 filter 中将服务器原生的 request(也有可能是框架封装过的 request ) , 当做参数 new 出来一个 MyRequest , 并且 MyRequest 也实现了HttpServletRequest 接口,其实就是对原生 request 的一个增强,这里主要重写了几个 request 的方法,但是最重要的是重写了 request.getSession(),写到这里大家应该都明白为什么重写这个方法了吧,当然是为了获取 MySession,于是这样就在filter 中,偷偷的将原生的request 换成MyRequest 了,然后再将替换过的request 传入chan.doFilter(), 这样

filter 时候的代码都使用的是 MyRequest 了,同时对业务代码是透明的,业务代码获取 session 的方法仍然是request.getSession(),但其实获取到的已经是 MySession 了,这样对 session 的操作已经变成了对 redis 的操作。这样实现的好处有两个,第一开发人员不需要对 session 共享做任何关注,session 共享对用户是透明的;第二,filter 是可配置的,通过 filter 的方式可以将 session 共享做成一项可插拔的功能,没有任何侵入性。

这个时候已经实现了一套可插拔的 session 共享的框架了,但是我们想到如果 redis 服务出了问题,这时我们该怎么办呢,于是我们延续 redis 的想法,想到可以将 session 维护在客户端内(加密的 cookie),当然实现方法还是一样的,我们重写 HttpSession 接口,实现其所有方法,比如 setAttribute 就是写入 cookie,getAttribute 就是读取cookie,我们可以将重写的 session 称作 MySession2,这时怎么让开发人员透明的获取到 MySession2 呢,实现方法还是在 filter 内偷梁换柱,在 MyRequest 加一个判断,读取 sessionType 配置,如果 sessionType 是 redis 的,那么getSession 的时候获取到的是 MySession,如果 sessionType 是cookie 的,那么 getSession 的时候获取到的是MySession2,以此类推,用同样的方法就可以获取到 MySession 3,4,5,6 等等。

这样两种方式都有了,那么我们怎实现两种 session 共享方式的快速切换呢,刚刚我提到一个 sessionType,这是用来决定获取到session 的类型的,只要变换sessionType 就能实现两种session 共享方式的切换,但是sessionType 必须对所有的服务器都是一致的,如果不一致那将会出现比较严重的问题,我们目前是将 sessionType 维护在环境变量里,如果要切换 sessionType 就要重启每一台服务器,完成 session 共享的转换,但是当服务器太多的时候将是一种灾难。而且重启服务意味着服务的中断,所以这样的方式只适合服务器规模比较小,而且用户量比较少的情况,当服务器太多的时候,务必需要一种协调技术,能够让服务器能够及时获取切换的通知。基于这样的原因,我们选用

zookeeper 作为配置平台,每一台服务器都会订阅 zookeeper 上的配置,当我们切换 sessionType 之后,所有服务器都会订阅到修改之后的配置,那么切换就会立即生效,当然可能会有短暂的时间延迟,但这是可以接受的。

3. 在单点登录中,如果 cookie 被禁用了怎么办?

单点登录的原理是后端生成一个 session ID,然后设置到 cookie,后面的所有请求浏览器都会带上 cookie, 然后服务端从 cookie 里获取 session ID,再查询到用户信息。所以,保持登录的关键不是 cookie,而是通过

cookie 保存和传输的 session ID,其本质是能获取用户信息的数据。除了 cookie,还通常使用 HTTP 请求头来传输。但是这个请求头浏览器不会像 cookie 一样自动携带,需要手工处理。

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多