session 跟 cookie关系 ,session共享,单点登录实现原理及实战

首先弄懂session 跟 cookie 关系

在这里插入图片描述

客户第一次发送请求给服务器,此时服务器产生一个唯一的sessionID,并返回给客户端(通过cookie),此时的cookie并没有setMaxAge();只是保存于客户端的内存中,并与一个浏览器窗口对应着,由于HTTP协议的特性,这一次连接就断开了
以后此客户端再发送请求给服务器的时候,就会在请求request中携带cookie,由于cookie中有sessionID,所以服务器就知道这是刚才那个客户,从而区分不同的人,购物车就是这样实现的

其次搞懂session共享

Session使用的场景?

HttpSession是通过Servlet容器进行创建和管理的,在单机环境中。通过Http请求创建的Session信息是存储在Web服务器内存中,如Tomcat/Jetty。
假如当用户通过浏览器访问应用服务器,session信息中保存了用户的登录信息,并且session信息没有过期失,效那么用户就一直处于登录状态,可以做一些登录状态的业务操作!

在这里插入图片描述
但是现在很多的服务器都采用分布式集群的方式进行部署,一个Web应用,可能部署在几台不同的服务器上,通过LVS或者Nginx等进行负载均衡(一般使用Nginx+Tomcat实现负载均衡)。此时来自同一用户的Http请求将有可能被分发到不同的web站点中去(如:第一次分配到A站点,第二次可能分配到B站点)。那么问题就来了,如何保证不同的web站点能够共享同一份session数据呢?
假如用户在发起第一次请求时候访问了A站点,并在A站点的session中保存了登录信息,当用户第二次发起请求,通过负载均衡请求分配到B站点了,那么此时B站点能否获取用户保存的登录的信息呢?答案是不能的,因为上面说明,Session是存储在对应Web服务器的内存的,不能进行共享,此时Spring-session就出现了,来帮我们解决这个session共享的问题!

在这里插入图片描述

如何进行Session共享呢?

简单点说就是请求http请求经过Filter职责链,根据配置信息过滤器将创建session的权利由tomcat交给了Spring-session中的SessionRepository,通过Spring-session创建会话,并保存到对应的地方。
在这里插入图片描述
实际上实现Session共享的方案很多,其中一种常用的就是使用Tomcat、Jetty等服务器提供的Session共享功能,将Session的内容统一存储在一个数据库(如MySQL)或缓存(如Redis,Mongo)中,
而上面说的使用Nginx也可以,使用ip_hash策略。
在使用Nginx的ip_hash策略时候,每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,也可以解决session的问题。

而springboot会话提供了与HttpSession的透明集成,允许以应用程序容器(即Tomcat)中性的方式替换HttpSession,但是我们从中得到了什么好处呢?
集群会话——Spring会话使支持集群会话变得微不足道,而不需要绑定到应用程序容器的特定解决方案,不依赖容器是更高层的封装。
多个浏览器会话——Spring会话支持在单个浏览器实例中管理多个用户会话(也就是多个经过验证的帐户,类似于谷歌)。
RESTful api——Spring会话允许在header中提供会话id以使用RESTful api。

如何使用springboot的session共享

这里不说了,参考我之前的文章,springboot怎么实现session共享

说明

单点登录(Single sign on),英文名称缩写SSO,SSO的意思就是在多系统的环境中,登录单方系统,就可以在不用再次登录的情况下访问相关受信任的系统。也就是说只要登录一次单体系统就可以。

登录

在这里插入图片描述

下面对上图简要描述

1.’用户访问系统1的受保护资源,系统1发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数
2.sso认证中心发现用户未登录,将用户引导至登录页面
用户输入用户名密码提交登录申请
3.sso认证中心校验用户信息,创建用户与sso认证中心之间的会话,称为全局会话,同时创建授权令牌
4.sso认证中心带着令牌跳转会最初的请求地址(系统1)
5.系统1拿到令牌,去sso认证中心校验令牌是否有效
6.sso认证中心校验令牌,返回有效,注册系统1
7.系统1使用该令牌创建与用户的会话,称为局部会话,返回受保护资源
8.用户访问系统2的受保护资源
9.系统2发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数
10.sso认证中心发现用户已登录,跳转回系统2的地址,并附上令牌
11.系统2拿到令牌,去sso认证中心校验令牌是否有效
12.sso认证中心校验令牌,返回有效,注册系统2
13.系统2使用该令牌创建与用户的局部会话,返回受保护资源
  用户登录成功之后,会与sso认证中心及各个子系统建立会话,用户与sso认证中心建立的会话称为全局会话,用户与各个子系统建立的会话称为局部会话,局部会话建立之后,用户访问子系统受保护资源将不再通过sso认证中心,全局会话与局部会话有如下约束关系

局部会话存在,全局会话一定存在
全局会话存在,局部会话不一定存在
全局会话销毁,局部会话必须销毁
  你可以通过博客园、百度、csdn、淘宝等网站的登录过程加深对单点登录的理解,注意观察登录过程中的跳转url与参数

退出

在这里插入图片描述

sso认证中心一直监听全局会话的状态,一旦全局会话销毁,监听器将通知所有注册系统执行注销操作
  下面对上图简要说明
用户向系统1发起注销请求
系统1根据用户与系统1建立的会话id拿到令牌,向sso认证中心发起注销请求
sso认证中心校验令牌有效,销毁全局会话,同时取出所有用此令牌注册的系统地址
sso认证中心向所有注册系统发起注销请求
各注册系统接收sso认证中心的注销请求,销毁局部会话
sso认证中心引导用户至登录页面

部署

在这里插入图片描述

这里是引用
sso认证中心与sso客户端通信方式有多种,httpClient ,web service、rpc、restful api都可以但我不打算这么做,token校验这步操作我不要了,我做了session共享所以打算不校验了

实战(基于springboot的session共享实现)

服务端核心代码

/**
 * @auther 高松
 * @DATE 2019/8/22  21:51
 * sso-client1
 */
public class ServerSsoFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        HttpServletResponse res = (HttpServletResponse) servletResponse;
        Cookie[] cookies = req.getCookies();
        String token = "";
        if(cookies!=null) for (Cookie cookie : cookies) {
            switch(cookie.getName()){
                case "tssoToken":
                    token = cookie.getValue();
                    break;
                default:
                    break;
            }
        }
        String tssotoken = (String )req.getSession().getAttribute("tssoToken");
        String returnUrl = req.getParameter("returnUrl");
        //session和cookie中华值都对应直接跳到目标页面
        if(returnUrl!=null&&!"".equals(returnUrl)&&tssotoken!=null&&tssotoken.equals(token)&&!"".equals(tssotoken)){
            res.sendRedirect(returnUrl);
        }
        //登录操作不拦截
        if(req.getServletPath().contains("login")){
            filterChain.doFilter(req, res);
            return;
        }
        //校验token
        if (tssotoken!=null&&tssotoken.equals(token)&&!"".equals(tssotoken)){
            filterChain.doFilter(req, res);
            return;
        }
        //校验token不通过,跳转至sso认证中心
        res.sendRedirect("http://localhost:8020/loginParent?returnUrl=http://localhost:8020/loginParentoo");
    }
}
####请求类
@Controller
public class loginViewController {
    @RequestMapping("loginParent")
    public String loginp(HttpSession session,HttpServletRequest httpServletRequest){
        System.out.println("loginServer-sessionid:"+httpServletRequest.getHeader("Cookie"));

        return "loginParent";
    }

    /**
     * 登录成功后,设置token给客户端
     * @param httpServletRequest
     * @param httpServletResponse
     * @return
     */
    @RequestMapping("loginServer")
    public String login(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse){
        Cookie[] cookies = httpServletRequest.getCookies();
        String token = "";
        if(cookies!=null) for (Cookie cookie : cookies) {
            switch(cookie.getName()){
                case "SESSION":
                    token = cookie.getValue();
                    break;
                default:
                    break;
            }
        }
       String id = token;
        String returnUrl = httpServletRequest.getParameter("returnUrl");
        System.out.println("server-hello-cookie:"+httpServletRequest.getHeader("Cookie"));
         httpServletResponse.addCookie(new Cookie("tssoToken",id));
         httpServletRequest.getSession().setAttribute("tssoToken",id);
        return  "redirect:"+returnUrl+"?tssoToken="+id;
    }
}

客户端核心代码
public class ServerSsoFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        HttpServletResponse res = (HttpServletResponse) servletResponse;
        String tssotoken = (String) req.getSession().getAttribute("tssoToken");
        if(req.getServletPath().contains("login")){
            filterChain.doFilter(req, res);
            return;
        }
        Cookie[] cookies = req.getCookies();
        String token = "";
       if(cookies!=null) for (Cookie cookie : cookies) {
            switch(cookie.getName()){
                case "tssoToken":
                    token = cookie.getValue();
                    break;
                default:
                    break;
            }
        }
        if (tssotoken!=null&&tssotoken.equals(token)&&!"".equals(tssotoken)){
            filterChain.doFilter(req, res);
            return;
        }
        req.getSession().setAttribute("name","gsong");
        //跳转至sso认证中心
      res.sendRedirect("http://localhost:8020/loginParent?returnUrl=http://localhost:8888/hello");
    }
}
/**
 * @auther 高松
 * @DATE 2019/8/22  21:51
 * sso-client1
 */
@Controller
public class ClientViewController {
    /**
     * 主页请求
     * @param session
     * @param httpServletRequest
     * @return
     */
    @RequestMapping(value = "hello")
    public String hello(HttpSession session,HttpServletRequest httpServletRequest){
        System.out.println("名字"+httpServletRequest.getSession().getAttribute("name"));
        System.out.println("client-hello-sessionid:"+session.getId());
        System.out.println("wewe");
        return "my";
    }

    /**
     * 退出操作
     * @param session
     * @param httpServletRequest
     * @return
     */
    @RequestMapping(value = "logout")
    public String logout(HttpSession session,HttpServletRequest httpServletRequest){
        session.setMaxInactiveInterval(0);
        System.out.println("退出登录移除session");
        return "redirect:http://localhost:8020/loginParent?returnUrl=http://localhost:8888/hello";
    }

}

源码下载

链接:https://pan.baidu.com/s/1JPqIzt-jPMiQnSkaTTO0CA
提取码:d78y

代码使用,启动一个服务端,多个客户端,配置redis服务地址即可

参考:参考1

参考:参考2

  • 5
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用SpringBoot框架结合MyBatisSession共享单点登录可以借助SpringSession和Redis来现。 首先,需要配置SpringSession以使用Redis作为存储方式。可以在SpringBoot的配置文件中添加以下配置: ``` spring.session.store-type=redis spring.session.redis.namespace=spring:session spring.redis.host=127.0.0.1 spring.redis.port=6379 ``` 这样配置后,SpringSession会自动将session信息存储到Redis中。 接着,在登录验证成功后,将用户信息存储到Redis中,并将该用户的唯一标识存储到当前Session的属性中,以便后续验证是否登录。例如: ``` @RequestMapping("/login") public String login(@RequestParam("username") String username, @RequestParam("password") String password, HttpSession session) { // 验证用户名和密码 // ... // 验证通过后,将用户信息存储到Redis中,并设置Session属性 redisTemplate.opsForHash().put("user:" + username, "username", username); session.setAttribute("username", username); return "success"; } ``` 在后续的请求中,可以通过拦截器或过滤器来验证Session是否有效。例如: ``` @Component public class SessionInterceptor implements HandlerInterceptor { @Autowired private RedisTemplate<String, Object> redisTemplate; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); String username = (String) session.getAttribute("username"); if (StringUtils.isEmpty(username)) { response.sendRedirect("/login"); return false; } String storedUsername = (String) redisTemplate.opsForHash().get("user:" + username, "username"); if (!StringUtils.equals(storedUsername, username)) { response.sendRedirect("/login"); return false; } return true; } } ``` 以上代码片段展示了如何通过拦截器验证Session的有效性。首先从当前Session中获取用户名,如果为空则重定向到登录页面。然后从Redis中获取存储的用户名,如果与当前用户名不匹配,则重定向到登录页面。 这样就现了SpringBoot、MyBatis、SpringSession和Redis共同完成Session共享单点登录的功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值