How can I use Spring Security without sessions?
Asked Answered
C

8

111

I am building a web application with Spring Security that will live on Amazon EC2 and use Amazon's Elastic Load Balancers. Unfortunately, ELB does not support sticky sessions, so I need to ensure my application works properly without sessions.

So far, I have setup RememberMeServices to assign a token via a cookie, and this works fine, but I want the cookie to expire with the browser session (e.g. when the browser closes).

I have to imagine I'm not the first one to want to use Spring Security without sessions... any suggestions?

Chiffonier answered 24/3, 2010 at 0:21 Comment(0)
C
28

It seems to be even easier in Spring Securitiy 3.0. If you're using namespace configuration, you can simply do as follows:

<http create-session="never">
  <!-- config -->
</http>

Or you could configure the SecurityContextRepository as null, and nothing would ever get saved that way as well.

Chiffonier answered 27/3, 2010 at 14:44 Comment(1)
This didn't work as I thought it would. Instead, there's a comment below that distinguishes between "never" and "stateless". Using "never", my app was still creating sessions. Using "stateless", my app actually went stateless, and I didn't need to implement any of the overrides mentioned in other answers. See the JIRA issue here: jira.springsource.org/browse/SEC-1424Fowkes
M
139

In Spring Security 3 with Java Config, you can use HttpSecurity.sessionManagement():

@Override
protected void configure(final HttpSecurity http) throws Exception {
    http
        .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
Milamilady answered 2/6, 2014 at 22:40 Comment(4)
This is the correct answer for Java config, mirroring what @sappenin correctly stated for xml config in a comment on the accepted answer. We use this method and indeed our application is sessionless.Virgo
This has a side effect. The Tomcat container will append ";jsessionid=..." to requests for images, stylesheets, etc, because Tomcat doesn't like to be stateless, and Spring Security will then block these assets on the first load because "the URL contained a potentially malicious String ';'".Lewan
@Lewan So, what you are trying to say by this java configuration, the sessions are not created by spring security rather tomcat?Firstfoot
@VishwasAtrey In my understanding (which may be wrong), Tomcat creates and maintains the sessions. Spring takes advantage of them, adding its own data. I tried to make a stateless web application and it didn't work, as I mentioned above. See this answer to my own question for more.Lewan
C
28

It seems to be even easier in Spring Securitiy 3.0. If you're using namespace configuration, you can simply do as follows:

<http create-session="never">
  <!-- config -->
</http>

Or you could configure the SecurityContextRepository as null, and nothing would ever get saved that way as well.

Chiffonier answered 27/3, 2010 at 14:44 Comment(1)
This didn't work as I thought it would. Instead, there's a comment below that distinguishes between "never" and "stateless". Using "never", my app was still creating sessions. Using "stateless", my app actually went stateless, and I didn't need to implement any of the overrides mentioned in other answers. See the JIRA issue here: jira.springsource.org/browse/SEC-1424Fowkes
T
28

We worked on the same issue (injecting a custom SecurityContextRepository to SecurityContextPersistenceFilter) for 4-5 hours today. Finally, we figured it out. First of all, in the section 8.3 of Spring Security ref. doc, there is a SecurityContextPersistenceFilter bean definition

<bean id="securityContextPersistenceFilter" class="org.springframework.security.web.context.SecurityContextPersistenceFilter">
    <property name='securityContextRepository'>
        <bean class='org.springframework.security.web.context.HttpSessionSecurityContextRepository'>
            <property name='allowSessionCreation' value='false' />
        </bean>
    </property>
</bean>

And after this definition, there is this explanation: "Alternatively you could provide a null implementation of the SecurityContextRepository interface, which will prevent the security context from being stored, even if a session has already been created during the request."

We needed to inject our custom SecurityContextRepository into the SecurityContextPersistenceFilter. So we simply changed the bean definition above with our custom impl and put it into the security context.

When we run the application, we traced the logs and saw that SecurityContextPersistenceFilter was not using our custom impl, it was using the HttpSessionSecurityContextRepository.

After a few other things we tried, we figured out that we had to give our custom SecurityContextRepository impl with the "security-context-repository-ref" attribute of "http" namespace. If you use "http" namespace and want to inject your own SecurityContextRepository impl, try "security-context-repository-ref" attribute.

When "http" namespace is used, a seperate SecurityContextPersistenceFilter definition is ignored. As I copied above, the reference doc. does not state that.

Please correct me if I misunderstood the things.

Treasatreason answered 4/10, 2011 at 20:33 Comment(3)
Thanks, this is valuable information. I will try it out in my application.Glisten
You are quite accurate when you say that the http namespace doesn't allow for a custom SecurityContextPersistenceFilter, it took me a couple of hours of debugging to figure it outWaikiki
Thank you very much for posting this! I was about to tear out what little hair I have. I was wondering why the setSecurityContextRepository method of SecurityContextPersistenceFilter was deprecated (the docs saying to use constructor injection, which is not right either).Blackdamp
J
11

Take a look at SecurityContextPersistenceFilter class. It defines how the SecurityContextHolder is populated. By default it uses HttpSessionSecurityContextRepository to store security context in http session.

I have implemented this mechanism quite easily, with custom SecurityContextRepository.

See the securityContext.xml below:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:sec="http://www.springframework.org/schema/security"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
       http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
       http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd">

    <context:annotation-config/>

    <sec:global-method-security secured-annotations="enabled" pre-post-annotations="enabled"/>

    <bean id="securityContextRepository" class="com.project.server.security.TokenSecurityContextRepository"/>

    <bean id="securityContextFilter" class="com.project.server.security.TokenSecurityContextPersistenceFilter">
        <property name="repository" ref="securityContextRepository"/>
    </bean>

    <bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
        <constructor-arg value="/login.jsp"/>
        <constructor-arg>
            <list>
                <bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/>
            </list>
        </constructor-arg>
    </bean>

    <bean id="formLoginFilter"
          class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="authenticationSuccessHandler">
            <bean class="com.project.server.security.TokenAuthenticationSuccessHandler">
                <property name="defaultTargetUrl" value="/index.html"/>
                <property name="passwordExpiredUrl" value="/changePassword.jsp"/>
                <property name="alwaysUseDefaultTargetUrl" value="true"/>
            </bean>
        </property>
        <property name="authenticationFailureHandler">
            <bean class="com.project.server.modules.security.CustomUrlAuthenticationFailureHandler">
                <property name="defaultFailureUrl" value="/login.jsp?failure=1"/>
            </bean>
        </property>
        <property name="filterProcessesUrl" value="/j_spring_security_check"/>
        <property name="allowSessionCreation" value="false"/>
    </bean>

    <bean id="servletApiFilter"
          class="org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter"/>

    <bean id="anonFilter" class="org.springframework.security.web.authentication.AnonymousAuthenticationFilter">
        <property name="key" value="ClientApplication"/>
        <property name="userAttribute" value="anonymousUser,ROLE_ANONYMOUS"/>
    </bean>


    <bean id="exceptionTranslator" class="org.springframework.security.web.access.ExceptionTranslationFilter">
        <property name="authenticationEntryPoint">
            <bean class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
                <property name="loginFormUrl" value="/login.jsp"/>
            </bean>
        </property>
        <property name="accessDeniedHandler">
            <bean class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
                <property name="errorPage" value="/login.jsp?failure=2"/>
            </bean>
        </property>
        <property name="requestCache">
            <bean id="nullRequestCache" class="org.springframework.security.web.savedrequest.NullRequestCache"/>
        </property>
    </bean>

    <alias name="filterChainProxy" alias="springSecurityFilterChain"/>

    <bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
        <sec:filter-chain-map path-type="ant">
            <sec:filter-chain pattern="/**"
                              filters="securityContextFilter, logoutFilter, formLoginFilter,
                                        servletApiFilter, anonFilter, exceptionTranslator, filterSecurityInterceptor"/>
        </sec:filter-chain-map>
    </bean>

    <bean id="filterSecurityInterceptor"
          class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
        <property name="securityMetadataSource">
            <sec:filter-security-metadata-source use-expressions="true">
                <sec:intercept-url pattern="/staticresources/**" access="permitAll"/>
                <sec:intercept-url pattern="/index.html*" access="hasRole('USER_ROLE')"/>
                <sec:intercept-url pattern="/rpc/*" access="hasRole('USER_ROLE')"/>
                <sec:intercept-url pattern="/**" access="permitAll"/>
            </sec:filter-security-metadata-source>
        </property>
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="accessDecisionManager" ref="accessDecisionManager"/>
    </bean>

    <bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
        <property name="decisionVoters">
            <list>
                <bean class="org.springframework.security.access.vote.RoleVoter"/>
                <bean class="org.springframework.security.web.access.expression.WebExpressionVoter"/>
            </list>
        </property>
    </bean>

    <bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager">
        <property name="providers">
            <list>
                <bean name="authenticationProvider"
                      class="com.project.server.modules.security.oracle.StoredProcedureBasedAuthenticationProviderImpl">
                    <property name="dataSource" ref="serverDataSource"/>
                    <property name="userDetailsService" ref="userDetailsService"/>
                    <property name="auditLogin" value="true"/>
                    <property name="postAuthenticationChecks" ref="customPostAuthenticationChecks"/>
                </bean>
            </list>
        </property>
    </bean>

    <bean id="customPostAuthenticationChecks" class="com.project.server.modules.security.CustomPostAuthenticationChecks"/>

    <bean name="userDetailsService" class="com.project.server.modules.security.oracle.UserDetailsServiceImpl">
        <property name="dataSource" ref="serverDataSource"/>
    </bean>

</beans>
Justification answered 26/3, 2010 at 12:33 Comment(2)
Hi Lukas, can you give any more details of your security context repository implementation?Agripina
class TokenSecurityContextRepository contains HashMap<String, SecurityContext> contextMap. In loadContext() method checks whether exists SecurityContext for session hash code passed by either requestParameter sid, or cookie, or custom requestHeader or combination of any above. Returns SecurityContextHolder.createEmptyContext() if context could not be resolved. Method saveContext puts resolved context to contextMap.Justification
A
8

Actually create-session="never" doesn't mean being completely stateless. There's an issue for that in Spring Security issue management.

Amniocentesis answered 30/3, 2010 at 17:3 Comment(0)
G
4

EDIT: As of Spring Security 3.1, there is a STATELESS option that can be used instead of all this. See the other answers. Original answer kept below for posterity.

After struggling with the numerous solutions posted in this answer, to try to get something working when using the <http> namespace config, I finally found an approach that actually works for my use case. I don't actually require that Spring Security doesn't start a session (because I use session in other parts of the application), just that it doesn't "remember" authentication in the session at all (it should be re-checked every request).

To begin with, I wasn't able to figure out how to do the "null implementation" technique described above. It wasn't clear whether you are supposed to set the securityContextRepository to null or to a no-op implementation. The former does not work because a NullPointerException gets thrown within SecurityContextPersistenceFilter.doFilter(). As for the no-op implementation, I tried implementing in the simplest way I could imagine:

public class NullSpringSecurityContextRepository implements SecurityContextRepository {

    @Override
    public SecurityContext loadContext(final HttpRequestResponseHolder requestResponseHolder_) {
        return SecurityContextHolder.createEmptyContext();
    }

    @Override
    public void saveContext(final SecurityContext context_, final HttpServletRequest request_,
            final HttpServletResponse response_) {
    }

    @Override
    public boolean containsContext(final HttpServletRequest request_) {
        return false;
    }

}

This doesn't work in my application, because of some strange ClassCastException having to do with the response_ type.

Even assuming I did manage to find an implementation that works (by simply not storing the context in session), there is still the problem of how to inject that into the filters built by the <http> configuration. You cannot simply replace the filter at the SECURITY_CONTEXT_FILTER position, as per the docs. The only way I found to hook into the SecurityContextPersistenceFilter that is created under the covers was to write an ugly ApplicationContextAware bean:

public class SpringSecuritySessionDisabler implements ApplicationContextAware {

    private final Logger logger = LoggerFactory.getLogger(SpringSecuritySessionDisabler.class);

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(final ApplicationContext applicationContext_) throws BeansException {
        applicationContext = applicationContext_;
    }

    public void disableSpringSecuritySessions() {
        final Map<String, FilterChainProxy> filterChainProxies = applicationContext
                .getBeansOfType(FilterChainProxy.class);
        for (final Entry<String, FilterChainProxy> filterChainProxyBeanEntry : filterChainProxies.entrySet()) {
            for (final Entry<String, List<Filter>> filterChainMapEntry : filterChainProxyBeanEntry.getValue()
                    .getFilterChainMap().entrySet()) {
                final List<Filter> filterList = filterChainMapEntry.getValue();
                if (filterList.size() > 0) {
                    for (final Filter filter : filterList) {
                        if (filter instanceof SecurityContextPersistenceFilter) {
                            logger.info(
                                    "Found SecurityContextPersistenceFilter, mapped to URL '{}' in the FilterChainProxy bean named '{}', setting its securityContextRepository to the null implementation to disable caching of authentication",
                                    filterChainMapEntry.getKey(), filterChainProxyBeanEntry.getKey());
                            ((SecurityContextPersistenceFilter) filter).setSecurityContextRepository(
                             new NullSpringSecurityContextRepository());
                        }
                    }
                }

            }
        }
    }
}

Anyway, to the solution that actually does work, albeit very hackish. Simply use a Filter that deletes the session entry that the HttpSessionSecurityContextRepository looks for when it does its thing:

public class SpringSecuritySessionDeletingFilter extends GenericFilterBean implements Filter {

    @Override
    public void doFilter(final ServletRequest request_, final ServletResponse response_, final FilterChain chain_)
            throws IOException, ServletException {
        final HttpServletRequest servletRequest = (HttpServletRequest) request_;
        final HttpSession session = servletRequest.getSession();
        if (session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY) != null) {
            session.removeAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
        }

        chain_.doFilter(request_, response_);
    }
}

Then in the configuration:

<bean id="springSecuritySessionDeletingFilter"
    class="SpringSecuritySessionDeletingFilter" />

<sec:http auto-config="false" create-session="never"
    entry-point-ref="authEntryPoint">
    <sec:intercept-url pattern="/**"
        access="IS_AUTHENTICATED_REMEMBERED" />
    <sec:intercept-url pattern="/static/**" filters="none" />
    <sec:custom-filter ref="myLoginFilterChain"
        position="FORM_LOGIN_FILTER" />

    <sec:custom-filter ref="springSecuritySessionDeletingFilter"
        before="SECURITY_CONTEXT_FILTER" />
</sec:http>
Glisten answered 1/10, 2011 at 19:44 Comment(4)
Nine years later, this is still the right answer. Now we can use Java configuration instead of XML. I added the custom filter in my WebSecurityConfigurerAdapter with "http.addFilterBefore(new SpringSecuritySessionDeletingFilter(), SecurityContextPersistenceFilter.class)"Lewan
If you use SessionCreationPolicy.STATELESS as described in another answer, this shouldn't be necessary. You must have something else going on.Aftercare
STATELESS appears to have been added in 3.1. At the time this answer was written, the latest released version was 3.0. So that explains it.Glisten
Thanks @JeffEvans, SpringSecuritySessionDeletingFilter save me a lot of time. I had a problem when in some cases i need stateless behavior, while in another cases notDiomedes
L
3

Just a quick note: it's "create-session" rather than "create-sessions"

create-session

Controls the eagerness with which an HTTP session is created.

If not set, defaults to "ifRequired". Other options are "always" and "never".

The setting of this attribute affect the allowSessionCreation and forceEagerSessionCreation properties of HttpSessionContextIntegrationFilter. allowSessionCreation will always be true unless this attribute is set to "never". forceEagerSessionCreation is "false" unless it is set to "always".

So the default configuration allows session creation but does not force it. The exception is if concurrent session control is enabled, when forceEagerSessionCreation will be set to true, regardless of what the setting is here. Using "never" would then cause an exception during the initialization of HttpSessionContextIntegrationFilter.

For specific details of the session usage, there is some good documentation in the HttpSessionSecurityContextRepository javadoc.

Latoyalatoye answered 7/4, 2010 at 9:7 Comment(2)
These are all great answers, but I've been beating my head against the wall trying to figure out how to achieve this when using the <http> config element. Even with auto-config=false, you apparently can't replace what's in the SECURITY_CONTEXT_FILTER position with your own. I've been hacking around trying to disable it with some ApplicationContextAware bean (using reflection to force the securityContextRepository to a null implementation in SessionManagementFilter) but no dice. And sadly, I can't switch to spring-security 3.1 year which would provide create-session=stateless.Glisten
Please visit this site, always informative. Hope this helps you and other as well "baeldung.com/spring-security-session" • always – a session will always be created if one doesn’t already exist • ifRequired – a session will be created only if required (default) • never – the framework will never create a session itself but it will use one if it already exists • stateless – no session will be created or used by Spring SecurityCrowd
F
0

Now ELB supports sticky sessions, I think from 2016. But also it's possible to store your sessions in Redis.

Flutist answered 16/9, 2021 at 16:25 Comment(1)
This does not provide an answer to the question. Once you have sufficient reputation you will be able to comment on any post; instead, provide answers that don't require clarification from the asker. - From ReviewLeucippus

© 2022 - 2024 — McMap. All rights reserved.