客户第一次发送请求给服务器,此时服务器产生一个唯一的sessionID,并返回给客户端(通过cookie),此时的cookie并没有setMaxAge();只是保存于客户端的内存中,并与一个浏览器窗口对应着,由于HTTP协议的特性,这一次连接就断开了
以后此客户端再发送请求给服务器的时候,就会在请求request中携带cookie,由于cookie中有sessionID,所以服务器就知道这是刚才那个客户,从而区分不同的人,购物车就是这样实现的
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共享的问题!
简单点说就是请求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共享
单点登录(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共享所以打算不校验了
/**
* @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
参考:参考1
参考:参考2