CAS單點登入-單使用者登入(十九)
簡介
所謂“單使用者單賬戶登入”是指:在同一系統中,一個使用者名不能在兩個地方同時登入。
如:
當某賬号在 A 處登入後,在未退出的情況下,如果再到 B 處登入,那麼,系統會擠下 A 處登入的賬号
程式邏輯
我們一路學習cas過來應該知道如下知識
- 維持一個使用者狀态是用
tgt
- 使用者登入成功後
會建立tgt
- 業務系統驗證成功是采用
的校驗st
- 使用者登出相當于删除
tgt
- 删除tgt采用
CentralAuthenticationService.destroyTicketGrantingTicket
有以上的知識我們即可對其他使用者的提出,程式應該滿足以下邏輯:
- 監聽
建立事件tgt
- 擷取使用者id,以及tgt
- 根據使用者id,認證方式
尋找所有的tgtclientName
- 過濾非目前使用者的tgt的所有tgt
- 删除過濾後的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)