草庐IT

java - Spring Security 通过并发登录尝试锁定用户

coder 2024-03-20 原文

我是安全方面的新手,遇到了一个导致用户帐户被锁定的问题,只有重新启动应用程序才能修复它。

我有一个带有 spring security (4.0.2.RELEASE) 应用程序的 spring boot (1.3.0.BUILD-SNAPSHOT),我试图控制并发 session 策略,以便用户只能进行一次登录。它正确地检测到来自另一个浏览器的后续登录尝试并阻止了这种情况。但是,我注意到一些我似乎无法追踪的奇怪行为:

  • 一个用户可以在同一个浏览器中验证两个选项卡。我无法使用三个选项卡登录,但有两个可以。注销一个似乎注销两个。我看到 cookie 值相同,所以我猜他们正在共享一个 session :

tab 1 JSESSIONID: DA7C3EF29D55297183AF5A9BEBEF191F & 941135CEBFA92C3912ADDC1DE41CFE9A

tab 2 JSESSIONID: DA7C3EF29D55297183AF5A9BEBEF191F & 48C17A19B2560EAB8EC3FDF51B179AAE

第二次登录尝试显示以下日志消息,这似乎表明第二次登录尝试(我通过单步执行 Spring-Security 源验证了这一点:

o.s.s.w.a.i.FilterSecurityInterceptor    : Secure object: FilterInvocation: URL: /loginPage; Attributes: [permitAll]
 o.s.s.w.a.i.FilterSecurityInterceptor    : Previously Authenticated: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@754041c8: Principal: User [username=xxx@xxx.xx, password=<somevalue> ]; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@43458: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 4708D404F64EE758662B2B308F36FFAC; Granted Authorities: Owner
 o.s.s.access.vote.AffirmativeBased       : Voter: org.springframework.security.web.access.expression.WebExpressionVoter@17527bbe, returned: 1
 o.s.s.w.a.i.FilterSecurityInterceptor    : Authorization successful
 o.s.s.w.a.i.FilterSecurityInterceptor    : RunAsManager did not change Authentication object
 o.s.security.web.FilterChainProxy        : /loginPage reached end of additional filter chain; proceeding with original chain
 org.apache.velocity                      : ResourceManager : unable to find resource 'loginPage.vm' in any resource loader.
 o.s.s.w.a.ExceptionTranslationFilter     : Chain processed normally
 s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
  • 当我使用两个选项卡登录然后注销时,用户帐户被锁定并且需要重新启动服务器。控制台没有错误,数据库中的用户记录没有改变。

这是我的安全配置:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

      @Autowired
      private CustomUserDetailsService customUserDetailsService;

      @Autowired
      private SessionRegistry sessionRegistry;

      @Autowired
      ServletContext servletContext;

      @Autowired
      private CustomLogoutHandler logoutHandler;

      @Autowired
      private MessageSource messageSource;


/**
 * Sets security configurations for the authentication manager
 */
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
                throws Exception {
    auth
                    .userDetailsService(customUserDetailsService)
                    .passwordEncoder(passwordEncoder());
    return;
}  
  protected void configure(HttpSecurity http) throws Exception {
        http
           .formLogin()
           .loginPage("/loginPage")
           .permitAll()
           .loginProcessingUrl("/login")
           .defaultSuccessUrl("/?tab=success")
           .and()
              .logout().addLogoutHandler(logoutHandler).logoutRequestMatcher( new AntPathRequestMatcher("/logout"))
              .deleteCookies("JSESSIONID")
               .invalidateHttpSession(true).permitAll().and().csrf()
           .and()  
              .sessionManagement().sessionAuthenticationStrategy(             concurrentSessionControlAuthenticationStrategy).sessionFixation().changeSessionId().maximumSessions(1)
              .maxSessionsPreventsLogin( true).expiredUrl("/login?expired" ).sessionRegistry(sessionRegistry )
          .and()
           .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
           .invalidSessionUrl("/")
           .and().authorizeRequests().anyRequest().authenticated();
        http.headers().contentTypeOptions();
        http.headers().xssProtection();
        http.headers().cacheControl();
        http.headers().httpStrictTransportSecurity();
        http.headers().frameOptions();
        servletContext.getSessionCookieConfig().setHttpOnly(true);
    }

  @Bean
  public ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlAuthenticationStrategy() {

      ConcurrentSessionControlAuthenticationStrategy strategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry());
      strategy.setExceptionIfMaximumExceeded(true);
      strategy.setMessageSource(messageSource);

      return strategy;
  }

  // Work around https://jira.spring.io/browse/SEC-2855
  @Bean
  public SessionRegistry sessionRegistry() {
      SessionRegistry sessionRegistry = new SessionRegistryImpl();
      return sessionRegistry;
  }
}

我还有以下方法来处理检查用户:

@Entity
@Table(name = "USERS")
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
                  property = "username")
public class User implements UserDetails {
...
 @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((username == null) ? 0 : username.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        User other = (User) obj;
        if (username == null) {
            if (other.username != null)
                return false;
        } else if (!username.equals(other.username))
            return false;
        return true;
    }
}

如何防止帐户像那样被锁定,或者至少如何以编程方式解锁它们?

编辑 1/5/16 我将以下内容添加到我的 WebSecurityConfig 中:

 @Bean
    public static ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
        return new ServletListenerRegistrationBean<HttpSessionEventPublisher>(new HttpSessionEventPublisher());
    }

并删除:

servletContext.addListener(httpSessionEventPublisher())

但当我在同一个浏览器上登录两次时,我仍然看到这种行为 - 注销会锁定帐户,直到我重新启动。

最佳答案

事实证明,SessionRegistryImpl 并未从 session 中删除用户。第一个选项卡注销实际上从未调用过服务器,因此第二个调用确实删除了一个 sessionid,在主体中留下了一个。

我不得不做一些改变:

@Component
public class CustomLogoutHandler implements LogoutHandler {
    @Autowired
    private SessionRegistry sessionRegistry;

    @Override
    public void logout(HttpServletRequest httpServletRequest, httpServletResponse httpServletResponse, Authentication authentication) {
...
httpServletRequest.getSession().invalidate();
httpServletResponse.setStatus(HttpServletResponse.SC_OK);
//redirect to login
httpServletResponse.sendRedirect("/");
    List<SessionInformation> userSessions = sessionRegistry.getAllSessions(user, true);

    for (SessionInformation session: userSessions) {
        sessionRegistry.removeSessionInformation(session.getSessionId());
    }
}

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Bean
    public SessionRegistry sessionRegistry() {
        if (sessionRegistry == null) {
            sessionRegistry = new SessionRegistryImpl();
        }
        return sessionRegistry;
    }

    @Bean
    public static ServletListenerRegistrationBean httpSessionEventPublisher() {
        return new ServletListenerRegistrationBean(new HttpSessionEventPublisher());
    }

    @Bean
    public ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlAuthenticationStrategy() {

        ConcurrentSessionControlAuthenticationStrategy strategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry());
        strategy.setExceptionIfMaximumExceeded(true);
        strategy.setMessageSource(messageSource);

        return strategy;
    }
}

关于java - Spring Security 通过并发登录尝试锁定用户,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34401172/

有关java - Spring Security 通过并发登录尝试锁定用户的更多相关文章

  1. ruby - ECONNRESET (Whois::ConnectionError) - 尝试在 Ruby 中查询 Whois 时出错 - 2

    我正在用Ruby编写一个简单的程序来检查域列表是否被占用。基本上它循环遍历列表,并使用以下函数进行检查。require'rubygems'require'whois'defcheck_domain(domain)c=Whois::Client.newc.query("google.com").available?end程序不断出错(即使我在google.com中进行硬编码),并打印以下消息。鉴于该程序非常简单,我已经没有什么想法了-有什么建议吗?/Library/Ruby/Gems/1.8/gems/whois-2.0.2/lib/whois/server/adapters/base.

  2. ruby - 通过 rvm 升级 ruby​​gems 的问题 - 2

    尝试通过RVM将RubyGems升级到版本1.8.10并出现此错误:$rvmrubygemslatestRemovingoldRubygemsfiles...Installingrubygems-1.8.10forruby-1.9.2-p180...ERROR:Errorrunning'GEM_PATH="/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/ruby-1.9.2-p180@global:/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/rub

  3. ruby - 通过 erb 模板输出 ruby​​ 数组 - 2

    我正在使用puppet为ruby​​程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby​​不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这

  4. ruby - 通过 ruby​​ 进程共享变量 - 2

    我正在编写一个gem,我必须在其中fork两个启动两个webrick服务器的进程。我想通过基类的类方法启动这个服务器,因为应该只有这两个服务器在运行,而不是多个。在运行时,我想调用这两个服务器上的一些方法来更改变量。我的问题是,我无法通过基类的类方法访问fork的实例变量。此外,我不能在我的基类中使用线程,因为在幕后我正在使用另一个不是线程安全的库。所以我必须将每个服务器派生到它自己的进程。我用类变量试过了,比如@@server。但是当我试图通过基类访问这个变量时,它是nil。我读到在Ruby中不可能在分支之间共享类变量,对吗?那么,还有其他解决办法吗?我考虑过使用单例,但我不确定这是

  5. ruby - 通过 RVM (OSX Mountain Lion) 安装 Ruby 2.0.0-p247 时遇到问题 - 2

    我的最终目标是安装当前版本的RubyonRails。我在OSXMountainLion上运行。到目前为止,这是我的过程:已安装的RVM$\curl-Lhttps://get.rvm.io|bash-sstable检查已知(我假设已批准)安装$rvmlistknown我看到当前的稳定版本可用[ruby-]2.0.0[-p247]输入命令安装$rvminstall2.0.0-p247注意:我也试过这些安装命令$rvminstallruby-2.0.0-p247$rvminstallruby=2.0.0-p247我很快就无处可去了。结果:$rvminstall2.0.0-p247Search

  6. ruby-on-rails - 使用 rails 4 设计而不更新用户 - 2

    我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它​​不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数

  7. java - 等价于 Java 中的 Ruby Hash - 2

    我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

  8. ruby-on-rails - Enumerator.new 如何处理已通过的 block ? - 2

    我在理解Enumerator.new方法的工作原理时遇到了一些困难。假设文档中的示例:fib=Enumerator.newdo|y|a=b=1loopdoy[1,1,2,3,5,8,13,21,34,55]循环中断条件在哪里,它如何知道循环应该迭代多少次(因为它没有任何明确的中断条件并且看起来像无限循环)? 最佳答案 Enumerator使用Fibers在内部。您的示例等效于:require'fiber'fiber=Fiber.newdoa=b=1loopdoFiber.yieldaa,b=b,a+bendend10.times.m

  9. ruby-on-rails - Rails - 乐观锁定总是触发 StaleObjectError 异常 - 2

    我正在学习Rails,并阅读了关于乐观锁的内容。我已将类型为integer的lock_version列添加到我的articles表中。但现在每当我第一次尝试更新记录时,我都会收到StaleObjectError异常。这是我的迁移:classAddLockVersionToArticle当我尝试通过Rails控制台更新文章时:article=Article.first=>#我这样做:article.title="newtitle"article.save我明白了:(0.3ms)begintransaction(0.3ms)UPDATE"articles"SET"title"='dwdwd

  10. ruby-on-rails - 每次我尝试部署时,我都会得到 - (gcloud.preview.app.deploy) 错误响应 : [4] DEADLINE_EXCEEDED - 2

    我是Google云的新手,我正在尝试对其进行首次部署。我的第一个部署是RubyonRails项目。我基本上是在关注thisguideinthegoogleclouddocumentation.唯一的区别是我使用的是我自己的项目,而不是他们提供的“helloworld”项目。这是我的app.yaml文件runtime:customvm:trueentrypoint:bundleexecrackup-p8080-Eproductionconfig.ruresources:cpu:0.5memory_gb:1.3disk_size_gb:10当我转到我的项目目录并运行gcloudprevie

随机推荐