20單點登入
阿新 • • 發佈:2018-11-04
傳統登入問題:
之前實現的登入和註冊是在同一個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);
}
}