SSO基于全局Session和局部Session的实现

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Coder_Joker/article/details/80432869

推荐一下自己的一个数据结构与算法整合的库,还处于更新状态

2019-01-12 18:23 更: demo明天给出 .   emmmmm我记得这个demo应该是18年的啊,怎么19是上个月给的?蛤?---

2019-01-13 18:51 更: https://github.com/ItsFunny/examples/tree/master/sso_demo

什么是sso:single sign on ,一处登录,处处登录

核心流程:

    

 

UML画图不好,请见谅(上面有一处忘记划过来了,就是登录成功后携带token跳转):

 

    1.用户发起Http请求子系统A的受限资源

    2.通过局部会话发现用户并未登录(filter拦截)

    3.跳转到sso服务中心

    4.sso服务中心又通过filter拦截,通过全局会话判断是否登录

        4.1如果用户未登录,则放行让用户跳转到登录页面,用户登录逻辑处:登录成功后设置全局会话,并且将生成的token携带跳转到原先的子系统

        4.2如果发现用户已经登录了,则从会话中去取token,携带跳转到子页面

    5.子系统的filter接收到token后,可以通过http的放行进行校验,也可以自个儿校验

        5.1校验成功后设置局部session,并且返回资源页面

        5.2校验失败,则继续任务3

    当另外一个子系统访问的时候,因为已经有过一个子系统访问过了,所以浏览器与sso服务中心记录了一个cookie,每次提交都会将这个cookie提交(jsessionid),所以这个子系统访问到的也是同一个session

sso-server的代码:

    filter:

    

[java] view plain copy

  1. @Override  
  2. public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2)  
  3.         throws IOException, ServletException  
  4. {  
  5.     HttpServletRequest request = (HttpServletRequest) arg0;  
  6.     HttpSession session = request.getSession();  
  7.         Object isAuth = session.getAttribute(SessionConstant.markIsAuth);  

[java] view plain copy

  1.                 //  说明在本系统已经登录过了  
  2.         if (null!=isAuth)  
  3.         {  
  4.             String encryptedToken = (String) session.getAttribute(SessionConstant.USER_LOGIN_TOKEN_IN_SESSION);  
  5.             String redirectUrl = StringUtils.isEmpty(request.getParameter("redirectUrl"))  
  6.                     ? TmallUrlEnum.TMALL_PORTAL.getLoginNotifyUrl()+"?"  
  7.                     : request.getParameter("redirectUrl");  
  8.             HttpServletResponse response = (HttpServletResponse) arg1;  
  9.             response.sendRedirect(redirectUrl + "token=" + URLEncoder.encode(encryptedToken, "utf-8"));  
  10.         } else  
  11.         {  
  12.             arg2.doFilter(arg0, arg1);  
  13.         }  
  14.     }  

Login登录的逻辑:

    

[java] view plain copy

  1. @RequestMapping("/login.do")  
  2.     public ModelAndView doLogin(@RequestParam Map<String, Object> params, HttpServletRequest request,  
  3.             HttpServletResponse response) throws UnsupportedEncodingException  
  4.     {  
  5.         ModelAndView modelAndView = null;  
  6.         String redirectUrl = StringUtils.isEmpty(params.get("redirectUrl")) ? TmallURLConstant.TMALAL_PORTAL_INDEX_URL  
  7.                 : (String) params.get("redirectUrl");  

[java] view plain copy

  1.                 //获取用户的server 地址,为什么要获取呢,因为注销的时候需要将旗下的登录着的子系统也注销了,注销的时候直接for循环发送http请求即可  
  2.         String server = StringUtils.isEmpty(params.get("server")) ? TmallUrlEnum.TMALL_PORTAL.getServerUrl()  
  3.                 : (String) params.get("server");  
  4.         String encryptedToken = "";  
  5.         /* 
  6.          * 下面这段redis判断其实没必要了,因为既然写了一个filter,filter中是ifPresentReturn 存在则返回,这里就不会执行到了 
  7.          * 当然我们还需要考虑这种情况,用户直接访问这个controller方法,所以我们需要在filter中设置一个默认返回的页面 
  8.          */  
  9.         // String encryptedToken = CookieUtils.getUserToken(request,  
  10.         // CookieConstant.USER_TOKEN);  
  11.         // if (!StringUtils.isEmpty(encryptedToken))  
  12.         // {  
  13.         // String primitiveToken = RSAUtils.decryptByPublic(encryptedToken,  
  14.         // keyProperties.getLoginPublicKey());  
  15.         // if (!StringUtils.isEmpty(primitiveToken)  
  16.         // &&  
  17.         // !StringUtils.isEmpty(redisService.get(String.format(RedisConstant.USER_INFO,  
  18.         // primitiveToken))))  
  19.         // {  
  20.         //  
  21.         // HttpSession session = request.getSession();  
  22.         // System.out.println(session.getId());  
  23.         // session.setAttribute(SessionConstant.markIsAuth, true);  
  24.         //// request.getSession(true).setAttribute(SessionConstant.markIsAuth, true);  
  25.         // String encode = URLEncoder.encode(encryptedToken, "utf-8");  
  26.         // System.out.println(encode);  
  27.         // modelAndView = new ModelAndView("redirect:" + redirectUrl + "token=" +  
  28.         // encode);  
  29.         // return modelAndView;  
  30.         // }  
  31.         // }  
  32.         String email = request.getParameter("email");  
  33.         String password = request.getParameter("password");  
  34.         User user = userService.findByEmail(email);  
  35.             // 双重保障:1.rsa的私钥只要不被泄露 不可能破解2.如果私钥泄露了,用户还需要凑出userId以及user登录的具体时间  
  36.             String token = UUID.randomUUID().toString() + "-" + user.getUserId() + "-" + System.currentTimeMillis();  
  37.             encryptedToken = RSAUtils.encryptByPrivate(token, keyProperties.getLoginPrivateKey());  
  38.             // 将token记录在session中  
  39. //          CookieUtils.setUserToken(response, CookieConstant.USER_TOKEN, encryptedToken,  
  40. //                  CookieConstant.USER_TOKEN_EXPIRE);  

[java] view plain copy

  1.                         //创建一个全局的session:保持用户的登录记录  
  2.             request.getSession(true).setAttribute(SessionConstant.markIsAuth, true);  
  3.             // 记录token对应的地址信息  
  4.             Set<String> serverList = new HashSet<>();  
  5.             String redisKey=String.format(RedisConstant.USER_SERVLER_URL, token);  
  6.             String servers = redisService.get(redisKey);  
  7.             if (!StringUtils.isEmpty(servers))  
  8.             {  
  9.                 serverList = new HashSet<>(JsonUtils.json2List(servers, new TypeToken<List<String>>()  
  10.                 {  
  11.                 }.getType()));  
  12.             }  
  13.             serverList.add(server);  

[java] view plain copy

  1.                         //在redis中记录登录着的服务的信息  
  2.             redisService.set(redisKey, JsonUtils.obj2Json(serverList));  
  3.             redisService.set(String.format(RedisConstant.USER_INFO, token), JsonUtils.obj2Json(user),  
  4.                     RedisConstant.USER_INFO_EXPIRE);  
  5.         }  
  6.         if (params.containsKey("error"))  
  7.         {  
  8.             modelAndView = new ModelAndView("login", params);  
  9.         } else  
  10.         {  
  11.             String string = URLEncoder.encode(encryptedToken, "utf-8");  
  12.             System.out.println(string);  
  13.             request.getSession().setAttribute(SessionConstant.markIsAuth, true);  
  14.             modelAndView = new ModelAndView("redirect:" + redirectUrl + "token=" + string+"&JSESSIONID="+request.getSession().getId());  
  15.         }  
  16.         return modelAndView;  
  17.     }  

[java] view plain copy

  1.   

子系统:

    filter:

    

[java] view plain copy

  1.     @Override  
  2.     public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)  
  3.             throws IOException, ServletException  
  4.     {  
  5.         HttpServletRequest request = (HttpServletRequest) req;  
  6.         HttpServletResponse response = (HttpServletResponse) resp;  
  7.         HttpSession session = request.getSession();  
  8.         Object isAuth = session.getAttribute(SessionConstant.markIsAuth);  
  9.         if (null!=isAuth)  
  10.         {  
  11.             chain.doFilter(req, resp);  
  12.             return;  
  13.         }  
  14.         String token = request.getParameter("token");  
  15.         if (!StringUtils.isEmpty(token))  
  16.         {  
  17.             // 校验token的有效性  
  18. //          RestTemplate restTemplate = new RestTemplate();  
  19. //          String json = restTemplate.getForObj1ect(TmallURLConstant.TMALL_LOGIN_SSO_AUTH_TOKEN_URL + "?token=" + URLEncoder.encode(token, "utf-8"),  
  20. //                  String.class);  
  21.             //既然用redis了,为啥不将redis作用共享的呢,子系统只有读的权限,就可以省略一个http调用了啊,如果是基于其他方式的话,就需要http调用了  
  22.             String primitiveToken = RSAUtils.decryptByPublic(token, keyProperties.getLoginPublicKey());  
  23.             if(StringUtils.isEmpty(primitiveToken))  
  24.             {  
  25.                 response.sendRedirect(TmallURLConstant.TMALL_LOGIN_URL+"?redirectUrl="+PortalUtils.getRedirectUrl(request));  
  26.             }  
  27.             String json=redisService.get(String.format(RedisConstant.USER_INFO, primitiveToken));  
  28.             if (!StringUtils.isEmpty(json))  
  29.             {  
  30.                 User user = JsonUtils.json2Object(json, User.class);  
  31.                 session.setAttribute(SessionConstant.markIsAuth, true);  
  32.                 session.setAttribute(SessionConstant.USER_IN_SESSION, user);  
  33.                 chain.doFilter(req, resp);  
  34.                 return;  
  35.             }  
  36.         }  
  37.         //传入的token无效的时候也会跳转到查询用户是否登录  
  38.         // 查询用户在sso-server中是否登录  
  39. //      response.sendRedirect(TmallURLConstant.TMALL_LOGIN_SSO_CHECKLOGIN_URL + "?redirectUrl="  
  40. //              + PortalUtils.getRedirectUrl(request));  
  41.         response.sendRedirect(TmallURLConstant.TMALL_LOGIN_URL+"?redirectUrl="+PortalUtils.getRedirectUrl(request)+"&server="+PortalUtils.getServerBaseUrl(request));  
  42.     }  

上述的有几处url拼接并没有加入?或者&是因为我截取的时候已经加上了:

	public static String getRedirectUrl(HttpServletRequest request)
	{
		StringBuffer buffer = request.getRequestURL();
		String queryString = request.getQueryString();
		if (!StringUtils.isEmpty(queryString))
		{
			buffer.append("?").append(queryString);
		}
		String t = buffer.toString();
		t += t.contains("?") ? "&" : "?";
		return t;
	}

 

遇到过的坑:

 

    子系统无论我如何去访问sso-server,每次访问到的session都是不同的,这是个大坑

究其原因在于sso-server和其他子系统都在同一个域名之下,而重定向请求是2次request请求,因为是在同一个域名之下,将sso-client1下的jsessionid携带过去了,第一次访问sso-server肯定没有这个相同sessionid的,所以新生成了一个session,并且设置到cookie中,跳转会子系统的时候又发现新改的这个session标识sso-client也没有,所以又重新生成一个,于此循环,所以另外的子系统访问sso-server的时候session永远不可能和其他子系统相同

session的生成:当为某次请求生成一个session的时候,如果请求带了cookie(包含jsessionid),则会试图以从服务器中找寻是否有相同的session,没有则重新创建一个新的

展开阅读全文

没有更多推荐了,返回首页