diff --git a/README.md b/README.md
index 4073fd0..26e77ca 100644
--- a/README.md
+++ b/README.md
@@ -33,6 +33,7 @@
* 验证码发送、校验(注册发送邮箱验证码)
* 服务监控检测
* 校验码登录
+* 单用户登录
## Tutorial: [![Csdn Blog Tutorial](https://img.shields.io/badge/csdn%20blog-tutorial-orange.svg)](http://blog.csdn.net/u010475041/article/category/7156505)
diff --git a/sso-server/pom.xml b/sso-server/pom.xml
index 304f892..b4fe52b 100644
--- a/sso-server/pom.xml
+++ b/sso-server/pom.xml
@@ -32,6 +32,12 @@
sso-support-custom-auth
${project.version}
+
+
+ com.carl.auth
+ sso-support-single-login
+ ${project.version}
+
com.carl.auth
sso-support-captcha
diff --git a/sso-support/pom.xml b/sso-support/pom.xml
index 9d18b25..a45cd3e 100644
--- a/sso-support/pom.xml
+++ b/sso-support/pom.xml
@@ -19,6 +19,7 @@
sso-support-custom-auth
sso-support-captcha
sso-support-validate
+ sso-support-single-login
diff --git a/sso-support/sso-support-single-login/README.md b/sso-support/sso-support-single-login/README.md
new file mode 100644
index 0000000..3135444
--- /dev/null
+++ b/sso-support/sso-support-single-login/README.md
@@ -0,0 +1 @@
+单用户登录,用户踢出
\ No newline at end of file
diff --git a/sso-support/sso-support-single-login/pom.xml b/sso-support/sso-support-single-login/pom.xml
new file mode 100644
index 0000000..d24aedf
--- /dev/null
+++ b/sso-support/sso-support-single-login/pom.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+ sso-support
+ com.carl.auth
+ 1.7.0-SNAPSHOT
+ ../pom.xml
+
+ 4.0.0
+
+ sso-support-single-login
+
+
+
+
+ org.apereo.cas
+ cas-server-core-configuration
+ ${cas.version}
+
+
+ org.apereo.cas
+ cas-server-core-audit
+ ${cas.version}
+ true
+
+
+ org.apereo.cas
+ cas-server-core
+ ${cas.version}
+
+
+
\ No newline at end of file
diff --git a/sso-support/sso-support-single-login/src/main/java/com/carl/sso/support/single/config/SingleLogoutTriggerConfiguration.java b/sso-support/sso-support-single-login/src/main/java/com/carl/sso/support/single/config/SingleLogoutTriggerConfiguration.java
new file mode 100644
index 0000000..27c6a61
--- /dev/null
+++ b/sso-support/sso-support-single-login/src/main/java/com/carl/sso/support/single/config/SingleLogoutTriggerConfiguration.java
@@ -0,0 +1,46 @@
+/*
+ * 版权所有.(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;
+ }
+}
diff --git a/sso-support/sso-support-single-login/src/main/java/com/carl/sso/support/single/listener/TGTCreateEventListener.java b/sso-support/sso-support-single-login/src/main/java/com/carl/sso/support/single/listener/TGTCreateEventListener.java
new file mode 100644
index 0000000..80169fa
--- /dev/null
+++ b/sso-support/sso-support-single-login/src/main/java/com/carl/sso/support/single/listener/TGTCreateEventListener.java
@@ -0,0 +1,46 @@
+/*
+ * 版权所有.(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 authIds = service.obtain(clientName, id);
+ if (authIds != null) {
+ //循环触发登出
+ authIds.forEach(authId -> logoutService.triggerLogout(authId, tgt));
+ }
+ }
+}
diff --git a/sso-support/sso-support-single-login/src/main/java/com/carl/sso/support/single/service/IUserIdObtainService.java b/sso-support/sso-support-single-login/src/main/java/com/carl/sso/support/single/service/IUserIdObtainService.java
new file mode 100644
index 0000000..b6cea02
--- /dev/null
+++ b/sso-support/sso-support-single-login/src/main/java/com/carl/sso/support/single/service/IUserIdObtainService.java
@@ -0,0 +1,24 @@
+/*
+ * 版权所有.(c)2008-2017. 卡尔科技工作室
+ */
+package com.carl.sso.support.single.service;
+
+import java.util.List;
+
+/**
+ * 用户认证识别器
+ *
+ * @author Carl
+ * @version 创建时间:2017/11/29
+ */
+public interface IUserIdObtainService {
+
+ /**
+ * 通过登录方式查询其他的id
+ *
+ * @param clientName 登录方式
+ * @param id 用户id
+ * @return 所有用户id
+ */
+ List obtain(String clientName, String id);
+}
diff --git a/sso-support/sso-support-single-login/src/main/java/com/carl/sso/support/single/service/TriggerLogoutService.java b/sso-support/sso-support-single-login/src/main/java/com/carl/sso/support/single/service/TriggerLogoutService.java
new file mode 100644
index 0000000..267b104
--- /dev/null
+++ b/sso-support/sso-support-single-login/src/main/java/com/carl/sso/support/single/service/TriggerLogoutService.java
@@ -0,0 +1,62 @@
+/*
+ * 版权所有.(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 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());
+ }
+ }
+}
diff --git a/sso-support/sso-support-single-login/src/main/java/com/carl/sso/support/single/service/UserIdObtainServiceImpl.java b/sso-support/sso-support-single-login/src/main/java/com/carl/sso/support/single/service/UserIdObtainServiceImpl.java
new file mode 100644
index 0000000..4079c96
--- /dev/null
+++ b/sso-support/sso-support-single-login/src/main/java/com/carl/sso/support/single/service/UserIdObtainServiceImpl.java
@@ -0,0 +1,30 @@
+/*
+ * 版权所有.(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 obtain(String clientName, String id) {
+ //由于这里目前只做测试所以只返回当前的id,在正常的情况逻辑应该如下
+
+ //根据校验client以及登录的id找到其他同一个用户的所有校验id返回,如通过邮箱登录的id,通过github登录的id等等
+ List ids = new ArrayList<>();
+ ids.add(id);
+ return ids;
+ }
+}
diff --git a/sso-support/sso-support-single-login/src/main/resources/META-INF/spring.factories b/sso-support/sso-support-single-login/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000..77948a3
--- /dev/null
+++ b/sso-support/sso-support-single-login/src/main/resources/META-INF/spring.factories
@@ -0,0 +1 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.carl.sso.support.single.config.SingleLogoutTriggerConfiguration
\ No newline at end of file