第三章 Session共享&单点登陆笔记

一、传统Session机制及身份认证方案

 

1Cookie与服务器的交互

如上图,http是无状态的协议,客户每次读取web页面时,服务器都打开新的会话,而且服务器也不会自动维护客户的上下文信息。比如我们现在要实现一个电商内的购物车功能,要怎么才能知道哪些购物车请求对应的是来自同一个客户的请求呢?session就是一种保存上下文信息的机制,它是针对每一个用户的,变量的值保存在服务器端,通过SessionID来区分不同的客户。session是以cookie或URL重写为基础的,默认使用cookie来实现,系统会创造一个名为JSESSIONID的值输出到cookie。

注意JSESSIONID是存储于浏览器内存中的,并不是写到硬盘上的,如果我们把浏览器的cookie禁止,则web服务器会采用URL重写的方式传递Sessionid,我们就可以在地址栏看到 sessionid=KWJHUG6JJM65之类的字符串。 通常JSESSIONID是不能跨窗口使用的,当你新开了一个浏览器窗口进入相同页面时,系统会赋予你一个新的sessionid,这样我们信息共享的目的就达不到了。

2、服务器端的session的机制

session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。但程序需要为某个客户端的请求创建一个session的时候,服务器首先检查这个客户端的请求里是否包含了一个JSESSIONID标识的sessionid,如果已经包含一个session id则说明以前已经为此客户创建过session,服务器就根据sessionid把这个session检索出来使用(如果检索不到,可能会新建一个,这种情况可能出现在服务端已经删除了该用户对应的session对象,但用户人为地在请求的URL后面附加上一个JSESSION的参数)。

如果客户请求不包含 session id,则为此客户创建一个session并且生成一个与此session相关联的session id,这个session id将在本次响应中返回给客户端保存。对每次http请求,都经历以下步骤处理:服务端首先查找对应的cookie的值(sessionid)。 根据sessionid从服务器端session存储中获取对应id的 session数据,进行返回。 如果找不到sessionid,服务器端就创建session,生成sessionid对应的cookie,写入到响应头中。

3、基于session的身份认证

看下图:

 

因为http请求是无状态请求,所以在Web领域,几乎所有的身份认证过程,都是这种模式。

二、集群下Session困境及解决方案

 

如上图,随着分布式架构的流行,单个服务器已经不能满足系统的需要了,通常都会把系统部署在多台服务器上,通过负载均衡把请求分发到其中的一台服务器上,这样很可能同一个用户的请求被分发到不同的服务器上,因为session是保存在服务器上的,那么很有可能第一次请求访问的A服务器,创建了session,但是第二次访问到了B服务器,这时就会出现取不到session的情况。我们知道,Session一般是用来存会话全局的用户信息(不仅仅是登陆方面的问题),用来简化/加速后续的业务请求。要在集群环境下使用,最好的的解决办法就是使用session共享:

 

1Session共享方案

传统的session由服务器端生成并存储,当应用进行分布式集群部署的时候,如何保证不同服务器上session信息能够共享呢?两种实现思路:session集中存储(redis,memcached,hbase等)。不同服务器上session数据进行复制,此方案延迟问题比较严重。 我们一般推荐第一种方案,基于session集中存储的实现方案,见下图:

 

具体过程如下:

新增Filter拦截请求,包装HttpServletRequest(使用HttpServletRequestWrapper) 改写getSession方法,从第三方存储中获取session数据(若没有则创建一个),返回自定义的HttpSession实例在http返回response时,提交session信息到第三方存储中。

2、需要考虑的问题

2.1、需要考虑以下问题:

session数据如何在Redis中存储?

session属性变更何时触发存储?

2.2、实现:

考虑到session中数据类似map的结构,采用redis中hash存储session数据比较合适如果使用单个value存储session数据,不加锁的情况下,就会存在session覆盖的问题,因此使用hash存储session,每次只保存本次变更session属性的数据,避免了锁处理,性能更好。如果每改一个session的属性就触发存储,在变更较多session属性时会触发多次redis写操作, 对性能也会有影响,我们是在每次请求处理完后,做一次session的写入,并且写入变更过的属性。如果本次没有做session的更改,是不会做redis写入的,仅当没有变更的session超过一个时间阀值(不变更session刷新过期时间的阀值),就会触发session保存,以便session能够延长有效期。

3、代码实战

3.1、新建项目springboot-session-bussiness

主要实现正常的登录拦截功能,为后面项目改造提供参考。

代码参见:springboot-platform------>springboot-session-bussiness模块

代码git地址:https://gitee.com/hankin_chj/springboot-platform.git

3.2、新建springboot-session-bussiness模块

该模块为公共模块,主要用于为其他服务提供统一的session拦截、分装等功能,主要对系统的request和session对象进行重写,其他服务需要用到request的时候就使用封装好的MyRequestWrapper来获取session相关内容。

1)重写HttpSession

public class MySession implements Serializable,HttpSession {
    private static final long serialVersionUID = -3923541488767125713L;
    private String id;
    private Map<String,Object> attrs;
    .....

2)request封装代码实现如下:

public class MyRequestWrapper extends HttpServletRequestWrapper {
    private volatile boolean committed = false;
    private String uuid = UUID.randomUUID().toString();
    private MySession session;
    private RedisTemplate redisTemplate;
    public MyRequestWrapper(HttpServletRequest request,RedisTemplate redisTemplate) {
        super(request);
        this.redisTemplate = redisTemplate;
    }
    // 提交session内值到redis
    public void commitSession() {
        if (committed) {
            return;
        }
        committed = true;
        MySession session = this.getSession();
        if (session != null && null != session.getAttrs()) {
            redisTemplate.opsForHash().putAll(session.getId(),session.getAttrs());
        }
    }
    //创建新session
    public MySession createSession() {
        String sessionId = CookieBasedSession.getRequestedSessionId(this);//从页面传来的
        Map<String,Object> attr ;
        if (null != sessionId){
            attr = redisTemplate.opsForHash().entries(sessionId);
        } else {
            System.out.println("create session by rId:"+uuid);
            sessionId = UUID.randomUUID().toString();
            attr = new HashMap<>();
        }
        //session成员变量持有
        session = new MySession();
        session.setId(sessionId);
        session.setAttrs(attr);
        return session;
    }
    //或取session
    public MySession getSession() {
        return this.getSession(true);
    }
    // 取session
    public MySession getSession(boolean create) {
        if (null != session){
            return session;
        }
        return this.createSession();
    }
    //是否已登陆
    public boolean isLogin(){
        Object user = getSession().getAttribute(SessionFilter.USER_INFO);
        return null != user;
    }

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值