session共享和单点登录

登陆测试

  • sso-session
    • login接口
    • LoginFilter过滤
      • 不拦截/toLogin和/login,其余的路径都要拦截
      • 通过LoginConfig类将一个LoginFilter配置到FilterRegistrationBean上
    • 显示cookie插件,editthiscookie

分布式环境下的难点

  • 1.用户登陆了tomcat1,不要重复去登陆tomcat2
  • 2.用户登陆tomcat1生成的用户信息,tomcat2如何知道–校验权限
    • 用一个单独的服务去保存session信息,可以通过redis
  • 3.用户在tomcat1注销后,tomcat3要及时得到注销

用redis保存session

  • 1.第一次request.getSession时,去redis内取值
  • 2.response返回数据时,推送session到redis内
  • 3.成熟的框架,spring-session

怎么修改request和response

  • SessionFilter-MyRequestWrapper

    • session_support项目,重置servlet中的request和response,LoginFilter.doFilter方法里面通过filterChanin.doFilter(myRequestWrapper,serviceResponse)

    • package com.enjoy.session;
      
      import com.enjoy.utils.CookieBasedSession;
      import org.springframework.data.redis.core.RedisTemplate;
      
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletRequestWrapper;
      import java.util.HashMap;
      import java.util.Map;
      import java.util.UUID;
      
      /**
       * Created by Peter on 2018/8/15.
       * 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
           * @return
           */
          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
           * @return
           */
          public MySession getSession() {
              return this.getSession(true);
          }
      
          /**
           * 取session
           * @return
           */
          public MySession getSession(boolean create) {
              if (null != session){
                  return session;
              }
              return this.createSession();
          }
      
          /**
           * 是否已登陆
           * @return
           */
          public boolean isLogin(){
              Object user = getSession().getAttribute(SessionFilter.USER_INFO);
              return null != user;
          }
      
      }
      
      
    • redis中用hash存的

    • 自己也要自定义一个MySession,其中有一个attrs的字段,是一个Map<String,Object>

测试session共享

  • 启动sessionA和sessionB

    • 在pom文件中引入

      <dependency>
          <artifactId>session_support</artifactId>
          <groupId>com.enjoy</groupId>
          <version>1.0</version>
      </dependency>
      
    • 新增一个配置类

      package com.enjoy.config;
      
      import com.enjoy.session.SessionFilter;
      import org.springframework.boot.web.servlet.FilterRegistrationBean;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.data.redis.core.RedisTemplate;
      
      /**
       * Created by Peter on 2018/8/15.
       */
      @Configuration
      public class SessionConfig {
      
          //配置filter生效
          @Bean
          public FilterRegistrationBean sessionFilterRegistration(SessionFilter sessionFilter) {
      
              FilterRegistrationBean registration = new FilterRegistrationBean();
              registration.setFilter(sessionFilter);
              registration.addUrlPatterns("/*");
              registration.addInitParameter("paramName", "paramValue");
              registration.setName("sessionFilter");
              registration.setOrder(1);
              return registration;
          }
      
          //定义过滤器组件
          @Bean
          public SessionFilter sessionFilter(RedisTemplate redisTemplate){
              SessionFilter sessionFilter = new SessionFilter();
              sessionFilter.setRedisTemplate(redisTemplate);
              return sessionFilter;
          }
      
      }
      
      
    • 针对不是通过网关路由进来的,而是每个子服务都有自己域名的需要保证有共有的父域名,此时两个页面共享一个session,关键是需要共享一个sessionId,sessionId放在顶级域名下,sessionA和sessionB的二级域名请求时会自动带上父域名cookie里的sessionId信息

      • 同理对于APP而言,tokenId也跟这个sessionId可以做一样的处理
    • 把cookie设置到顶级域名

      • package com.enjoy.utils;
        
        import javax.servlet.http.Cookie;
        import javax.servlet.http.HttpServletRequest;
        import javax.servlet.http.HttpServletResponse;
        import javax.servlet.http.HttpSession;
        
        public class CookieBasedSession{
        
            public static final String COOKIE_NAME_SESSION = "psession";
        
            public static String getRequestedSessionId(HttpServletRequest request) {
                Cookie[] cookies = request.getCookies();
                if (cookies == null) {
                    return null;
                }
                for (Cookie cookie : cookies) {
                    if (cookie == null) {
                        continue;
                    }
        
                    if (!COOKIE_NAME_SESSION.equalsIgnoreCase(cookie.getName())) {
                        continue;
                    }
        
                    return cookie.getValue();
                }
                return null;
            }
        
            public static void onNewSession(HttpServletRequest request,
                                     HttpServletResponse response) {
                HttpSession session = request.getSession();
                String sessionId = session.getId();
                Cookie cookie = new Cookie(COOKIE_NAME_SESSION, sessionId);
                cookie.setHttpOnly(true);
                cookie.setPath(request.getContextPath() + "/");
                cookie.setDomain("dev.com");
                cookie.setMaxAge(Integer.MAX_VALUE);
                response.addCookie(cookie);
            }
        
        }
        
        
      • 一级域名dev.com和二级域名a.dev.com

        • 但是跨域了解决不了了

单点登录–不使用session共享

  • 通过页面重定向,把前端身份信息id进行互传
    • 1.A已经在a.com页面正常访问(已登录状态)
    • 2.A访问b.com页面,发现未登录,于是跳转login.cas.com去登陆,此时,在cas.com域名下有cookie
    • 3.login.cas.com服务经过校验,发现此cookie是有效的,直接返回success.cas.com页面和ticket
    • 4.success.cas.com页面发生302跳转,转回b.com页面并携带ticket值
    • 5.b.com服务器探测到此请求未登录,于是后端发ticket到cas.com校验,校验通过,则b.com种cookie到b.com域名下

测试sso-cas项目

  • cas-login

    • loginFilter不变

    • 在/login接口方法下生成一个ticket,保存下来,不仅是redis,然后重定向回原页面,并带上ticket值

      • @PostMapping("/login")
        public void login(@ModelAttribute UserForm user,HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException {
            System.out.println("backurl:"+user.getBackurl());
            request.getSession().setAttribute(LoginFilter.USER_INFO,user);
        
            //登陆成功,创建用户信息票据
            String ticket = UUID.randomUUID().toString();
            redisTemplate.opsForValue().set(ticket,user,20, TimeUnit.SECONDS);
            //重定向,回原url  ---a.com
            if (null == user.getBackurl() || user.getBackurl().length()==0){
                response.sendRedirect("/index");
            } else {
                response.sendRedirect(user.getBackurl()+"?ticket="+ticket);
            }
        }
        
  • 登录过程

    • 1.a.com登录,发现是未登录
    • 2.会跳转到cas.com,自己输入信息登录成功,此网站后台有用户信息(ticket=userInfo)
    • 3.跳转回a.com?ticket=xxx
    • 4.b.com进页面,发现是未登录
    • 5.跳转cas.com(本来就是登录的),此网站后台有用户信息(ticket-userInfo),不用用户登录填信息
    • 6.跳转回b.com?ticket=xxx
  • cas-website

    • SSOFilter需要增加逻辑,通过ticket去拿userInfo

      • 看url上面是有ticket,如果有,从之前存储的地方取出用户信息,然后就不用登录了,并把用户信息set到session里面

      • package com.enjoy.utils;
        
        import org.springframework.data.redis.core.RedisTemplate;
        
        import javax.servlet.*;
        import javax.servlet.http.HttpServletRequest;
        import javax.servlet.http.HttpServletResponse;
        import java.io.IOException;
        
        public class SSOFilter implements Filter {
            private RedisTemplate redisTemplate;
        
            public static final String USER_INFO = "user";
        
            public SSOFilter(RedisTemplate redisTemplate){
                this.redisTemplate = redisTemplate;
            }
            @Override
            public void init(FilterConfig filterConfig) throws ServletException {
        
            }
        
            @Override
            public void doFilter(ServletRequest servletRequest,
                                 ServletResponse servletResponse, FilterChain filterChain)
                throws IOException, ServletException {
        
                HttpServletRequest request = (HttpServletRequest) servletRequest;
                HttpServletResponse response = (HttpServletResponse)servletResponse;
        
                Object userInfo = request.getSession().getAttribute(USER_INFO);;
        
                //如果未登陆,则拒绝请求,转向登陆页面
                String requestUrl = request.getServletPath();
                if (!"/toLogin".equals(requestUrl)//不是登陆页面
                    && !requestUrl.startsWith("/login")//不是去登陆
                    && null == userInfo) {//不是登陆状态
        
                    String ticket = request.getParameter("ticket");
                    //有票据,则使用票据去尝试拿取用户信息
                    if (null != ticket){
                        userInfo = redisTemplate.opsForValue().get(ticket);
                    }
                    //无法得到用户信息,则去登陆页面
                    if (null == userInfo){
                        response.sendRedirect("http://cas.com:8080/toLogin?url="+request.getRequestURL().toString());
                        return ;
                    }
        
                    /**
                     * 将用户信息,加载进session中
                     */
                    request.getSession().setAttribute(SSOFilter.USER_INFO,userInfo);
                    redisTemplate.delete(ticket);
                }
        
                filterChain.doFilter(request,servletResponse);
            }
        
            @Override
            public void destroy() {
        
            }
        
        }
        
        
      • 觉得请求上带着ticketId不安全,一是可以redis中带失效时间,二是doFilter接口中取出用户信息就删除,只让登录一次

  • cas-website2

    • package com.enjoy.utils;
      
      import org.springframework.data.redis.core.RedisTemplate;
      
      import javax.servlet.*;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      import java.io.IOException;
      
      public class SSOFilter implements Filter {
          private RedisTemplate redisTemplate;
      
          public static final String USER_INFO = "user";
      
          public SSOFilter(RedisTemplate redisTemplate){
              this.redisTemplate = redisTemplate;
          }
          @Override
          public void init(FilterConfig filterConfig) throws ServletException {
      
          }
      
          @Override
          public void doFilter(ServletRequest servletRequest,
                               ServletResponse servletResponse, FilterChain filterChain)
                  throws IOException, ServletException {
      
              HttpServletRequest request = (HttpServletRequest) servletRequest;
              HttpServletResponse response = (HttpServletResponse)servletResponse;
      
              Object userInfo = request.getSession().getAttribute(USER_INFO);;
      
              //如果未登陆,则拒绝请求,转向登陆页面
              String requestUrl = request.getServletPath();
              if (!"/toLogin".equals(requestUrl)//不是登陆页面
                      && !requestUrl.startsWith("/login")//不是去登陆
                      && null == userInfo) {//不是登陆状态
      
                  String ticket = request.getParameter("ticket");
                  //有票据,则使用票据去尝试拿取用户信息
                  if (null != ticket){
                      userInfo = redisTemplate.opsForValue().get(ticket);
                  }
                  //无法得到用户信息,则去登陆页面
                  if (null == userInfo){
                      response.sendRedirect("http://cas.com:8080/toLogin?url="+request.getRequestURL().toString());
                      return ;
                  }
      
                  /**
                   * 将用户信息,加载进session中
                   */
                  request.getSession().setAttribute(SSOFilter.USER_INFO,userInfo);
              }
      
              filterChain.doFilter(request,servletResponse);
          }
      
          @Override
          public void destroy() {
      
          }
      
      }
      
      
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值