key属性的作用说实话不是很理解,token-validity-seconds表示remember-me的秒数,这里大概是7天吧。注意:remember-me本有两个版本,一个是inmemory,另外一个基于数据库。默认为inmemory实现,表示将凭证存储在内存中。重启服务器后凭证会消失,据说在集群中也不可用。但添加data-source-ref标签后,则自动激活数据库实现,表结构为:
(如果要自定义表结构,则需要实现***忘了)
之后实现了session的并发控制和防固化攻击,配置如下:
表示像QQ那样有人上线会把你顶下去。顶下去之后你再进行任何操作都会被重定向到/login?error=expired。
但问题来了,当在两个浏览器中登陆同一个账户时,发现第一个登陆的果然被顶下去了,第二个登陆的会话有效,但是如果此时重启服务器,会发现第二个登陆的用户的remember-me失效了,重现了几次之后发现确实是这个问题。继续看debug日志。
22:28:49,552 DEBUG FilterChainProxy:324 - / at position 2 of 16 in additional filter chain; firing Filter: 'ConcurrentSessionFilter'
22:28:49,552 DEBUG SecurityContextLogoutHandler:64 - Invalidating session: f9f57cm68yj11q4vcyzedjayw
22:28:49,552 DEBUG HttpSessionEventPublisher:88 - Publishing event: org.springframework.security.web.session.HttpSessionDestroyedEvent[source=org.eclipse.jetty.server.session.HashedSession:f9f57cm68yj11q4vcyzedjayw@1532245675]
22:28:49,553 DEBUG SessionRegistryImpl:167 - Removing session f9f57cm68yj11q4vcyzedjayw from principal's set of registered sessions
22:28:49,553 DEBUG PersistentTokenBasedRememberMeServices:407 - Logout of user guest
22:28:49,553 DEBUG PersistentTokenBasedRememberMeServices:346 - Cancelling cookie
22:28:49,554 DEBUG JdbcTemplate:908 - Executing prepared SQL update
22:28:49,554 DEBUG JdbcTemplate:627 - Executing prepared SQL statement [delete from persistent_logins where username = ?]
22:28:49,554 DEBUG DataSourceUtils:110 - Fetching JDBC Connection from DataSource
22:28:49,560 DEBUG JdbcTemplate:918 - SQL update affected 2 rows
22:28:49,561 DEBUG DataSourceUtils:327 - Returning JDBC Connection to DataSource
22:28:49,562 DEBUG DefaultRedirectStrategy:39 - Redirecting to '/login?error=expired'
22:28:49,562 DEBUG HttpSessionSecurityContextRepository:337 - SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
22:28:49,562 DEBUG SecurityContextPersistenceFilter:105 - SecurityContextHolder now cleared, as request processing completed
22:28:49,566 DEBUG AntPathRequestMatcher:151 - Checking match of request : '/login'; against '/resources/**'
22:28:49,566 DEBUG AntPathRequestMatcher:151 - Checking match of request : '/login'; against '/about'
22:28:49,567 DEBUG FilterChainProxy:324 - /login?error=expired at position 1 of 16 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
22:28:49,567 DEBUG HttpSessionSecurityContextRepository:159 - No HttpSession currently exists
22:28:49,567 DEBUG HttpSessionSecurityContextRepository:101 - No SecurityContext was available from the HttpSession: null. A new one will be created.
22:28:49,567 DEBUG FilterChainProxy:324 - /login?error=expired at position 2 of 16 in additional filter chain; firing Filter: 'ConcurrentSessionFilter'
22:28:49,567 DEBUG FilterChainProxy:324 - /login?error=expired at position 3 of 16 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
22:28:49,567 DEBUG FilterChainProxy:324 - /login?error=expired at position 4 of 16 in additional filter chain; firing Filter: 'HeaderWriterFilter'
22:28:49,568 DEBUG HstsHeaderWriter:128 - Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@12197aee
22:28:49,568 DEBUG FilterChainProxy:324 - /login?error=expired at position 5 of 16 in additional filter chain; firing Filter: 'CsrfFilter'
22:28:49,568 DEBUG FilterChainProxy:324 - /login?error=expired at position 6 of 16 in additional filter chain; firing Filter: 'LogoutFilter'
22:28:49,568 DEBUG AntPathRequestMatcher:131 - Request 'GET /login' doesn't match 'POST /logout
22:28:49,568 DEBUG FilterChainProxy:324 - /login?error=expired at position 7 of 16 in additional filter chain; firing Filter: 'UsernamePasswordAuthenticationFilter'
22:28:49,569 DEBUG AntPathRequestMatcher:131 - Request 'GET /login' doesn't match 'POST /login
22:28:49,569 DEBUG FilterChainProxy:324 - /login?error=expired at position 8 of 16 in additional filter chain; firing Filter: 'BasicAuthenticationFilter'
22:28:49,569 DEBUG FilterChainProxy:324 - /login?error=expired at position 9 of 16 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
22:28:49,569 DEBUG FilterChainProxy:324 - /login?error=expired at position 10 of 16 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
22:28:49,570 DEBUG FilterChainProxy:324 - /login?error=expired at position 11 of 16 in additional filter chain; firing Filter: 'RememberMeAuthenticationFilter'
22:28:49,570 DEBUG FilterChainProxy:324 - /login?error=expired at position 12 of 16 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
22:28:49,570 DEBUG AnonymousAuthenticationFilter:100 - Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken@9055c2bc: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS'
22:28:49,570 DEBUG FilterChainProxy:324 - /login?error=expired at position 13 of 16 in additional filter chain; firing Filter: 'SessionManagementFilter'
22:28:49,570 DEBUG SessionManagementFilter:109 - Requested session ID f9f57cm68yj11q4vcyzedjayw is invalid.
22:28:49,571 DEBUG FilterChainProxy:324 - /login?error=expired at position 14 of 16 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
22:28:49,571 DEBUG FilterChainProxy:324 - /login?error=expired at position 15 of 16 in additional filter chain; firing Filter: 'IPRoleAuthenticationFilter'
22:28:49,571 DEBUG FilterChainProxy:324 - /login?error=expired at position 16 of 16 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
22:28:49,571 DEBUG AntPathRequestMatcher:151 - Checking match of request : '/login'; against '/login'
22:28:49,572 DEBUG FilterSecurityInterceptor:218 - Secure object: FilterInvocation: URL: /login?error=expired; Attributes: [permitAll]
22:28:49,572 DEBUG FilterSecurityInterceptor:347 - Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@9055c2bc: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
22:28:49,572 DEBUG AffirmativeBased:65 - Voter: org.springframework.security.web.access.expression.WebExpressionVoter@7112ce6, returned: 1
22:28:49,572 DEBUG FilterSecurityInterceptor:242 - Authorization successful
22:28:49,572 DEBUG FilterSecurityInterceptor:255 - RunAsManager did not change Authentication object
22:28:49,572 DEBUG FilterChainProxy:309 - /login?error=expired reached end of additional filter chain; proceeding with original chain
22:28:49,573 DEBUG DispatcherServlet:861 - DispatcherServlet with name 'springmvc' processing GET request for [/login]
22:28:49,573 DEBUG RequestMappingHandlerMapping:294 - Looking up handler method for path /login
22:28:49,575 DEBUG RequestMappingHandlerMapping:299 - Returning handler method [public java.lang.String com.bay1ts.controller.BaseController.login(java.lang.String,org.springframework.ui.Model)]
22:28:49,575 DEBUG DefaultListableBeanFactory:248 - Returning cached instance of singleton bean 'baseController'
22:28:49,576 DEBUG DispatcherServlet:947 - Last-Modified value for [/login] is: -1
22:28:49,588 DEBUG DefaultListableBeanFactory:1616 - Invoking afterPropertiesSet() on bean with name 'error_expired'
22:28:49,588 DEBUG DispatcherServlet:1241 - Rendering view [org.springframework.web.servlet.view.JstlView: name 'error_expired'; URL [/WEB-INF/jsps/error_expired.jsp]] in DispatcherServlet with name 'springmvc'
22:28:49,589 DEBUG JstlView:432 - Added model object 'errormsg' of type [java.lang.String] to request in view with name 'error_expired'
22:28:49,589 DEBUG DefaultListableBeanFactory:248 - Returning cached instance of singleton bean 'requestDataValueProcessor'
22:28:49,589 DEBUG JstlView:166 - Forwarding to resource [/WEB-INF/jsps/error_expired.jsp] in InternalResourceView 'error_expired'
22:28:49,726 DEBUG HttpSessionEventPublisher:71 - Publishing event: org.springframework.security.web.session.HttpSessionCreatedEvent[source=org.eclipse.jetty.server.session.HashedSession:12ba07fd1tanb15gzsllvsv78o@218958377]
22:28:49,730 DEBUG HttpSessionSecurityContextRepository:337 - SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
22:28:49,730 DEBUG DispatcherServlet:996 - Successfully completed request
22:28:49,731 DEBUG ExceptionTranslationFilter:116 - Chain processed normally
22:28:49,731 DEBUG SecurityContextPersistenceFilter:105 - SecurityContextHolder now cleared, as request processing completed
重点在这里:
22:28:49,552 DEBUG HttpSessionEventPublisher:88 - Publishing event: org.springframework.security.web.session.HttpSessionDestroyedEvent[source=org.eclipse.jetty.server.session.HashedSession:f9f57cm68yj11q4vcyzedjayw@1532245675]
22:28:49,553 DEBUG SessionRegistryImpl:167 - Removing session f9f57cm68yj11q4vcyzedjayw from principal's set of registered sessions
22:28:49,553 DEBUG PersistentTokenBasedRememberMeServices:407 - Logout of user guest
22:28:49,553 DEBUG PersistentTokenBasedRememberMeServices:346 - Cancelling cookie
22:28:49,554 DEBUG JdbcTemplate:908 - Executing prepared SQL update
22:28:49,554 DEBUG JdbcTemplate:627 - Executing prepared SQL statement [delete from persistent_logins where username = ?]
22:28:49,554 DEBUG DataSourceUtils:110 - Fetching JDBC Connection from DataSource
22:28:49,560 DEBUG JdbcTemplate:918 - SQL update affected 2 rows
22:28:49,561 DEBUG DataSourceUtils:327 - Returning JDBC Connection to DataSource
22:28:49,562 DEBUG DefaultRedirectStrategy:39 - Redirecting to '/login?error=expired'
22:28:49,562 DEBUG HttpSessionSecurityContextRepository:337 - SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
第四行红色标注的就是前面提到的忘记了的那个实现。
下面一行红色的标注就是原因所在了。这个实现的处理有点欠考虑啊,能看到这里的大概能明白哪里欠考虑吧。(也许人家是觉着这是一种安全机制,表示你的账户被"异常登录"?后,你的remember-me会失效。但我觉着这样确实不太友好。)只按用户名删除,导致了第二个登陆那个人的remember-me信息丢失了。
要解决这个问题的话,我想到的实现就是在spring里注册一下这个bean,修改一下这条语句,再注入进去。至于如何修改,我觉着可以比较一下last_used这个时间戳,只删除小的那个。就能解决啦。sql语句我就不改了,因为我不会。
delete from p1 where last_used=(select * from (select MIN(last_used) from p1 where username='a')as b) and username='a';
注意,上面是错的,哈哈没搞出来,去stackoverflow提问去了,欢迎解答!
http://stackoverflow.com/questions/33408271/spring-security-remember-me-function-failure