Spring security 4防止并发登录无法正常工作

时间:2017-06-19 22:22:18

标签: java spring spring-boot spring-security

我正在尝试使用spring-security4中的自定义User和UserDetails进行基于表单的用户登录。我希望阻止来自同一用户的并发登录(因此用户只能登录1台设备)。

但是,如果我使用两种不同的浏览器,我可以使用同一个用户帐户登录两个浏览器(这是不可取的)。

我发现了一些类似的问题和解决方案,但不幸的是,解决方案在我的案例中似乎没有用。

请注意我使用的是spring-boot,因此我没有web.xml

我制作了一个如下所示的配置文件:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity( securedEnabled = true )
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    public SecurityConfig(UserDetailsService userServ) {
        userService = userServ;
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

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

    @Autowired
    public void configureAuth(AuthenticationManagerBuilder auth) throws Exception{
        auth
            .userDetailsService(userService)
            .passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()

            // Allow anonymous access to the static resources
            .antMatchers("/css/**").permitAll()
            .antMatchers("/font-awesome/**").permitAll()
            .antMatchers("/fonts/**").permitAll()
            .antMatchers("/img/**").permitAll()
            .antMatchers("/js/**").permitAll()

            // Need to be account admin in your business to control other accounts
            .antMatchers("/app/mybusiness/users").hasRole("ACCOUNT_ADMIN")

            .anyRequest().authenticated()
            .and()
            .formLogin()
            .loginPage("/auth/login")
            .usernameParameter("email")
            .permitAll()
            .and()
            .logout()
            .logoutSuccessUrl("/auth/login?logout")
            .and()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
            .sessionFixation()
            .migrateSession()
            .maximumSessions(1)
            .maxSessionsPreventsLogin(true);

    }

    private UserDetailsService userService;
}

提前致谢

修改

正如你们中的一些人指出我忘记添加.maxSessionsPreventsLogin(true);在我的代码中。我将此添加到我的代码中,但遗憾的是没有任何变化,我仍然可以使用两个不同的浏览器(Safari和Firefox)使用相同的帐户登录。

我被要求在启用DEBUG的情况下添加spring安全日志。

我首先与Safari连接并登录到该应用程序。登录spring安全日志后如下所示:

********************************************************************
**********        Security debugging is enabled.       *************
**********    This may include sensitive information.  *************
**********      Do not use in a production system!     *************
********************************************************************
2017-06-20 20:19:57.061  INFO 810 --- [  restartedMain]
o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
2017-06-20 20:19:57.133  INFO 810 --- [  restartedMain] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2017-06-20 20:19:57.199  INFO 810 --- [  restartedMain] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2017-06-20 20:19:57.205  INFO 810 --- [  restartedMain] com.processbyte.PmsApplication           : Started PmsApplication in 19.414 seconds (JVM running for 19.979)
2017-06-20 20:20:01.585  INFO 810 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2017-06-20 20:20:01.585  INFO 810 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2017-06-20 20:20:01.600  INFO 810 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 15 ms
2017-06-20 20:20:01.609  INFO 810 --- [nio-8080-exec-1] Spring Security Debugger                 :
************************************************************
Request received for GET '/app/myaccount':
org.apache.catalina.connector.RequestFacade@e90e20
servletPath:/app/myaccount
pathInfo:null

headers: 
host: localhost:8080
accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
upgrade-insecure-requests: 1
cookie: JSESSIONID=BC2B8F18111AB83F76BE3015A75B0F22
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5)     AppleWebKit/603.2.4 (KHTML, like Gecko) Version/10.1.1 Safari/603.2.4
accept-language: en-us
accept-encoding: gzip, deflate
connection: keep-alive


Security filter chain: [
    WebAsyncManagerIntegrationFilter
    SecurityContextPersistenceFilter
    HeaderWriterFilter
    CsrfFilter
    LogoutFilter
    UsernamePasswordAuthenticationFilter
    ConcurrentSessionFilter
    RequestCacheAwareFilter
    SecurityContextHolderAwareRequestFilter
    AnonymousAuthenticationFilter
    SessionManagementFilter
    ExceptionTranslationFilter
    FilterSecurityInterceptor
]


************************************************************


2017-06-20 20:20:01.639  INFO 810 --- [nio-8080-exec-2] Spring Security    Debugger                 : 

************************************************************

Request received for GET '/auth/login':

org.apache.catalina.connector.RequestFacade@e90e20

servletPath:/auth/login
pathInfo:null
headers: 
host: localhost:8080
accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
upgrade-insecure-requests: 1
cookie: JSESSIONID=BC2B8F18111AB83F76BE3015A75B0F22
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5)     AppleWebKit/603.2.4 (KHTML, like Gecko) Version/10.1.1 Safari/603.2.4
accept-language: en-us
accept-encoding: gzip, deflate
connection: keep-alive


Security filter chain: [
    WebAsyncManagerIntegrationFilter
    SecurityContextPersistenceFilter
    HeaderWriterFilter
    CsrfFilter
    LogoutFilter
    UsernamePasswordAuthenticationFilter
    ConcurrentSessionFilter
    RequestCacheAwareFilter
    SecurityContextHolderAwareRequestFilter
    AnonymousAuthenticationFilter
    SessionManagementFilter
    ExceptionTranslationFilter
    FilterSecurityInterceptor
]


************************************************************


2017-06-20 20:21:18.862  INFO 810 --- [nio-8080-exec-5] Spring Security Debugger                 : 

************************************************************

Request received for POST '/auth/login':

org.apache.catalina.connector.RequestFacade@740e4169

servletPath:/auth/login
pathInfo:null
headers: 
host: localhost:8080
accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
accept-encoding: gzip, deflate
accept-language: en-us
content-type: application/x-www-form-urlencoded
origin: http://localhost:8080
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/603.2.4 (KHTML, like Gecko) Version/10.1.1 Safari/603.2.4
connection: keep-alive
upgrade-insecure-requests: 1
referer: http://localhost:8080/auth/login
content-length: 90
cookie: JSESSIONID=BC2B8F18111AB83F76BE3015A75B0F22


Security filter chain: [
    WebAsyncManagerIntegrationFilter
    SecurityContextPersistenceFilter
    HeaderWriterFilter
    CsrfFilter
    LogoutFilter
    UsernamePasswordAuthenticationFilter
    ConcurrentSessionFilter
    RequestCacheAwareFilter
    SecurityContextHolderAwareRequestFilter
    AnonymousAuthenticationFilter
    SessionManagementFilter
    ExceptionTranslationFilter
    FilterSecurityInterceptor
]


************************************************************


2017-06-20 20:21:18.891  INFO 810 --- [nio-8080-exec-5]  org.mongodb.driver.connection            : Opened connection  [connectionId{localValue:2, serverValue:7}] to localhost:27017
2017-06-20 20:21:19.103  INFO 810 --- [nio-8080-exec-3] Spring Security  Debugger                 : 

************************************************************
Request received for GET '/app/myaccount':

org.apache.catalina.connector.RequestFacade@740e4169

servletPath:/app/myaccount
pathInfo:null
headers: 
host: localhost:8080
origin: http://localhost:8080
cookie: JSESSIONID=846CA88C32032533628A31839F083F6D
connection: keep-alive
upgrade-insecure-requests: 1
accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
accept-language: en-us
referer: http://localhost:8080/auth/login
accept-encoding: gzip, deflate
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5)   AppleWebKit/603.2.4 (KHTML, like Gecko) Version/10.1.1 Safari/603.2.4


Security filter chain: [
    WebAsyncManagerIntegrationFilter
    SecurityContextPersistenceFilter
    HeaderWriterFilter
    CsrfFilter
    LogoutFilter
    UsernamePasswordAuthenticationFilter
    ConcurrentSessionFilter
    RequestCacheAwareFilter
    SecurityContextHolderAwareRequestFilter
    AnonymousAuthenticationFilter
    SessionManagementFilter
    ExceptionTranslationFilter
    FilterSecurityInterceptor
]

此后我用Firefox连接到相同的URL并使用相同的用户帐户登录,弹簧安全日志附加了这个:

2017-06-20 20:23:17.381  INFO 810 --- [nio-8080-exec-4] Spring Security Debugger                 :
************************************************************

Request received for GET '/app/myaccount':

org.apache.catalina.connector.RequestFacade@60b5387b

servletPath:/app/myaccount
pathInfo:null
headers: 
host: localhost:8080
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:53.0)     Gecko/20100101 Firefox/53.0
accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
accept-language: en-US,en;q=0.5
accept-encoding: gzip, deflate
connection: keep-alive
upgrade-insecure-requests: 1


Security filter chain: [
    WebAsyncManagerIntegrationFilter
    SecurityContextPersistenceFilter
    HeaderWriterFilter
    CsrfFilter
    LogoutFilter
    UsernamePasswordAuthenticationFilter
    ConcurrentSessionFilter
    RequestCacheAwareFilter
    SecurityContextHolderAwareRequestFilter
    AnonymousAuthenticationFilter
    SessionManagementFilter
    ExceptionTranslationFilter
    FilterSecurityInterceptor
]


************************************************************

2017-06-20 20:23:17.388  INFO 810 --- [nio-8080-exec-4] Spring Security Debugger                 : 

************************************************************

New HTTP session created: 62F06B8BD67C21B90E7FE199C740767B

Call stack: 

at org.springframework.security.web.debug.Logger.info(Logger.java:44)
at org.springframework.security.web.debug.DebugRequestWrapper.getSession(DebugFilter.java:166)
at javax.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:240)
at javax.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:240)
at javax.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:240)
at org.springframework.security.web.savedrequest.HttpSessionRequestCache.saveRequest(HttpSessionRequestCache.java:59)
at org.springframework.security.web.access.ExceptionTranslationFilter.sendStartAuthentication(ExceptionTranslationFilter.java:201)
at org.springframework.security.web.access.ExceptionTranslationFilter.handleSpringSecurityException(ExceptionTranslationFilter.java:177)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:133)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:170)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.session.ConcurrentSessionFilter.doFilter(ConcurrentSessionFilter.java:155)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:200)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:214)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:177)
at org.springframework.security.web.debug.DebugFilter.invokeWithWrappedRequest(DebugFilter.java:90)
at org.springframework.security.web.debug.DebugFilter.doFilter(DebugFilter.java:77)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:105)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:478)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:80)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:799)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1455)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)


************************************************************


2017-06-20 20:23:17.408  INFO 810 --- [nio-8080-exec-5] Spring Security Debugger                 : 

************************************************************

Request received for GET '/auth/login':

org.apache.catalina.connector.RequestFacade@60b5387b

servletPath:/auth/login
pathInfo:null
headers: 
host: localhost:8080
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:53.0) Gecko/20100101 Firefox/53.0
accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
accept-language: en-US,en;q=0.5
accept-encoding: gzip, deflate
cookie: JSESSIONID=62F06B8BD67C21B90E7FE199C740767B
connection: keep-alive
upgrade-insecure-requests: 1


Security filter chain: [
    WebAsyncManagerIntegrationFilter
    SecurityContextPersistenceFilter
    HeaderWriterFilter
    CsrfFilter
    LogoutFilter
    UsernamePasswordAuthenticationFilter
    ConcurrentSessionFilter
    RequestCacheAwareFilter
    SecurityContextHolderAwareRequestFilter
    AnonymousAuthenticationFilter
    SessionManagementFilter
    ExceptionTranslationFilter
    FilterSecurityInterceptor
]


************************************************************


2017-06-20 20:23:23.349  INFO 810 --- [nio-8080-exec-5] Spring Security Debugger                 : 

************************************************************

Request received for POST '/auth/login':

org.apache.catalina.connector.RequestFacade@196c2c

servletPath:/auth/login
pathInfo:null
headers: 
host: localhost:8080
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:53.0)    Gecko/20100101 Firefox/53.0
accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
accept-language: en-US,en;q=0.5
accept-encoding: gzip, deflate
referer: http://localhost:8080/auth/login
content-type: application/x-www-form-urlencoded
content-length: 90
cookie: JSESSIONID=62F06B8BD67C21B90E7FE199C740767B
connection: keep-alive
upgrade-insecure-requests: 1


Security filter chain: [
    WebAsyncManagerIntegrationFilter
    SecurityContextPersistenceFilter
    HeaderWriterFilter
    CsrfFilter
    LogoutFilter
    UsernamePasswordAuthenticationFilter
    ConcurrentSessionFilter
    RequestCacheAwareFilter
    SecurityContextHolderAwareRequestFilter
    AnonymousAuthenticationFilter
    SessionManagementFilter
    ExceptionTranslationFilter
    FilterSecurityInterceptor
]


************************************************************


2017-06-20 20:23:23.504  INFO 810 --- [nio-8080-exec-7] Spring Security Debugger                 : 

************************************************************

Request received for GET '/app/myaccount':

org.apache.catalina.connector.RequestFacade@196c2c

servletPath:/app/myaccount
pathInfo:null
headers: 
host: localhost:8080
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:53.0)    Gecko/20100101 Firefox/53.0
accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
accept-language: en-US,en;q=0.5
accept-encoding: gzip, deflate
referer: http://localhost:8080/auth/login
cookie: JSESSIONID=8D8FE6BC6BEDCAB6529E30E2775817E6
connection: keep-alive
upgrade-insecure-requests: 1


Security filter chain: [
    WebAsyncManagerIntegrationFilter
    SecurityContextPersistenceFilter
    HeaderWriterFilter
    CsrfFilter
    LogoutFilter
    UsernamePasswordAuthenticationFilter
    ConcurrentSessionFilter
    RequestCacheAwareFilter
    SecurityContextHolderAwareRequestFilter
    AnonymousAuthenticationFilter
    SessionManagementFilter
    ExceptionTranslationFilter
    FilterSecurityInterceptor
]

2 个答案:

答案 0 :(得分:3)

感谢大家的回复,因为事实证明我犯了几个错误。 我根据收到的反馈编辑了我的问题中的代码。 但是,为了解决这个问题,还有一件事需要做。

由于我使用UserDetails的自定义实现,我不得不在UserDetailsImpl类中覆盖equals和hashCode。

@Override
public boolean equals(Object otherUser) {
    if(otherUser == null) return false;
    else if (!(otherUser instanceof UserDetails)) return false;
    else return (otherUser.hashCode() == hashCode());
}

@Override
public int hashCode() {
    return user.getEmail().hashCode() ;
}

click here for the source

答案 1 :(得分:0)

将以下内容添加到最后一行:

.sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(true);

YMMV,我没有对此进行测试,只是查看了文档