天天看點

CAS單點登入-單使用者登入(十九)CAS單點登入-單使用者登入(十九)簡介程式邏輯實戰測試作者聯系方式

CAS單點登入-單使用者登入(十九)

簡介

所謂“單使用者單賬戶登入”是指:在同一系統中,一個使用者名不能在兩個地方同時登入。

如:

當某賬号在 A 處登入後,在未退出的情況下,如果再到 B 處登入,那麼,系統會擠下 A 處登入的賬号

程式邏輯

我們一路學習cas過來應該知道如下知識

  1. 維持一個使用者狀态是用

    tgt

  2. 使用者登入成功後

    tgt

    會建立
  3. 業務系統驗證成功是采用

    st

    的校驗
  4. 使用者登出相當于删除

    tgt

  5. 删除tgt采用

    CentralAuthenticationService.destroyTicketGrantingTicket

有以上的知識我們即可對其他使用者的提出,程式應該滿足以下邏輯:

  1. 監聽

    tgt

    建立事件
  2. 擷取使用者id,以及tgt
  3. 根據使用者id,認證方式

    clientName

    尋找所有的tgt
  4. 過濾非目前使用者的tgt的所有tgt
  5. 删除過濾後的tgt(正确的邏輯過濾後一般情況剩下一個,因為已經單使用者登入了)

第三點詳解:

為什麼要采用clientName進行過濾呢,因為認證平台可能通過restful認證,qq、github、微信的OAuth2認證等等,是以認證方式不同,最後的使用者id以及clientName會不同,是以要根據使用者認證方式以及id,找到所有該使用者的認證方式進行删除tgt,否則會出現,oauth2登入的使用者用賬号登入無法強制登出

實戰

TGT建立監聽

這個監聽是為了使用者登入成功後對其他使用者進行剔除

TGTCreateEventListener.java

/*
 * 版權所有.(c)2008-2017. 卡爾科技工作室
 */

package com.carl.sso.support.single.listener;

import com.carl.sso.support.single.service.IUserIdObtainService;
import com.carl.sso.support.single.service.TriggerLogoutService;
import org.apereo.cas.support.events.ticket.CasTicketGrantingTicketCreatedEvent;
import org.apereo.cas.ticket.TicketGrantingTicket;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;

import javax.validation.constraints.NotNull;
import java.util.List;

/**
 * 識别事件然後删除
 *
 * @author Carl
 * @version 建立時間:2017/11/29
 */
public class TGTCreateEventListener {
    private TriggerLogoutService logoutService;
    private IUserIdObtainService service;

    public TGTCreateEventListener(@NotNull TriggerLogoutService logoutService, @NotNull IUserIdObtainService service) {
        this.logoutService = logoutService;
        this.service = service;
    }

    @EventListener
    @Async
    public void onTgtCreateEvent(CasTicketGrantingTicketCreatedEvent event) {
        TicketGrantingTicket ticketGrantingTicket = event.getTicketGrantingTicket();
        String id = ticketGrantingTicket.getAuthentication().getPrincipal().getId();
        String tgt = ticketGrantingTicket.getId();
        String clientName = (String) ticketGrantingTicket.getAuthentication().getAttributes().get("clientName");
        //擷取可以認證的id
        List<String> authIds = service.obtain(clientName, id);
        if (authIds != null) {
            //循環觸發登出
            authIds.forEach(authId -> logoutService.triggerLogout(authId, tgt));
        }
    }
}

           

剔除過濾使用者

根據使用者id,tgt,篩選出使用者,并剔除

TriggerLogoutService.java

/*
 * 版權所有.(c)2008-2017. 卡爾科技工作室
 */



package com.carl.sso.support.single.service;

import org.apereo.cas.CentralAuthenticationService;
import org.apereo.cas.authentication.Authentication;
import org.apereo.cas.ticket.Ticket;
import org.apereo.cas.ticket.TicketGrantingTicket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collection;

/**
 * 登出觸發器
 *
 * @author Carl
 * @date 2017/11/29
 */
public class TriggerLogoutService {
    private static final Logger LOGGER = LoggerFactory.getLogger(TriggerLogoutService.class);
    private CentralAuthenticationService service;

    public TriggerLogoutService(CentralAuthenticationService service) {
        this.service = service;
    }

    /**
     * 觸發其他使用者退出
     *
     * @param id  使用者id
     * @param tgt 目前登入的tgt
     */
    public void triggerLogout(String id, String tgt) {
        //找出使用者id,并且不為目前tgt的,這裡應當考慮資料性能,直接篩選使用者再篩選tgt
        Collection<Ticket> tickets = this.service.getTickets(ticket -> {
            if(ticket instanceof TicketGrantingTicket) {
                TicketGrantingTicket t = ((TicketGrantingTicket)ticket).getRoot();
                Authentication authentication = t.getAuthentication();
                return t != null && authentication != null
                        && authentication.getPrincipal() != null && id.equals(authentication.getPrincipal().getId())
                        && !tgt.equals(t.getId());
            } else {
                return false;
            }

        });

        if (tickets != null && tickets.size() > 0) {
            LOGGER.info(String.format("[%s]強制強制登出%s", id, tickets.size()));
        }

        //發出登出
        for (Ticket ticket : tickets) {
            service.destroyTicketGrantingTicket(ticket.getId());
        }
    }
}

           

擷取使用者id

UserIdObtainServiceImpl.java

/*
 * 版權所有.(c)2008-2017. 卡爾科技工作室
 */

package com.carl.sso.support.single.service;


import java.util.ArrayList;
import java.util.List;

/**
 * @author Carl
 * @version 建立時間:2017/11/29
 */
public class UserIdObtainServiceImpl implements IUserIdObtainService {

    public UserIdObtainServiceImpl() {

    }

    @Override
    public List<String> obtain(String clientName, String id) {
        //由于這裡目前隻做測試是以隻傳回目前的id,在正常的情況邏輯應該如下

        //根據校驗client以及登入的id找到其他同一個使用者的所有校驗id傳回,如通過郵箱登入的id,通過github登入的id等等
        List<String> ids = new ArrayList<>();
        ids.add(id);
        return ids;
    }
}

           

spring配置注冊

SingleLogoutTriggerConfiguration.java

/*
 * 版權所有.(c)2008-2017. 卡爾科技工作室
 */


package com.carl.sso.support.single.config;

import com.carl.sso.support.single.listener.TGTCreateEventListener;
import com.carl.sso.support.single.service.TriggerLogoutService;
import com.carl.sso.support.single.service.UserIdObtainServiceImpl;
import org.apereo.cas.CentralAuthenticationService;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 登出配置
 *
 * @author Carl
 * @date 2017/11/29
 */
@Configuration("singleLogoutTriggerConfiguration")
@EnableConfigurationProperties(CasConfigurationProperties.class)
public class SingleLogoutTriggerConfiguration {
    @Autowired
    private CentralAuthenticationService centralAuthenticationService;

    /**
     * 觸發登出服務
     *
     * @return 觸發登出服務
     */
    @Bean
    protected TriggerLogoutService triggerLogoutService() {
        return new TriggerLogoutService(centralAuthenticationService);
    }

    @Bean
    //注冊事件監聽tgt的建立
    protected TGTCreateEventListener tgtCreateEventListener() {
        TGTCreateEventListener listener = new TGTCreateEventListener(triggerLogoutService(), new UserIdObtainServiceImpl());
        return listener;
    }
}

           

測試

代碼送出可以參考github送出

測試流程:

在chrome浏覽器登陸,然後在IE浏覽器登陸同樣的賬号,chrome浏覽器的使用者已登出

下載下傳代碼嘗試:[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-aOLNA5A3-1611805583688)(https://img.shields.io/badge/downloads-v1.7.0=RC1-brightgreen.svg)] 其他版本可以到GitHub或者碼雲檢視

發現一些意外的事情可以考慮翻翻前面的部落格進行學習哦

作者聯系方式

如果技術的交流或者疑問可以聯系或者提出issue。

郵箱:[email protected]

QQ: 756884434 (請注明:SSO-CSDN)

繼續閱讀