分布式系统登录功能拦截器的实现以及cookie的共享问题(利用cookie实现session在分布式系统的共享)
当我们的网站采用分布式部署系统时,每个子系统拥有自己独立的session,如果不实现session共享,当用户切换系统访问的时候,会不停的提示登录,这对于用户体验是非常不好的。因此对于多个子系统的的访问,为了达到用户登录一次即可以访问其他各个子系统,我们采用了sso单点登录系统。之前文章介绍了单点登录系统的实现功能1,现在我们来看下当访问子系统时如何拦截用户,当用户的session过期了,如何提示用户登录,这里采用了SpringMVC的拦截器的实现。
(1)当登录页面时,用户登录成功后,在页面的首页可以显示:某某,欢迎您访问此网站。这时候我们是将之前生成的用户的token值写入Cookie中,因为只要浏览器没关,cookie就存在,当不设置cookie的过期时间时,浏览器关闭,cookie就消逝。将token的内容写入cookie,然后我们利用jsonp访问sso系统的利用token读取用户信息。然后显示在页面即可。
另外这里还用到一个技术:就是分布式系统的cookie的共享问题。这个将cookie的属性domain设置成全局共享即可。即不同的子系统的域名是不同的,sso.taotao.com;search.taotao.com;我们需要将其域名设置为**.taotao.com,这样可以实现cookie的共享。
具体实现如下:
if (null != request) {// 设置域名的cookie String domainName = getDomainName(request); System.out.println(domainName); if (!"localhost".equals(domainName)) { cookie.setDomain(domainName); } } cookie.setPath("/"); response.addCookie(cookie); } catch (Exception e) { e.printStackTrace(); } } /** * 得到cookie的域名 */ private static final String getDomainName(HttpServletRequest request) { String domainName = null; String serverName = request.getRequestURL().toString(); if (serverName == null || serverName.equals("")) { domainName = ""; } else { serverName = serverName.toLowerCase(); serverName = serverName.substring(7); final int end = serverName.indexOf("/"); serverName = serverName.substring(0, end); final String[] domains = serverName.split("\\."); int len = domains.length; if (len > 3) { // www.xxx.com.cn domainName = "." + domains[len - 3] + "." + domains[len - 2] + "." + domains[len - 1]; } else if (len <= 3 && len > 1) { // xxx.com or xxx.cn domainName = "." + domains[len - 2] + "." + domains[len - 1]; } else { domainName = serverName; } } if (domainName != null && domainName.indexOf(":") > 0) { String[] ary = domainName.split("\\:"); domainName = ary[0]; } return domainName; }
(2)当用户访问订单系统的时候,如果用户没有登录,这时候需要拦截用户提示用户登录此系统。这时候用了拦截器的作用。
package com.taotao.portal.interceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import com.taotao.common.utils.CookieUtils; import com.taotao.pojo.TbUser; import com.taotao.portal.service.impl.UserServiceImpl; public class LoginInterceptor implements HandlerInterceptor { @Autowired private UserServiceImpl userService; //执行之前拦截 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { /* handler执行之前拦截,根据true或者false来判断是否拦截。 浏览器没关,这个cookie就存在,也就说token在里面,至于token的session是否有效,这个需要再判断。 当用户登录子系统时,需要提示用户进行登录,这是好用到拦截器,拦截这个页面,原理的具体实现是这样的 第一步:当用户访问页面时,判断cookie中是否有该用户的token。即先从cookie中获取token对象。 * 第二步:根据token查询用户是否存在,调用sso的从token中查询用户的接口来查询用户(这个过程是先从redis中检查token是否过期,如果过期则没有用户,需要登录,如果没有过期,则表示存在用户,因为之前用户信息写入了redis) *通过查询用户,如果用户存在,则放行,如果不存在,则进行拦截,则跳转到sso的登录系统,提示登录 */ String token = CookieUtils.getCookieValue(request,"TT_TOKEN"); //根据token来获取用户的信息,调用sso接口 TbUser user= userService.getBySSO(token); //判断用户是否存在 if (user==null) { //如果用户为空,则拦截到登录界面,页面重定向到的登录页面 response.sendRedirect(userService.SSO_BASE_URL+userService.SSO_PAGE_LOGIN+"?redirect="+request.getRequestURL()); //response.sendRedirect(userService.SSO_BASE_URL + userService.SSO_PAGE_LOGIN + "?redirect=" + request.getRequestURL()); return false; } return true; } //之后拦截 @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { // handler执行之后拦截 } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // TODO Auto-generated method stub } }
调用sso根据token获取用户信息
package com.taotao.portal.service.impl; import org.springframework.beans.factory.annotation.Value; import com.taotao.common.utils.HttpClientUtil; import com.taotao.common.utils.JsonUtils; import com.taotao.common.utils.TaotaoResult; import com.taotao.pojo.TbUser; import com.taotao.portal.service.UserService; //通过token来获取用户信息,需要调用sso的接口信息 public class UserServiceImpl implements UserService { @Value("${SSO_BASE_URL}") public String SSO_BASE_URL; @Value("${SSO_TOKEN_URL}") public String SSO_TOKEN_URL; @Value("${SSO_PAGE_LOGIN}") public String SSO_PAGE_LOGIN; //这是根据token获取用户信息 @Override public TbUser getBySSO(String token) {
//不同子系统之间的访问,跨域访问,利用httpclient来实现 String json = HttpClientUtil.doGet(SSO_BASE_URL+SSO_TOKEN_URL+token); TaotaoResult result=TaotaoResult.formatToPojo(json, TbUser.class); if (result!=null) { TbUser user=(TbUser) result.getData(); return user; } return null; } }
最后不要忘了重要的一步:在springMVC中配置拦截器。这个配置就关系到到了哪个功能模块,使用拦截器,比如我们提交订单的时候,如果用户没有登录,则此处增加拦截器。因为拦截器是在我们商城的大的一个客户端,淘淘商城中相当于taotao-portal中,当我们写好了订单服务的接口时,url是order/***,用httpclientUtil来调用订单服务的接口,此时我们在springMVC.xml配置文件中,设置拦截order,如果提交订单时如果没有登录就会跳转到登录窗口。
只需要将这里的
在springMVC中配置拦截器: