HTTP 是无状态协议,所以服务端如果需要记住登录用户,就需要维护一个 SessionId(Cookie) - Session 的键值对。Session 存放用户信息对象。用户信息对象作为 Session 的一个 Attribute。当浏览器请求中包含 Cookie 时,服务器就能识别出具体是哪个用户了。
默认 SessionId 与 Session 的键值对由服务器来维护,Session 的过期时间默认为 30 分钟(可通过 Debug 查看 maxInactiveInterval 的值)。
使用 HttpSession
下面是一个简单的使用 Session 来保存用户登录状态的例子,相关代码我放到了 GitHub 上
设置 Attribute(登录时)
"/signin") ( |
获取 Attribute(判断是否已经登录)
"/profile") ( |
删除 Attribute(退出时)
"/signout") ( |
这里的 HttpSession session
可以用 HTTPServletRequest request
代替,此时使用 request.getSession().getAttribute()
。HttpSession session
和 HTTPServletRequest request
可以认为是方法默认就包含的参数。
Session 的生命周期是半小时,如果半小时后访问时,服务器将重新建立连接,将发送新的 SessionId 到浏览器,再次访问时, 新 Session 中将没有 User,此时登录将失效。
浏览器 Cookie 样式:
Cookie: JSESSIONID=C8698B74AFAD403C6E28D77B75373500 |
此部分代码对应 v1
使用 Redis
当存在跨域问题时,即多个服务都需要用到 Session 判断登录状态时,就需要将 Session 在每个服务中复制一份,或做成分布式 Session。一般使用 Redis 实现。
下面使用 Redis 来维护这个 SessionId - Session 的键值对,或者说维护一个 SessionId - Attributes 的键值对。
public class BaseController { |
自定义 RedisUtils,使用静态方法
4j |
UserController
public class UserController extends BaseController { |
此部分代码对应 v2
自定义 Session
上面这种方式实现了一个简单的分布式 Session,我们可以自定义 Session 来对其进行一定优化,使其具有以下特点:
- 封装 Attribute 的设置与获取的实现细节
- 可以自定义 Cookie
- Attributes 做一个二级缓存,自定义 Session 中存放一份,Redis 再存放一份。
需要利用下面这几个原生类:
HttpSession |
设计
1、设置自定义 Session、Request 和 Response
public class WrapperSession implements HttpSession { |
public class WrapperSessionServletRequest extends HttpServletRequestWrapper { |
public class WrapperSessionServletResponse extends HttpServletResponseWrapper { |
2、使用 session-config.xml 配置 cookie 和 cache,一个 entry 对应一个 SessionConfigEntry。
|
public class SessionConfigEntry { |
3、使用 CookieStore 存放 Cookie。使用 CacheStore 存放 attributes,获取 attribute 时默认直接从 CacheStore 中取(CacheStore 从 Redis 缓存中读取)。
public class CacheStore implements SessionStore, SessionCacheContainerAware { |
public class CookieStore implements SessionStore { |
链路调用
1、项目启动时根据 session-config.xml 中初始化 SessionConfigEntry
public class WrapperSessionFilter implements Filter { |
2、请求时,拦截,查找 SessionId 在 Redis 是否有对应的 Attributes,设置时先设置到 SessionStore
public class CacheStore implements SessionStore, SessionCacheContainerAware { |
3、返回前端前,将 Attributes 更新到 Redis
public class WrapperSessionServletResponse extends HttpServletResponseWrapper { |
4、获取时,直接从 SessionStore 中获取,默认将从 Redis 中读取一次,读取后将不再读取,因为以后都就将写入 Attributes
... |
使用
UserController
public class UserController extends BaseController { |
BaseController
public class BaseController { |
此部分代码对应 v3。
结语
自定义分布式 Session 一般实现在网关中,网关接口对外暴露,请求先调用网关,网关请求只能内网访问的业务系统接口。网关和业务系统规定相应的调用规则(如:添加指定 Header),网关来负责验证登录状态。
Redis 可以实现集群保证可用性。当不使用分布式 Session 时,可以使用 JSON Web Token