【问题标题】:how to log a user out programmatically using spring security如何使用spring security以编程方式注销用户
【发布时间】:2014-04-17 17:26:25
【问题描述】:

我正在使用 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"/>

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

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

当我在同一台计算机上对其进行测试时,此代码“有点”有效。假设我们有一个用户 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 就会知道,“嘿,这个人的会话无效或过期,将他发送到登录页面“?

感谢任何帮助。谢谢。

【问题讨论】:

    标签: java spring spring-mvc spring-security


    【解决方案1】:

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

    尝试删除该行:

    sessionRegistry.removeSessionInformation(info.getSessionId());
    

    【讨论】:

    • 谢谢,删除该行肯定会消除该副作用。但是,当我刷新受弹簧保护的页面时,我收到此消息,“此会话已过期(可能是由于以同一用户身份尝试多个并发登录)。”重定向是正确的,它重定向到 /home,但它不是显示 /home 的内容,而是显示该消息,然后我必须刷新该消息才能显示真正的内容。这个消息是从哪里来的?看起来不像是 JEE 容器。以及如何自定义它?
    • 好的,我的后续行动已经有了答案。您可以在并发控制中控制过期网址。
    • 对。 namespace appendix 是查找配置属性的好地方。此外,它们会显示在一个不错的 XML 编辑器中。
    • 对此的旁注:我正在使用自定义会话注册表,它在启动时将 tomcat 会话从磁盘加载到我的会话注册表中。删除此行使得会话注册表在会话过期时不会删除会话,直到下一个请求。如果服务器在下一个请求之前重新启动,则会话由 tomcat 持久化,在启动时加载,然后在下一个请求时复活。 我通过创建一个事件侦听器来解决此问题,该侦听器侦听 SessionDestroyedEvent 何时发布并在那里读取此行。
    【解决方案2】:

    您只需要修改equals()hashCode()。您的代码将在稍后运行。这可能会有所帮助:http://blog.trifork.com/2014/02/28/session-timeout-and-concurrent-session-control-with-spring-security-and-spring-mvc/

    【讨论】:

      猜你喜欢
      • 2012-03-22
      • 2012-06-15
      • 2019-11-01
      • 2014-10-15
      • 2011-03-07
      • 2011-12-15
      • 2011-11-28
      • 2017-01-15
      • 2013-04-29
      相关资源
      最近更新 更多