Session consistency is important part which is not overlooked by Spring Security. Thanks to special filter, SessionManagementFilter, the project allows to control better sessions.
At the begin of this article we'll talk about this specific filter. We'll also see how to define it. At the second part we'll explore this filter more in details. The exploration will start by control of concurrent sessions. After that, we'll take up the subject of session fixation. At the end we'll setup the session management filter.
SessionManagementFilter in Spring Security
As we mentioned earlier, filter responsible for session state management is org.springframework.security.web.session.SessionManagementFilter. In XML configuration it's represented by a tag called <session-management />. This filter is an entry point for session protection, activated for currently set Authentication object. If this object is not set or it's corrupted, an instance of InvalidSessionStrategy is used to adopt application's behavior for given situation.
Default implementation of InvalidSessionStrategy is SimpleRedirectInvalidSessionStrategy. When the session is invalid, this class will make redirect request to page specified in private final String destinationUrl field. The value of this field can be set as invalid-session-url attribute of <session-management /> tag. The redirect can be made with the same session id or with a new one. It's advised to reset the session (ie. session will have different id). Thanks to it, we can avoid the cases of infinite redirect loop. This kind of errors can occur when the same session is detected every time by SessionManagerFilter as invalid. If new session can't be created, the redirect should be made on URL in which SessionManagerFilter is not applied.
Session concurrency management in Spring Security
Session manager protects also against multiple existence of the same session. It protects for example against the multiple connection of the same user. Spring Security handles this case with org.springframework.security.web.session.ConcurrentSessionFilter. At the begin of its doFilter method, the filter gets session information from session registry. Registry is represented by SessionRegistry interface which default implementation is SessionRegistryImpl. This implementation is also a listener. It implements ApplicationListener interface and is listening on SessionDestroyedEvent. This event is triggered every time when one session terminates. In this situation, SessionRegistryImpl removes given session from the registry.
Session information is stored in org.springframework.security.core.session.SessionInformation class. It contains the main information about session stored in registry: last request time, principal associated to session, session id and session state (expired or not). One session can be marked as expired with the call of expireNow() method of SessionInformation class. This method is invoked in the class handling session concurrency. Until Spring Security 3.0, class used for concurrency management was ConcurrentSessionControlStrategy. Since 3.2 version, this class is org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy.
When user authenticates, public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) is invoked. Inside it, Spring checks if the number of sessions belonging to connected user in session registry is lower than the max number of allowed sessions. Session is invalidated in protected void allowableSessionsExceeded(List<SessionInformation> sessions, int allowableSessions, SessionRegistry registry) method. It picks up session to invalidate and make that by calling expireNow() method. Thanks to it the concurrency detection in ConcurrentSessionFilter is possible.
If SessionInformation is considered as expired, Authentication object associated to analyzed SecurityContext is removed and user is redirected URL specified in expired-url attribute.
Session fixation protection in Spring Security
Another potential session danger is an attack called session fixation. This attack is based on hijack of session id. Thanks to that, the aggressor can, without any authentication effort, be recognized by a system as victim user. To represent this better, let's write a schema:
- Aggressor tries to authentify in attacked website. The authentication fails and the aggressor gets his session id from attacked site.
- Aggressor sends a link to victim. After opening this link, the victim will have the same session id as aggressor. This fixation can be made with the send of link containing JSESSIONID parameter.
- Victim authenticates now in attacked website. After his authentication, the aggressor is authenticated too because they're share both the same session id.
Session manager activates the protection against this danger by the presence of session-fixation-protection attribute in <session-management /> element. If specified, the bean representing session fixation strategy is constructed. Depending on attribute's value, it uses either ChangeSessionInAuthenticationStrategy or SessionFixationProtectionStrategy. ChangeSessionInAuthenticationStrategy is used when we want that session's id changes after user authentication. SessionFixationProtectionStrategy makes the same thing but in more sophisticated way. It creates new session and copies all current session content into it. One point is common for both solutions: they generate new session id. So, even if aggressor fixed an id to his victim, he won't be able to exploit that because the victim will have a session with different id generated by Spring Security. Associated attribute with this configuration is called migrateSession.
The default implementation of session fixation strategy for migrateSession depends on servlet's version. For versions lower than 3.1, SessionFixationProtectionStrategy is used. Otherwise, ChangeSessionInAuthenticationStrategy is invoked.
We can also disable session fixation protection by putting none to session-fixation-protection attribute. There're another choice available, newSession. It will use SessionFixationProtectionStrategy but it won't copy old session's attributes to the new session. This dependency is managed through boolean migrateSessionAttributes = true field. If false, attributes from old session won't be copied into new one.
Setup session management in Spring Security
At the begin of this article we mentioned that session management is defined in <session-management /> tag. In additionally, we've already seen which attributes must be set to activate listed session protection mechanisms. So following definition of session management shouldn't be mysterious:
<security:session-management invalid-session-url="/invalid-session" session-fixation-protection="migrateSession"> <security:concurrency-control expired-url="/expired-session" max-sessions="1" error-if-maximum-exceeded="true" /> </security:session-management>
This configuration activates all protections. When a session is invalid (session fixation attempted), user should be redirected to /invalid-session page. In the case when the same user tries to login more than 1 times (for example by creating two sessions, both in two different browsers), he should be redirected to /expired-session page. Thanks to error-if-maximum-exceeded set to true, the second user will throw SessionAuthenticationException. If this value is false, user already logged will be logged out. We'll check that below in example.
So, let's begin by trying to login twice, from different browsers. The first browser should allow log in while the second should produce following log output:
DEBUG: org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy - Delegating to org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy@25d8c06a DEBUG: org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter - Authentication request failed: org.springframework.security.web.authentication.session.SessionAuthenticationException: Maximum sessions of 1 for this principal exceeded DEBUG: org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter - Updated SecurityContextHolder to contain null Authentication DEBUG: org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter - Delegating to authentication failure handler org.springfra mework.security.web.authentication.SimpleUrlAuthenticationFailureHandler@2af604de DEBUG: org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices - Interactive login attempt was unsuccessful. DEBUG: org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices - Cancelling cookie DEBUG: org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler - Redirecting to /login?error=true
Note one interesting thing. If connected user logouts and tries to login again, he won't be able to do that. Spring doesn't know that a session of one principal was released. If you remember well, filter in charge of concurrent authentication is listening on SessionDestroyedEvent events. But if there're no event publisher, it's not able to detect that one user logged out. To correct that we need to define a listener in web.xml file. The listener is in the same time the expected event publisher:
<listener> <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher </listener>
If you look carefully on source code for this class, you'll see that it sends events when two operations occur: session is created and session is destroyed. Thanks to these both methods our session registry can intercept the session destruction event and remove corresponding session. In this way the session will be available once again for another logins.
After, let's check protection against session fixation. To start, we open the first browser and copy current value of JSESSIONID (E0DB75D7963A5466188B4E0B18ABFD14 in our example). After that, we open the second browser and paste retrieved value to homepage URL (should done http://localhost:8080?JSESSIONID=E0DB75D7963A5466188B4E0B18ABFD14). We check after is session's id cookie is equal to copied value (E0DB75D7963A5466188B4E0B18ABFD14 in our case). The second browser is considered as victim. So, we try to login by providing correct credentials. However, it won't allow us to connect because of session fixation protection. Instead of see dashboard page, user will see page defined in invalid-session-url attribute. In additionnally, his JSESSIONID will change. Logs should contain following entries:
DEBUG: org.springframework.security.web.session.SessionManagementFilter - Requested session ID E0DB75D7963A5466188B4E0B18ABFD14 is invalid. DEBUG: org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy - Starting new session (if required) and redirecting to '/invalid-session'
In this article we discovered another face of Spring Security, until now known for its authentication, authorization and CSRF dangers. But we see that this project can also help to protect sessions thanks to SessionManagementFilter. The second part described how to use this filter to manage the number of authorized concurrent sessions per authenticated user. After that we discovered how to protect user against session fixation attack. At the end we put everything in practice and saw what happen in front and server-side with activated session management filter.