感謝coding老師的課程,讓我能很好的學習,掌握單點登陸的相關知識,所有的代碼都來自或者修改coding老師的視頻,簡單總結一下
單點登陸項目GitHub
github:https://github.com/Handoking/Single-Sign-on
單點登陸的原理
單點登陸最主要保證的就是一處登陸,處處登陸,也就是我們登陸taobao後,訪問賬號相通的另一個應用時,只需要刷新頁面就登陸了。單點登陸的設計合理性就是認證中心的存在,不可能讓taobao的服務器通知其他產品矩陣中的應用。
本博文的單點登陸是一個比較傳統的實現方式-CAS原理,使用全局會話,局部會話完成。多客戶端實現單點登陸的步驟:
- 用戶第一次訪問taobao,認證中心發現用戶未登錄,先登錄
- 登陸成功後,生成唯一token,並存入認證中心(現在很多自動完成認證,不需要存入),生成全局會話
- 訪問tianmao時,先從全局會話中獲得token,攜帶token訪問
- 認證中心驗證token是否存在
- 驗證成功,重定向到tianmao首頁。
原理:
- 認證中心(授權服務器)保存一份全局的session(一般用cache中間件比如redis等實現),多個客戶端保存本地局部session。
- 用戶訪問時,客戶端先查看本地session是否登陸,如果沒有登陸,跳轉認證中心全局session是否登陸。如果已經登陸,授權訪問客戶端(重定向到來時的地址)。
- 如果全局session中也沒有登陸,用戶先登陸認證中心。登陸成功後,全局會話中存入登陸的票據或者令牌
返回用戶訪問的應用,客戶端將登陸信息放入本地session.
單點登陸的關鍵:
- 應用1跳轉到認證中心登陸或者驗證後,需要重定向應用1.因此關鍵是所有訪問要攜帶來時的地址
- 產生令牌,傳遞保存令牌,驗證令牌。
客戶端一攔截器
在認證中心授權前,用戶不能訪問客戶端的資源,因此使用攔截器或者過濾器來實現。我採用攔截器。
xml攔截器配置:
<!--配置攔截器-->
<mvc:interceptors>
<mvc:interceptor>
<!--/**攔截所有請求-->
<mvc:mapping path="/**"/>
<!--bean配置的就是攔截器-->
<bean class="com.handoking.interceptor.ClientInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
攔截器將所有訪問客戶端的請求都進行判斷,直接放行或者攔截跳轉到認證中心進行登陸或者驗證。
客戶端攔截器實現代碼:
package com.handoking.interceptor;
import com.handoking.utils.HttpUtil;
import com.handoking.utils.SSOClientUtil;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName TmClientInterceptor
* @Description TODO
* @Author Handoking
* @Date 2019/12/28 15:48
**/
public class TmClientInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
//1.判斷本地是否存在會話
Boolean isLogin = (Boolean) session.getAttribute("isLogin");
if (isLogin!=null&&isLogin){
System.out.println(System.currentTimeMillis()+":再次登陸天貓,直接驗證成功");
return true;
}
//2.判斷本地是否有token
String token = request.getParameter("token");
System.out.println("令牌:"+token);
if (!StringUtils.isEmpty(token)){
String httpUrl = SSOClientUtil.SERVER_URL_PREFIX+"/verify";
Map<String, String> params = new HashMap<>(10);
params.put("token",token);
params.put("clientUrl",SSOClientUtil.getClientLogOutUrl());
params.put("jessionId",session.getId());
String verigfyBool = HttpUtil.sendHttpRequest(httpUrl, params);
if ("true".equals(verigfyBool)) {
//服務器驗證成功
System.out.println(System.currentTimeMillis()+":訪問天貓-服務器驗證成功");
session.setAttribute("isLogin",true);
return true;
}
}
//3.不存在會話、token,那麼攜帶來時的url即redirectUrl,跳轉到服務器判斷是否有其他賬號互通的客戶端登陸
System.out.println(System.currentTimeMillis()+" 本地不存在會話,也不存在token");
SSOClientUtil.redirectToSSOURL(request,response);
return false;
}
@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 {
}
}
SSOClientUtil是一個工具包來完成跳轉認證中心。
package com.handoking.utils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;
import java.util.Properties;
public class SSOClientUtil {
public static Properties ssoProperties = new Properties();
/**統一認證中心地址**/
public static String SERVER_URL_PREFIX;
/**當前客戶端地址**/
public static String CLIENT_HOST_URL;
static {
try {
ssoProperties.load(Objects.requireNonNull(SSOClientUtil.class.getClassLoader().getResourceAsStream("sso.properties")));
} catch (IOException e) {
e.printStackTrace();
}
SERVER_URL_PREFIX = ssoProperties.getProperty("server-url-prefix");
CLIENT_HOST_URL = ssoProperties.getProperty("client-host-url");
}
/**
* 當客戶端請求被攔截,跳往統一認證中心,需要帶redirectUrl的參數,統一認證中心登錄後回調的地址
*/
public static String getRedirectUrl(HttpServletRequest request){
//獲取請求URL
//getServletPath()獲取的是servlet的路徑,是完全匹配web.xml中配置的url-pattern
return CLIENT_HOST_URL+request.getServletPath();
}
/**
* 根據request獲取跳轉到統一認證中心的地址,通過Response跳轉到指定的地址
*/
public static void redirectToSSOURL(HttpServletRequest request,HttpServletResponse response) throws IOException {
String redirectUrl = getRedirectUrl(request);
// 拼接跳轉的url http://www.sso.com:8003/checkLogin?redirectUrl=http://www.tbao.com:8002
StringBuilder url = new StringBuilder(50)
.append(SERVER_URL_PREFIX)
.append("/checkLogin?redirectUrl=")
.append(redirectUrl);
response.sendRedirect(url.toString());
}
/**
* 獲取客戶端的完整登出地址
*/
public static String getClientLogOutUrl(){
return CLIENT_HOST_URL+"/logOut";
}
/**
* 獲取認證中心的登出地址
*/
public static String getServerLogOutUrl(){
return SERVER_URL_PREFIX+"/logOut";
}
}
sso.properties配置文件
server-url-prefix=http://www.sso.com:8003
client-host-url=http://www.tbao.com:8002