如何使用Spring Security以编程方式注销用户


问题内容

我正在使用Spring Security
v3.1.4。我要实现的是让管理员能够注销普通用户(使他的会话无效)。用户在任何给定时间只能登录一次,但是如果他忘记注销,那么当他尝试从其他位置登录时,将无法登录。管理员,管理员将使他以前登录的所有会话均无效(希望只有一个)。

在我的web.xml中,我定义了以下内容。

<listener>
 <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>

在我的spring security xml中,我定义了以下内容。

<session-management invalid-session-url="/home">
 <concurrency-control max-sessions="1" error-if-maximum-exceeded="true" session-registry-ref="sessionRegistry"/>
</session-management>
<beans:bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl"/>

然后,我有一个类似Rest的控制器来执行注销。

@Controller
@RequestMapping("/api/admin")
public class RestAdminController {
 static final Set<SimpleGrantedAuthority> AUTHS = new HashSet<>();
 static {
  AUTHS.add(new SimpleGrantedAuthority("ROLE_USER"));
 }

 @Autowired
 private SessionRegistry sessionRegistry;

 @RequestMapping("/user/logout");
 public @ResponseBody String logout(@RequestBody Account account) {
  User user = new User(account.getUsername(), "", AUTHS);
  List<SessionInformation> infos = sessionRegistry.getAllSessions(u, false);

  for(SessionInformation info : infos) {
   info.expireNow(); //expire the session
   sessionRegistry.removeSessionInformation(info.getSessionId()); //remove session
  }

  return "ok";
 }
}

当我从同一台计算机对其进行测试时,此代码“ kinda”有效。假设我们有一个用户USER_A和一个管理员ADMIN_A。

  • USER_A使用chrome登录到APP。
  • USER_A使用firefox登录到APP。他被拒绝,因为用户一次只能进行1次登录会话。
  • ADMIN_A进入,并调用类似rest的服务(上面的代码)以“踢出”所有USER_A的会话。
  • USER_A现在可以使用firefox登录到APP。

但是,USER_A现在登录了两次,一次是在chrome中,一次是在Firefox中。

  • 在Chrome(首次登录)中刷新USER_A的(受弹簧安全性保护的)页面(首次登录)不会强制将其重定向到登录页面。
  • 在Firefox(第二次登录)中刷新USER_A的受保护页面也不会强制他被重定向。

关于如何完全使USER_A的第一个/先前的登录会话无效/销毁的方法有什么想法,以便如果他尝试访问受保护的页面,spring
security将知道:“嘿,此人的会话无效或已过期,请将其发送到登录页面”?

任何帮助表示赞赏。谢谢。


问题答案:

看起来您差不多已经掌握了,但是我认为问题是您正在过早地从中删除信息SessionRegistry。在ConcurrentSessionFilter执行对当用户发出请求的当前会话的检查,并且在这一点上,注销过期会话并使其无效。由于您已经删除了该会话的信息,因此找不到该信息,并且不会执行任何操作。

尝试删除该行:

sessionRegistry.removeSessionInformation(info.getSessionId());