传统登录
逻辑实现
缺陷
仅限单台tomcat可以。集群tomcat就会出现问题,存在session共享的问题,每个系统都有自己的session,不能统一。
共享session解决方案
- tomcat的session复制
优点:不需要额外开发,只需要搭建tomcat集群即可。
缺点:tomcat 是全局session复制,集群内每个tomcat的session完全同步(也就是任何时候都完全一样的) 在大规模应用的时候,用户过多,集群内tomcat数量过多,session的全局复制会导致集群性能下降, 因此,tomcat的数量不能太多,5个以下为好。 - 实现单点登录系统,提供服务接口。把session数据存放在redis。
Redis可以设置key的生存时间、访问速度快效率高。
优点:redis存取速度快,不会出现多个节点session复制的问题。效率高。
单点登录系统流程
单点实例
portal项目(前台展示),点击登录跳转到sso系统的登录页面(login.html->login.jsp),验证账号密码之后,保存token(redis服务)到cookie(会话结束则消失),
- UserController
@Autowired
private UserService userService;
//创建用户
@RequestMapping(value="/register", method=RequestMethod.POST)
@ResponseBody
public TaotaoResult createUser(TbUser user) {
try {
TaotaoResult result = userService.createUser(user);
return result;
} catch (Exception e) {
e.printStackTrace();
return TaotaoResult.build(500, ExceptionUtil.getStackTrace(e));
}
}
//用户登录
@RequestMapping(value="/login", method=RequestMethod.POST)
@ResponseBody
public TaotaoResult userLogin(String username, String password,
HttpServletRequest request, HttpServletResponse response) {
try {
TaotaoResult result = userService.userLogin(username, password, request, response);
return result;
} catch (Exception e) {
e.printStackTrace();
return TaotaoResult.build(500, ExceptionUtil.getStackTrace(e));
}
}
@RequestMapping("/token/{token}")
@ResponseBody
public Object getUserByToken(@PathVariable String token, String callback) {
TaotaoResult result = null;
try {
result = userService.getUserByToken(token);
} catch (Exception e) {
e.printStackTrace();
result = TaotaoResult.build(500, ExceptionUtil.getStackTrace(e));
}
//判断是否为jsonp调用
if (StringUtils.isBlank(callback)) {
return result;
} else {
MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(result);
mappingJacksonValue.setJsonpFunction(callback);
return mappingJacksonValue;
}
}
- UserServiceImpl
public TaotaoResult userLogin(String username, String password,
HttpServletRequest request, HttpServletResponse response) {
TbUserExample example = new TbUserExample();
Criteria criteria = example.createCriteria();
criteria.andUsernameEqualTo(username);
List list = userMapper.selectByExample(example);
//如果没有此用户名
if (null == list || list.size() == 0) {
return TaotaoResult.build(400, "用户名或密码错误");
}
TbUser user = list.get(0);
//比对密码
if (!DigestUtils.md5DigestAsHex(password.getBytes()).equals(user.getPassword())) {
return TaotaoResult.build(400, "用户名或密码错误");
}
//生成token
String token = UUID.randomUUID().toString();
//保存用户之前,把用户对象中的密码清空。
user.setPassword(null);
//把用户信息写入redis
jedisClient.set(REDIS_USER_SESSION_KEY + ":" + token, JsonUtils.objectToJson(user));
//设置session的过期时间
jedisClient.expire(REDIS_USER_SESSION_KEY + ":" + token, SSO_SESSION_EXPIRE);
//添加写cookie的逻辑,cookie的有效期是关闭浏览器就失效。
CookieUtils.setCookie(request, response, "TT_TOKEN", token);
//返回token
return TaotaoResult.ok(token);
}
@Override
public TaotaoResult getUserByToken(String token) {
//根据token从redis中查询用户信息
String json = jedisClient.get(REDIS_USER_SESSION_KEY + ":" + token);
//判断是否为空
System.out.println(json);
if (StringUtils.isBlank(json)) {
return TaotaoResult.build(400, "此session已经过期,请重新登录");
}
//更新过期时间
jedisClient.expire(REDIS_USER_SESSION_KEY + ":" + token, SSO_SESSION_EXPIRE);
//返回用户信息
return TaotaoResult.ok(JsonUtils.jsonToPojo(json, TbUser.class));
}
- PageController
@Controller
@RequestMapping("/page")
public class PageController {
@RequestMapping("/register")
public String showRegister() {
return "register";
}
@RequestMapping("/login")
public String showLogin(String redirect, Model model) {
model.addAttribute("redirect", redirect);
return "login";
}
}
redirect 为回调的url
- login.jsp
doLogin:function() {
$.post("/user/login", $("#formlogin").serialize(),function(data){
if (data.status == 200) {
alert("登录成功!");
if (redirectUrl == "") {
location.href = "http://localhost:8083";
} else {
location.href = redirectUrl;
}
} else {
alert("登录失败,原因是:" + data.msg);
$("#loginname").select();
}
});
},
cookie共享
- domain
- path
cookie共享
拦截器的实现
- LoginInterceptor
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.UserService;
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执行之前处理
//判断用户是否登录
//从cookie中取token
String token = CookieUtils.getCookieValue(request, "TT_TOKEN");
//根据token换取用户信息,调用sso系统的接口。
TbUser user = userService.getUserByToken(token);
//取不到用户信息
if (null == user) {
//跳转到登录页面,把用户请求的url作为参数传递给登录页面。
response.sendRedirect(userService.SSO_DOMAIN_BASE_USRL + userService.SSO_PAGE_LOGIN
+ "?redirect=" + request.getRequestURL());
//返回false
return false;
}
//取到用户信息,放行
//把用户信息放入Request
request.setAttribute("user", user);
//返回值决定handler是否执行。true:执行,false:不执行。
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// handler执行之后,返回ModelAndView之前
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
// 返回ModelAndView之后。
//响应用户之后。
}
}
- 拦截器的配置