我正在尝试使用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
]
答案 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() ;
}
答案 1 :(得分:0)
将以下内容添加到最后一行:
.sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(true);
YMMV,我没有对此进行测试,只是查看了文档