單點登陸SSO(一)原理、多客戶端登陸


感謝coding老師的課程,讓我能很好的學習,掌握單點登陸的相關知識,所有的代碼都來自或者修改coding老師的視頻,簡單總結一下

單點登陸項目GitHub

github:https://github.com/Handoking/Single-Sign-on

單點登陸的原理

SSO示意圖

單點登陸最主要保證的就是一處登陸,處處登陸,也就是我們登陸taobao後,訪問賬號相通的另一個應用時,只需要刷新頁面就登陸了。單點登陸的設計合理性就是認證中心的存在,不可能讓taobao的服務器通知其他產品矩陣中的應用。
本博文的單點登陸是一個比較傳統的實現方式-CAS原理,使用全局會話,局部會話完成。多客戶端實現單點登陸的步驟:

  1. 用戶第一次訪問taobao,認證中心發現用戶未登錄,先登錄
  2. 登陸成功後,生成唯一token,並存入認證中心(現在很多自動完成認證,不需要存入),生成全局會話
  3. 訪問tianmao時,先從全局會話中獲得token,攜帶token訪問
  4. 認證中心驗證token是否存在
  5. 驗證成功,重定向到tianmao首頁。

原理

  1. 認證中心(授權服務器)保存一份全局的session(一般用cache中間件比如redis等實現),多個客戶端保存本地局部session。
  2. 用戶訪問時,客戶端先查看本地session是否登陸,如果沒有登陸,跳轉認證中心全局session是否登陸。如果已經登陸,授權訪問客戶端(重定向到來時的地址)。
  3. 如果全局session中也沒有登陸,用戶先登陸認證中心。登陸成功後,全局會話中存入登陸的票據或者令牌
    返回用戶訪問的應用,客戶端將登陸信息放入本地session.

單點登陸的關鍵

  1. 應用1跳轉到認證中心登陸或者驗證後,需要重定向應用1.因此關鍵是所有訪問要攜帶來時的地址
  2. 產生令牌,傳遞保存令牌,驗證令牌。

客戶端一攔截器

在認證中心授權前,用戶不能訪問客戶端的資源,因此使用攔截器或者過濾器來實現。我採用攔截器。
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
發佈了131 篇原創文章 · 獲贊 74 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章