传统登录问题:
之前实现的登录和注册是在同一个tomcat内部完成。我们现在的系统架构是每一个系统都是由一个团队进行维护,每个系统都是单独部署运行一个单独的tomcat,所以,不能将用户的登录信息保存到session中(多个tomcat的session是不能共享的),所以我们需要一个单独的系统来维护用户的登录信息。
如何解决?
解决的关键就在于如何实现多个Tomcat之间的数据共享,Session是无法完成这一点的,因此我们需要用其它东西来代替Session的功能。
要代替Session,需要具备怎样的特点?
- 内存中保存,速度快
- 数据具有时效性
- 多项目数据共享
而这些特性Redis都具备,因此我们的解决方案就是使用Redis来代替Session。在一个独立系统中完成登录注册的逻辑,并完成用户登录时数据保存到Redis中。这样的一个系统就是单点登录系统(SSO)
====================
之前的登录流程
这套流程只能运行在单Tomcat中,因为Session无法共享
现在的登录流程
这套流程中,用Redis代替了Session,实现多系统之间的数据共享
登录成功后,将生成的token保存自cookie 和Redis中,访问其他系统时候,拿着cookie去Redis查,如果能查到,说明是该用户。
==========================
工具类: CookieUtils
package com.taotao.store.web.util;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* Cookie 工具类
*
*/
public final class CookieUtils {
protected static final Logger logger = LoggerFactory.getLogger(CookieUtils.class);
/**
* 得到Cookie的值, 不编码
*
* @param request
* @param cookieName
* @return
*/
public static String getCookieValue(HttpServletRequest request, String cookieName) {
return getCookieValue(request, cookieName, false);
}
/**
* 得到Cookie的值,
*
* @param request
* @param cookieName
* @return
*/
public static String getCookieValue(HttpServletRequest request, String cookieName, boolean isDecoder) {
Cookie[] cookieList = request.getCookies();
if (cookieList == null || cookieName == null){
return null;
}
String retValue = null;
try {
for (int i = 0; i < cookieList.length; i++) {
if (cookieList[i].getName().equals(cookieName)) {
if (isDecoder) {
retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8");
} else {
retValue = cookieList[i].getValue();
}
break;
}
}
} catch (UnsupportedEncodingException e) {
logger.error("Cookie Decode Error.", e);
}
return retValue;
}
/**
* 得到Cookie的值,
*
* @param request
* @param cookieName
* @return
*/
public static String getCookieValue(HttpServletRequest request, String cookieName, String encodeString) {
Cookie[] cookieList = request.getCookies();
if (cookieList == null || cookieName == null){
return null;
}
String retValue = null;
try {
for (int i = 0; i < cookieList.length; i++) {
if (cookieList[i].getName().equals(cookieName)) {
retValue = URLDecoder.decode(cookieList[i].getValue(), encodeString);
break;
}
}
} catch (UnsupportedEncodingException e) {
logger.error("Cookie Decode Error.", e);
}
return retValue;
}
/**
* 设置Cookie的值 不设置生效时间默认浏览器关闭即失效,也不编码
*/
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue) {
setCookie(request, response, cookieName, cookieValue, -1);
}
/**
* 设置Cookie的值 在指定时间内生效,但不编码
*/
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxage) {
setCookie(request, response, cookieName, cookieValue, cookieMaxage, false);
}
/**
* 设置Cookie的值 不设置生效时间,但编码
*/
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, boolean isEncode) {
setCookie(request, response, cookieName, cookieValue, -1, isEncode);
}
/**
* 设置Cookie的值 在指定时间内生效, 编码参数
*/
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxage, boolean isEncode) {
doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, isEncode);
}
/**
* 设置Cookie的值 在指定时间内生效, 编码参数(指定编码)
*/
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxage, String encodeString) {
doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, encodeString);
}
/**
* 删除Cookie带cookie域名
*/
public static void deleteCookie(HttpServletRequest request, HttpServletResponse response, String cookieName) {
doSetCookie(request, response, cookieName, "", -1, false);
}
/**
* 设置Cookie的值,并使其在指定时间内生效
*
* @param cookieMaxage
* cookie生效的最大秒数
*/
private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxage, boolean isEncode) {
try {
if (cookieValue == null) {
cookieValue = "";
} else if (isEncode) {
cookieValue = URLEncoder.encode(cookieValue, "utf-8");
}
Cookie cookie = new Cookie(cookieName, cookieValue);
if (cookieMaxage > 0)
cookie.setMaxAge(cookieMaxage);
if (null != request)// 设置域名的cookie
cookie.setDomain(getDomainName(request));
cookie.setPath("/");
response.addCookie(cookie);
} catch (Exception e) {
logger.error("Cookie Encode Error.", e);
}
}
/**
* 设置Cookie的值,并使其在指定时间内生效
*
* @param cookieMaxage
* cookie生效的最大秒数
*/
private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxage, String encodeString) {
try {
if (cookieValue == null) {
cookieValue = "";
} else {
cookieValue = URLEncoder.encode(cookieValue, encodeString);
}
Cookie cookie = new Cookie(cookieName, cookieValue);
if (cookieMaxage > 0)
cookie.setMaxAge(cookieMaxage);
if (null != request)// 设置域名的cookie
cookie.setDomain(getDomainName(request));
cookie.setPath("/");
response.addCookie(cookie);
} catch (Exception e) {
logger.error("Cookie Encode Error.", e);
}
}
/**
* 得到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;
}
}
=====================================
1登录的代码:
controller:
service层:
======================================
其他系统运行的时候,要确保在登录的状态下进行,所以可以写拦截器。再调用其他接口的时候,先判断是否登录
package com.taotao.web.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
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.web.bean.User;
import com.taotao.web.bean.UserThreadLocal;
import com.taotao.web.service.UserService;
/**
* 订单的相关操作都要求用户必须登录 ,所以前置拦截判断
* 拦截器逻辑:
* 1.获取cookie中的token
* 2.如果token为空,说明未登录,拦截,重定向到登录页
* 3.如果token不为空,调用SSO系统接口获取用户信息
* 4.如果User为null,说明未登录,拦截,重定向到登录页
* 5.user不为null,说明登录有效,放行
* @author Henry
*
*/
public class UserLoginInterceptor implements HandlerInterceptor{
@Autowired
private UserService userService;
private final String COOKIE_NAME = "TT_TOKEN";
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
// 前置方法 负责拦截 return true(通过,不拦截),return false(拦截,请求结束)
String token = CookieUtils.getCookieValue(request, COOKIE_NAME);
if(StringUtils.isEmpty(token)){
response.sendRedirect("http://sso.taotao.com/user/page/login.html");
return false;
}
User user = this.userService.queryUserByToken(token);
if(user == null){
response.sendRedirect("http://sso.taotao.com/user/page/login.html");
return false;
}
//此处User对象已经获取到
UserThreadLocal.set(user);
return true;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// 后置方法
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
// 完成方法 擦除本地线程中的User对象
UserThreadLocal.set(null);
}
}