天天看點

SSO單點登入詳解-------五、手寫單點登入用戶端和服務端單點登出

一、前言

單點登入自然也要單點登出,在一個子系統中登出,所有子系統的會話都将被銷毀,用下面的圖來說明。單點登出難點在于在其中一個系統登出之後,需要把其他的子系統的會話銷毀.是以肯定需要子系統在令牌校驗通過之後,統一認證中心要把該子系統的位址和會話記錄起來.才能在登出的時候找到這些子系統通,依次調用子系統通的登出方法,銷毀局部會話.

二、單點登出流程圖

SSO單點登入詳解-------五、手寫單點登入用戶端和服務端單點登出

單點登出流程

SSO單點登入詳解-------五、手寫單點登入用戶端和服務端單點登出

cookie和session存儲結構

三、代碼實作

用戶端(注意:兩個用戶端項目都得改):

步驟:

1.在兩個用戶端項目中修改

main.jsp

,讓退出的位址映射到統一認證中心的登出方法.

2.在

SSOClientFilter.java

校驗令牌資訊token的時候,還需要把該系統的登出位址和該系統的會話id一并的發送到統一認證中心.修改

SSOClientFilter.java

doFilter

方法

@Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        HttpSession session = req.getSession();
        //1.判斷是否有局部的會話
        Boolean isLogin = (Boolean) session.getAttribute("isLogin");
        if(isLogin!=null && isLogin){
            //有局部會話,直接放行.
            chain.doFilter(request, response);
            return;
        }
        //判斷位址欄中是否有攜帶token參數.
        String token = req.getParameter("token");
        if(StringUtils.isNoneBlank(token)){
            //token資訊不為null,說明位址中包含了token,擁有令牌.
            //判斷token資訊是否由認證中心産生的.
            //驗證位址為:http://www.sso.com:8443/verify
            String httpURL = SSOClientUtil.SERVER_URL_PREFIX+"/verify";
            Map<String,String> params = new HashMap<String,String>();
            //把用戶端位址欄添加到的token資訊傳遞給統一認證中心進行校驗
            params.put("token", token);
            /**-------------------------階段四添加的代碼start---------------------------------**/
            //擷取用戶端的完整登出位址 http://www.crm.com:8088/logOut
            params.put("clientUrl", SSOClientUtil.getClientLogOutUrl());
            params.put("jsessionid", session.getId());
            /**-------------------------階段四添加的代碼end---------------------------------**/
            try {
                String isVerify = HttpUtil.sendHttpRequest(httpURL, params);
                if("true".equals(isVerify)){
                    //如果傳回的字元串是true,說明這個token是由統一認證中心産生的.
                    //建立局部的會話.
                    session.setAttribute("isLogin", true);
                    //放行該次的請求
                    chain.doFilter(request, response);
                    return;
                }
            } catch (Exception e) {
                //這裡可以完善,比如出現異常在前台顯示具體頁面
                //我們這個案例就不做這個哈.
                e.printStackTrace();
            }
        }
        //沒有局部會話,重定向到統一認證中心,檢查是否有其他的系統已經登入過.
        // http://www.sso.com:8443/checkLogin?redirectUrl=http://www.crm.com:8088
        SSOClientUtil.redirectToSSOURL(req, resp);
    }
           

服務端:

步驟:

1.在

MockDatabaseUtil.java

中模拟表t_client_info存儲已注冊的子系統資訊,建立

ClientInfoVo.java

封裝用戶端資訊

package cn.wolfcode.sso.vo;
import lombok.Getter;
import lombok.Setter;
/**
 * Created by wolfcode-lanxw
 */
@Setter@Getter
public class ClientInfoVo {
    private String clientUrl;//用戶端登出位址
    private String jsessionid;//用戶端會話id
}
           

MockDatabaseUtil.java

中模拟表t_client_info的資訊.

public class MockDatabaseUtil {
    //模拟資料庫中的t_token表
    public static Set<String> T_TOKEN = new HashSet<String>();
    //模拟資料庫中的t_client_info表
    public static Map<String,List<ClientInfoVo>> T_CLIENT_INFO =new HashMap<String,List<ClientInfoVo>>();
}
           

2.需要在

SSOServerController.java

修改

verifyToken

方法的邏輯,擷取用戶端傳過來的令牌資訊(token),用戶端登出位址(clientUrl),用戶端的會話id(jsessionid),并且需要把用戶端位址存儲起來.

@RequestMapping("/verify")
    @ResponseBody
    public String verifyToken(String token,String clientUrl,String jsessionid){
        //在模拟的資料庫表t_token中查找是否有這條記錄
        if(MockDatabaseUtil.T_TOKEN.contains(token)){
            //根據token擷取用戶端的登出位址記錄集合
            List<ClientInfoVo> clientInfoList = MockDatabaseUtil.T_CLIENT_INFO.get(token);
            if(clientInfoList==null){
                //第一個系統注冊的時候擷取出來的集合空的,是以需要建立一個
                clientInfoList = new ArrayList<ClientInfoVo>();
                //建立好之後需要放入到map中,友善後續的擷取
                MockDatabaseUtil.T_CLIENT_INFO.put(token,clientInfoList);
            }
            //封裝用戶端的資訊
            ClientInfoVo vo = new ClientInfoVo();
            vo.setClientUrl(clientUrl);
            vo.setJsessionid(jsessionid);
            //把目前的用戶端位址注冊到集合中.
            clientInfoList.add(vo);
            //說明令牌有效,傳回true
            return "true";
        }
        return "false";
    }
           

3.在

SSOServerController.java

添加一個統一認證中心登入的方法

logOut

@RequestMapping("/logOut")
 public String logOut(HttpSession session){
     //銷毀全局會話
     session.invalidate();
     return "logOut";
 }
           

4.拷貝之前用戶端工具類

HttpUtil.java

到項目中

5.建立

MySessionListener.java

,監聽session的銷毀事件,當全局會話銷毀的時候,擷取全局會話中的令牌資訊token,通過token資訊查詢t_client_info表,擷取已注冊的子系統集合,周遊子系統集合,依次調用子系統的登出方法.

6.清空t_token表資料,清空t_client_info表資料

package cn.wolfcode.sso.listener;

import cn.wolfcode.sso.util.HttpUtil;
import cn.wolfcode.sso.util.MockDatabaseUtil;
import cn.wolfcode.sso.vo.ClientInfoVo;

import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import java.util.List;

/**
 * Created by wolfcode-lanxw
 */
@WebListener
public class MySessionListener implements HttpSessionListener {
    @Override
    public void sessionCreated(HttpSessionEvent se) {
    }
    //監聽session的銷毀事件
    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        HttpSession session = se.getSession();
        //擷取會話中的令牌資訊
        String token = (String) session.getAttribute("token");
        //删除t_token表中的資料
        MockDatabaseUtil.T_TOKEN.remove(token);
        //删除t_client_info表中的資料
        List<ClientInfoVo> clientInfoVoList = MockDatabaseUtil.T_CLIENT_INFO.remove(token);
        try{
            for(ClientInfoVo vo:clientInfoVoList){
                //擷取出注冊的子系統,依次調用子系統的登出的方法
                HttpUtil.sendHttpRequest(vo.getClientUrl(),vo.getJsessionid());
            }
        }catch(Exception e){
            e.printStackTrace();;
        }
    }
}
           

四、單點登出流程梳理

用戶端:

1.在登陸的按鈕連結寫上統一認證中心的登出方法即可.
<a href=”http://www.sso.com/logOut”>退出</a>
2.校驗令牌資訊的同時把用戶端登出位址(clientUrl)和用戶端會話id(jsession)一并的傳到統一認證中心
           

服務端:

1.編寫logOut方法,調用session.invalidate()
    2.建立session的監聽器,在session的監聽器的銷毀方法寫如下邏輯
        2.1擷取session中的token.
        2.2根據token擷取所有用戶端的登出位址和會話id
        2.3通過HttpUtil選項調用用戶端的登出方法.
           

五、測試

在服務端和兩個用戶端運作

tomcat7:run

指令.

在浏覽器中按下

Ctrl+Shift+Delete

按鍵清楚cookie和緩存,避免幹擾.

1.通路

http://www.crm.com:8088/main

,跳轉到統一認證中心登入界面.

2.輸入賬号密碼,放行請求,通路到CRM系統的main.jsp頁面.

3.在同一浏覽器中打開多個視窗輸入

http://www.wms.com:8089/main

,都是放行請求.

4.點選CRM系統中的退出按鈕,顯示系統已登出

5.在同一浏覽器中打開

http://www.wms.com:8089/main

,此時就跳轉到統一認證中心的登陸頁面.

如果測試結果和上述一緻,說明單點登出功能也完成了.

六、代碼下載下傳

熟悉git指令的同學:

用戶端單點登出Demo:

git reset --hard 5631a13ecfd0b73bfc27adaa34ba0fd6fe01b2fb
           

用戶端2單點登出Demo:

git reset --hard 01db6af390ff9f765121d3f9e9b1895b0e671bd5
           

服務端單點登出Demo:

git reset --hard 5d619c5065cabd83a16d5b4d34e3c89a5950fb5b
           
不熟悉git指令的同學

用戶端單點登出Demo

用戶端2單點登出Demo

服務端單點登出Demo

原文連結:https://www.jianshu.com/p/667c8f0b514f