如何使用Spring Security以编程方式注销用户 [英] how to log a user out programmatically using spring security

查看:255
本文介绍了如何使用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());

i am using spring security v3.1.4. what i want to achieve is to have an admin be able to log out a regular user (invalidate his session). a user can only log in once at any given time, but if he forgets to log out, then when he attempts to log in from another location, he won't be able to log in. so he'll put in a ticket to the admin, and the admin will invalidate all his previously logged in sessions (hopefully there's just one).

in my web.xml i have the following defined.

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

in my spring security xml i have the following defined.

<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"/>

i then have a rest-like controller to perform the logging out.

@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";
 }
}

this code "kinda" works when i test it from the same computer. let's say we have a user, USER_A, and an administrator, ADMIN_A.

  • USER_A uses chrome to log into the APP.
  • USER_A uses firefox to log into the APP. he is denied because a user can only have 1 login session at a time.
  • ADMIN_A goes in, and calls the rest-like service (code above) to "kick" out all of USER_A's session.
  • USER_A can now use firefox to log into the APP.

however, USER_A is now logged in twice, once in chrome and once in firefox.

  • refreshing the (spring security protected) pages for USER_A in chrome (first log in) doesn't force him to be redirected (to the login page).
  • refreshing the protected pages for USER_A in firefox (second login) also doesn't force him to be redirected.

any idea on approaches on how to completely invalidate/destroy USER_A's first/previous login sessions such that if he tries to access protected pages spring security will know, "hey this guy's session is invalid or expired, send him to the login page" ?

any help is appreciated. thanks.

解决方案

It looks like you've almost got it, but I think the problem is that you are removing the information prematurely from the SessionRegistry. The ConcurrentSessionFilter performs a check on the current session when a user makes a request, and at this point, it logs out an expired session and invalidates it. Since you have already removed the information for that session, it won't find it and will do nothing.

Try removing the line:

sessionRegistry.removeSessionInformation(info.getSessionId());

这篇关于如何使用Spring Security以编程方式注销用户的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆