I am following this tutorial on Spring Security in the Spring.io guides. The logout and login functions are working fine, but when I add the following line in the WebSecurityConfigurerAdapter
it doesn't work as expected. (Basically, I want to prevent a user from login from two devices if he is already logged in in one)
@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// The configuration as in the tutorial
http
.httpBasic().and()
.authorizeRequests()
.antMatchers("/index.html", "/home.html", "/login.html", "/").permitAll()
.anyRequest().authenticated()
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
// Added this for session management
http.sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(true)
}
}
The problem occurs if you logout and try to login again, a 401 is returned with the message 'Authentication Failed: Maximum
sessions of 1 for this principal exceeded'.
Yet the logout URL is hit on this part in the AngularJs
app
self.logout = function() {
$http.post('logout', {}).finally(function() {
$rootScope.authenticated = false;
$location.path("/");
});
}
Why isn't the number of sessions reset in this case?
What can be done to make it work as expected?
Spring Security logs in debug mode
2017-01-03 21:38:01.806 DEBUG 32624 --- [nio-8080-exec-9] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/logout'; against '/error'
2017-01-03 21:38:01.806 DEBUG 32624 --- [nio-8080-exec-9] o.s.security.web.FilterChainProxy : /logout at position 1 of 13 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2017-01-03 21:38:01.806 DEBUG 32624 --- [nio-8080-exec-9] o.s.security.web.FilterChainProxy : /logout at position 2 of 13 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2017-01-03 21:38:01.807 DEBUG 32624 --- [nio-8080-exec-9] w.c.HttpSessionSecurityContextRepository : Obtained a valid SecurityContext from SPRING_SECURITY_CONTEXT: 'org.springframework.security.core.context.SecurityContextImpl@442b5a9f: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@442b5a9f: Principal: org.springframework.security.core.userdetails.User@36ebcb: Username: user; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; 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_USER'
2017-01-03 21:38:01.807 DEBUG 32624 --- [nio-8080-exec-9] o.s.security.web.FilterChainProxy : /logout at position 3 of 13 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2017-01-03 21:38:01.807 DEBUG 32624 --- [nio-8080-exec-9] o.s.security.web.FilterChainProxy : /logout at position 4 of 13 in additional filter chain; firing Filter: 'CsrfFilter'
2017-01-03 21:38:01.807 DEBUG 32624 --- [nio-8080-exec-9] o.s.security.web.FilterChainProxy : /logout at position 5 of 13 in additional filter chain; firing Filter: 'LogoutFilter'
2017-01-03 21:38:01.807 DEBUG 32624 --- [nio-8080-exec-9] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/logout'; against '/logout'
2017-01-03 21:38:01.807 DEBUG 32624 --- [nio-8080-exec-9] o.s.s.w.a.logout.LogoutFilter : Logging out user 'org.springframework.security.authentication.UsernamePasswordAuthenticationToken@442b5a9f: Principal: org.springframework.security.core.userdetails.User@36ebcb: Username: user; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; 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_USER' and transferring to logout destination
2017-01-03 21:38:01.807 DEBUG 32624 --- [nio-8080-exec-9] o.s.s.w.a.l.SecurityContextLogoutHandler : Invalidating session: DDC79F814F9ECD2A0192531E977D53C9
2017-01-03 21:38:01.807 DEBUG 32624 --- [nio-8080-exec-9] o.s.s.web.util.matcher.OrRequestMatcher : Trying to match using RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]
2017-01-03 21:38:01.808 DEBUG 32624 --- [nio-8080-exec-9] o.s.s.web.util.matcher.OrRequestMatcher : matched
2017-01-03 21:38:01.808 DEBUG 32624 --- [nio-8080-exec-9] o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@e0735a1
2017-01-03 21:38:01.809 DEBUG 32624 --- [nio-8080-exec-9] w.c.HttpSessionSecurityContextRepository : SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
2017-01-03 21:38:01.809 DEBUG 32624 --- [nio-8080-exec-9] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
2017-01-03 21:38:38.069 DEBUG 32624 --- [io-8080-exec-10] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/user'; against '/error'
2017-01-03 21:38:38.069 DEBUG 32624 --- [io-8080-exec-10] o.s.security.web.FilterChainProxy : /user at position 1 of 13 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2017-01-03 21:38:38.069 DEBUG 32624 --- [io-8080-exec-10] o.s.security.web.FilterChainProxy : /user at position 2 of 13 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2017-01-03 21:38:38.069 DEBUG 32624 --- [io-8080-exec-10] w.c.HttpSessionSecurityContextRepository : No HttpSession currently exists
2017-01-03 21:38:38.069 DEBUG 32624 --- [io-8080-exec-10] w.c.HttpSessionSecurityContextRepository : No SecurityContext was available from the HttpSession: null. A new one will be created.
2017-01-03 21:38:38.069 DEBUG 32624 --- [io-8080-exec-10] o.s.security.web.FilterChainProxy : /user at position 3 of 13 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2017-01-03 21:38:38.069 DEBUG 32624 --- [io-8080-exec-10] o.s.security.web.FilterChainProxy : /user at position 4 of 13 in additional filter chain; firing Filter: 'CsrfFilter'
2017-01-03 21:38:38.069 DEBUG 32624 --- [io-8080-exec-10] o.s.security.web.FilterChainProxy : /user at position 5 of 13 in additional filter chain; firing Filter: 'LogoutFilter'
2017-01-03 21:38:38.069 DEBUG 32624 --- [io-8080-exec-10] o.s.s.w.u.matcher.AntPathRequestMatcher : Request 'GET /user' doesn't match 'POST /logout
2017-01-03 21:38:38.069 DEBUG 32624 --- [io-8080-exec-10] o.s.security.web.FilterChainProxy : /user at position 6 of 13 in additional filter chain; firing Filter: 'ConcurrentSessionFilter'
2017-01-03 21:38:38.069 DEBUG 32624 --- [io-8080-exec-10] o.s.security.web.FilterChainProxy : /user at position 7 of 13 in additional filter chain; firing Filter: 'BasicAuthenticationFilter'
2017-01-03 21:38:38.070 DEBUG 32624 --- [io-8080-exec-10] o.s.security.web.FilterChainProxy : /user at position 8 of 13 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
2017-01-03 21:38:38.070 DEBUG 32624 --- [io-8080-exec-10] o.s.security.web.FilterChainProxy : /user at position 9 of 13 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
2017-01-03 21:38:38.070 DEBUG 32624 --- [io-8080-exec-10] o.s.security.web.FilterChainProxy : /user at position 10 of 13 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
2017-01-03 21:38:38.070 DEBUG 32624 --- [io-8080-exec-10] o.s.s.w.a.AnonymousAuthenticationFilter : 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'
2017-01-03 21:38:38.070 DEBUG 32624 --- [io-8080-exec-10] o.s.security.web.FilterChainProxy : /user at position 11 of 13 in additional filter chain; firing Filter: 'SessionManagementFilter'
2017-01-03 21:38:38.070 DEBUG 32624 --- [io-8080-exec-10] o.s.s.w.session.SessionManagementFilter : Requested session ID DDC79F814F9ECD2A0192531E977D53C9 is invalid.
2017-01-03 21:38:38.070 DEBUG 32624 --- [io-8080-exec-10] o.s.security.web.FilterChainProxy : /user at position 12 of 13 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
2017-01-03 21:38:38.070 DEBUG 32624 --- [io-8080-exec-10] o.s.security.web.FilterChainProxy : /user at position 13 of 13 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
2017-01-03 21:38:38.070 DEBUG 32624 --- [io-8080-exec-10] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/user'; against '/index.html'
2017-01-03 21:38:38.070 DEBUG 32624 --- [io-8080-exec-10] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/user'; against '/home.html'
2017-01-03 21:38:38.070 DEBUG 32624 --- [io-8080-exec-10] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/user'; against '/login.html'
2017-01-03 21:38:38.070 DEBUG 32624 --- [io-8080-exec-10] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/user'; against '/'
2017-01-03 21:38:38.070 DEBUG 32624 --- [io-8080-exec-10] o.s.s.w.a.i.FilterSecurityInterceptor : Secure object: FilterInvocation: URL: /user; Attributes: [authenticated]
2017-01-03 21:38:38.071 DEBUG 32624 --- [io-8080-exec-10] o.s.s.w.a.i.FilterSecurityInterceptor : 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
2017-01-03 21:38:38.071 DEBUG 32624 --- [io-8080-exec-10] o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.web.access.expression.WebExpressionVoter@6bd96c27, returned: -1
2017-01-03 21:38:38.072 DEBUG 32624 --- [io-8080-exec-10] o.s.s.w.a.ExceptionTranslationFilter : Access is denied (user is anonymous); redirecting to authentication entry point
org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:84) ~[spring-security-core-4.1.3.RELEASE.jar:4.1.3.RELEASE]
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:233) ~[spring-security-core-4.1.3.RELEASE.jar:4.1.3.RELEASE]
2017-01-03 21:38:38.073 DEBUG 32624 --- [io-8080-exec-10] o.s.s.w.util.matcher.AndRequestMatcher : Trying to match using Ant [pattern='/**', GET]
2017-01-03 21:38:38.073 DEBUG 32624 --- [io-8080-exec-10] o.s.s.w.u.matcher.AntPathRequestMatcher : Request '/user' matched by universal pattern '/**'
2017-01-03 21:38:38.073 DEBUG 32624 --- [io-8080-exec-10] o.s.s.w.util.matcher.AndRequestMatcher : Trying to match using NegatedRequestMatcher [requestMatcher=Ant [pattern='/**/favicon.ico']]
2017-01-03 21:38:38.073 DEBUG 32624 --- [io-8080-exec-10] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/user'; against '/**/favicon.ico'
2017-01-03 21:38:38.073 DEBUG 32624 --- [io-8080-exec-10] o.s.s.w.u.matcher.NegatedRequestMatcher : matches = true
2017-01-03 21:38:38.073 DEBUG 32624 --- [io-8080-exec-10] o.s.s.w.util.matcher.AndRequestMatcher : Trying to match using NegatedRequestMatcher [requestMatcher=MediaTypeRequestMatcher [contentNegotiationStrategy=org.springframework.web.accept.ContentNegotiationManager@47099aec, matchingMediaTypes=[application/json], useEquals=false, ignoredMediaTypes=[*/*]]]
2017-01-03 21:38:38.074 DEBUG 32624 --- [io-8080-exec-10] o.s.s.w.u.m.MediaTypeRequestMatcher : httpRequestMediaTypes=[application/json, text/plain, */*]
2017-01-03 21:38:38.074 DEBUG 32624 --- [io-8080-exec-10] o.s.s.w.u.m.MediaTypeRequestMatcher : Processing application/json
2017-01-03 21:38:38.074 DEBUG 32624 --- [io-8080-exec-10] o.s.s.w.u.m.MediaTypeRequestMatcher : application/json .isCompatibleWith application/json = true
2017-01-03 21:38:38.074 DEBUG 32624 --- [io-8080-exec-10] o.s.s.w.u.matcher.NegatedRequestMatcher : matches = false
2017-01-03 21:38:38.074 DEBUG 32624 --- [io-8080-exec-10] o.s.s.w.util.matcher.AndRequestMatcher : Did not match
2017-01-03 21:38:38.074 DEBUG 32624 --- [io-8080-exec-10] o.s.s.w.s.HttpSessionRequestCache : Request not saved as configured RequestMatcher did not match
2017-01-03 21:38:38.074 DEBUG 32624 --- [io-8080-exec-10] o.s.s.w.a.ExceptionTranslationFilter : Calling Authentication entry point.
2017-01-03 21:38:38.074 DEBUG 32624 --- [io-8080-exec-10] s.w.a.DelegatingAuthenticationEntryPoint : Trying to match using RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]
2017-01-03 21:38:38.074 DEBUG 32624 --- [io-8080-exec-10] s.w.a.DelegatingAuthenticationEntryPoint : Match found! Executing org.springframework.security.web.authentication.HttpStatusEntryPoint@301fca8
2017-01-03 21:38:38.074 DEBUG 32624 --- [io-8080-exec-10] o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@e0735a1
2017-01-03 21:38:38.074 DEBUG 32624 --- [io-8080-exec-10] w.c.HttpSessionSecurityContextRepository : SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
2017-01-03 21:38:38.074 DEBUG 32624 --- [io-8080-exec-10] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
2017-01-03 21:39:24.188 DEBUG 32624 --- [nio-8080-exec-1] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/user'; against '/error'
2017-01-03 21:39:24.188 DEBUG 32624 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : /user at position 1 of 13 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2017-01-03 21:39:24.188 DEBUG 32624 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : /user at position 2 of 13 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2017-01-03 21:39:24.188 DEBUG 32624 --- [nio-8080-exec-1] w.c.HttpSessionSecurityContextRepository : No HttpSession currently exists
2017-01-03 21:39:24.188 DEBUG 32624 --- [nio-8080-exec-1] w.c.HttpSessionSecurityContextRepository : No SecurityContext was available from the HttpSession: null. A new one will be created.
2017-01-03 21:39:24.188 DEBUG 32624 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : /user at position 3 of 13 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2017-01-03 21:39:24.188 DEBUG 32624 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : /user at position 4 of 13 in additional filter chain; firing Filter: 'CsrfFilter'
2017-01-03 21:39:24.188 DEBUG 32624 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : /user at position 5 of 13 in additional filter chain; firing Filter: 'LogoutFilter'
2017-01-03 21:39:24.188 DEBUG 32624 --- [nio-8080-exec-1] o.s.s.w.u.matcher.AntPathRequestMatcher : Request 'GET /user' doesn't match 'POST /logout
2017-01-03 21:39:24.188 DEBUG 32624 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : /user at position 6 of 13 in additional filter chain; firing Filter: 'ConcurrentSessionFilter'
2017-01-03 21:39:24.188 DEBUG 32624 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : /user at position 7 of 13 in additional filter chain; firing Filter: 'BasicAuthenticationFilter'
2017-01-03 21:39:24.188 DEBUG 32624 --- [nio-8080-exec-1] o.s.s.w.a.www.BasicAuthenticationFilter : Basic Authentication Authorization header found for user 'user'
2017-01-03 21:39:24.188 DEBUG 32624 --- [nio-8080-exec-1] o.s.s.authentication.ProviderManager : Authentication attempt using org.springframework.security.authentication.dao.DaoAuthenticationProvider
2017-01-03 21:39:24.189 DEBUG 32624 --- [nio-8080-exec-1] o.s.s.w.a.www.BasicAuthenticationFilter : Authentication success: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@442b5a9f: Principal: org.springframework.security.core.userdetails.User@36ebcb: Username: user; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; 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_USER
2017-01-03 21:39:24.189 DEBUG 32624 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : /user at position 8 of 13 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
2017-01-03 21:39:24.189 DEBUG 32624 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : /user at position 9 of 13 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
2017-01-03 21:39:24.189 DEBUG 32624 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : /user at position 10 of 13 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
2017-01-03 21:39:24.189 DEBUG 32624 --- [nio-8080-exec-1] o.s.s.w.a.AnonymousAuthenticationFilter : SecurityContextHolder not populated with anonymous token, as it already contained: 'org.springframework.security.authentication.UsernamePasswordAuthenticationToken@442b5a9f: Principal: org.springframework.security.core.userdetails.User@36ebcb: Username: user; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; 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_USER'
2017-01-03 21:39:24.189 DEBUG 32624 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : /user at position 11 of 13 in additional filter chain; firing Filter: 'SessionManagementFilter'
2017-01-03 21:39:24.189 DEBUG 32624 --- [nio-8080-exec-1] s.CompositeSessionAuthenticationStrategy : Delegating to org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy@2bf94401
2017-01-03 21:39:24.191 DEBUG 32624 --- [nio-8080-exec-1] o.s.s.w.session.SessionManagementFilter : SessionAuthenticationStrategy rejected the authentication object
org.springframework.security.web.authentication.session.SessionAuthenticationException: Maximum sessions of 1 for this principal exceeded
at org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy.allowableSessionsExceeded(ConcurrentSessionControlAuthenticationStrategy.java:153) ~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
at org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy.onAuthentication(ConcurrentSessionControlAuthenticationStrategy.java:123) ~[spring-security-web-4.1.3.RELEASE.jar:4.1.3.RELEASE]
2017-01-03 21:39:24.192 DEBUG 32624 --- [nio-8080-exec-1] .a.SimpleUrlAuthenticationFailureHandler : No failure URL set, sending 401 Unauthorized error
2017-01-03 21:39:24.192 DEBUG 32624 --- [nio-8080-exec-1] o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@e0735a1
2017-01-03 21:39:24.192 DEBUG 32624 --- [nio-8080-exec-1] w.c.HttpSessionSecurityContextRepository : SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
2017-01-03 21:39:24.192 DEBUG 32624 --- [nio-8080-exec-1] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
You need to do following things to make it work:
According to https://github.com/spring-projects/spring-security/issues/3078, you need to provide the session registry explicitly as a workaround to this issue.(This step is optional. I am guessing it has been fixed in the latest version. If the functionality doesn't work then you can add this step.)
Spring Security requires a
HttpSessionListener
to be registered.Your final code should look something like this: