推荐一下自己的一个数据结构与算法整合的库,还处于更新状态
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
- @Override
- public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2)
- throws IOException, ServletException
- {
- HttpServletRequest request = (HttpServletRequest) arg0;
- HttpSession session = request.getSession();
- Object isAuth = session.getAttribute(SessionConstant.markIsAuth);
[java] view plain copy
- // 说明在本系统已经登录过了
- if (null!=isAuth)
- {
- String encryptedToken = (String) session.getAttribute(SessionConstant.USER_LOGIN_TOKEN_IN_SESSION);
- String redirectUrl = StringUtils.isEmpty(request.getParameter("redirectUrl"))
- ? TmallUrlEnum.TMALL_PORTAL.getLoginNotifyUrl()+"?"
- : request.getParameter("redirectUrl");
- HttpServletResponse response = (HttpServletResponse) arg1;
- response.sendRedirect(redirectUrl + "token=" + URLEncoder.encode(encryptedToken, "utf-8"));
- } else
- {
- arg2.doFilter(arg0, arg1);
- }
- }
Login登录的逻辑:
[java] view plain copy
- @RequestMapping("/login.do")
- public ModelAndView doLogin(@RequestParam Map params, HttpServletRequest request,
- HttpServletResponse response) throws UnsupportedEncodingException
- {
- ModelAndView modelAndView = null;
- String redirectUrl = StringUtils.isEmpty(params.get("redirectUrl")) ? TmallURLConstant.TMALAL_PORTAL_INDEX_URL
- : (String) params.get("redirectUrl");
[java] view plain copy
- //获取用户的server 地址,为什么要获取呢,因为注销的时候需要将旗下的登录着的子系统也注销了,注销的时候直接for循环发送http请求即可
- String server = StringUtils.isEmpty(params.get("server")) ? TmallUrlEnum.TMALL_PORTAL.getServerUrl()
- : (String) params.get("server");
- String encryptedToken = "";
- /*
- * 下面这段redis判断其实没必要了,因为既然写了一个filter,filter中是ifPresentReturn 存在则返回,这里就不会执行到了
- * 当然我们还需要考虑这种情况,用户直接访问这个controller方法,所以我们需要在filter中设置一个默认返回的页面
- */
- // String encryptedToken = CookieUtils.getUserToken(request,
- // CookieConstant.USER_TOKEN);
- // if (!StringUtils.isEmpty(encryptedToken))
- // {
- // String primitiveToken = RSAUtils.decryptByPublic(encryptedToken,
- // keyProperties.getLoginPublicKey());
- // if (!StringUtils.isEmpty(primitiveToken)
- // &&
- // !StringUtils.isEmpty(redisService.get(String.format(RedisConstant.USER_INFO,
- // primitiveToken))))
- // {
- //
- // HttpSession session = request.getSession();
- // System.out.println(session.getId());
- // session.setAttribute(SessionConstant.markIsAuth, true);
- //// request.getSession(true).setAttribute(SessionConstant.markIsAuth, true);
- // String encode = URLEncoder.encode(encryptedToken, "utf-8");
- // System.out.println(encode);
- // modelAndView = new ModelAndView("redirect:" + redirectUrl + "token=" +
- // encode);
- // return modelAndView;
- // }
- // }
- String email = request.getParameter("email");
- String password = request.getParameter("password");
- User user = userService.findByEmail(email);
- // 双重保障:1.rsa的私钥只要不被泄露 不可能破解2.如果私钥泄露了,用户还需要凑出userId以及user登录的具体时间
- String token = UUID.randomUUID().toString() + "-" + user.getUserId() + "-" + System.currentTimeMillis();
- encryptedToken = RSAUtils.encryptByPrivate(token, keyProperties.getLoginPrivateKey());
- // 将token记录在session中
- // CookieUtils.setUserToken(response, CookieConstant.USER_TOKEN, encryptedToken,
- // CookieConstant.USER_TOKEN_EXPIRE);
[java] view plain copy
- //创建一个全局的session:保持用户的登录记录
- request.getSession(true).setAttribute(SessionConstant.markIsAuth, true);
- // 记录token对应的地址信息
- Set serverList = new HashSet<>();
- String redisKey=String.format(RedisConstant.USER_SERVLER_URL, token);
- String servers = redisService.get(redisKey);
- if (!StringUtils.isEmpty(servers))
- {
- serverList = new HashSet<>(JsonUtils.json2List(servers, new TypeToken
>()
- {
- }.getType()));
- }
- serverList.add(server);
[java] view plain copy
- //在redis中记录登录着的服务的信息
- redisService.set(redisKey, JsonUtils.obj2Json(serverList));
- redisService.set(String.format(RedisConstant.USER_INFO, token), JsonUtils.obj2Json(user),
- RedisConstant.USER_INFO_EXPIRE);
- }
- if (params.containsKey("error"))
- {
- modelAndView = new ModelAndView("login", params);
- } else
- {
- String string = URLEncoder.encode(encryptedToken, "utf-8");
- System.out.println(string);
- request.getSession().setAttribute(SessionConstant.markIsAuth, true);
- modelAndView = new ModelAndView("redirect:" + redirectUrl + "token=" + string+"&JSESSIONID="+request.getSession().getId());
- }
- return modelAndView;
- }
[java] view plain copy
-
子系统:
filter:
[java] view plain copy
- @Override
- public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
- throws IOException, ServletException
- {
- HttpServletRequest request = (HttpServletRequest) req;
- HttpServletResponse response = (HttpServletResponse) resp;
- HttpSession session = request.getSession();
- Object isAuth = session.getAttribute(SessionConstant.markIsAuth);
- if (null!=isAuth)
- {
- chain.doFilter(req, resp);
- return;
- }
- String token = request.getParameter("token");
- if (!StringUtils.isEmpty(token))
- {
- // 校验token的有效性
- // RestTemplate restTemplate = new RestTemplate();
- // String json = restTemplate.getForObj1ect(TmallURLConstant.TMALL_LOGIN_SSO_AUTH_TOKEN_URL + "?token=" + URLEncoder.encode(token, "utf-8"),
- // String.class);
- //既然用redis了,为啥不将redis作用共享的呢,子系统只有读的权限,就可以省略一个http调用了啊,如果是基于其他方式的话,就需要http调用了
- String primitiveToken = RSAUtils.decryptByPublic(token, keyProperties.getLoginPublicKey());
- if(StringUtils.isEmpty(primitiveToken))
- {
- response.sendRedirect(TmallURLConstant.TMALL_LOGIN_URL+"?redirectUrl="+PortalUtils.getRedirectUrl(request));
- }
- String json=redisService.get(String.format(RedisConstant.USER_INFO, primitiveToken));
- if (!StringUtils.isEmpty(json))
- {
- User user = JsonUtils.json2Object(json, User.class);
- session.setAttribute(SessionConstant.markIsAuth, true);
- session.setAttribute(SessionConstant.USER_IN_SESSION, user);
- chain.doFilter(req, resp);
- return;
- }
- }
- //传入的token无效的时候也会跳转到查询用户是否登录
- // 查询用户在sso-server中是否登录
- // response.sendRedirect(TmallURLConstant.TMALL_LOGIN_SSO_CHECKLOGIN_URL + "?redirectUrl="
- // + PortalUtils.getRedirectUrl(request));
- response.sendRedirect(TmallURLConstant.TMALL_LOGIN_URL+"?redirectUrl="+PortalUtils.getRedirectUrl(request)+"&server="+PortalUtils.getServerBaseUrl(request));
- }
上述的有几处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,没有则重新创建一个新的