1
votes

I'm trying to develop an application with spring security login functionality while user session is distributed using apache Ignite.

  • Server: Apache Tomcat 8
  • Spring version: 4.2.2.RELEASE
  • Ignite version: 2.1.0

I have two errors in my application.

  1. Logging an exception when logging out from the application. Apart from that session invalidation is done as expected.

12-Aug-2017 14:09:01.580 SEVERE [http-nio-8080-exec-2] org.apache.ignite.logger.java.JavaLogger.error Failed to update web session: null java.lang.NullPointerException at org.apache.ignite.cache.websession.WebSessionFilter$RequestWrapperV2.getSession(WebSessionFilter.java:1001) at org.apache.ignite.cache.websession.WebSessionFilter.doFilterV2(WebSessionFilter.java:564) at org.apache.ignite.cache.websession.WebSessionFilter.doFilterDispatch(WebSessionFilter.java:407) at org.apache.ignite.cache.websession.WebSessionFilter.doFilter(WebSessionFilter.java:383) 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.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:624) 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)

  1. When two tomcat servers with same application deployments started up in two different ports, can access login page only from one server. (If I accessed login page from first server page is loaded fine as expected. But if I tried to access login page from second server again it gives error. However once logged in application worked as expected and could access distributed session from both servers.
    1. First access

screenshot

  1. Second access from another server

screenshot

My configuration files are as follows.

  1. Spring context configuration

    <beans xmlns.... >
    <context:component-scan base-package="test.ignite.spring"/>
    <mvc:annotation-driven/>
    <context:property-placeholder location="classpath:system.properties" ignore-resource-not-found="true" ignore-unresolvable="true"/>
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    <mvc:interceptors>
        <mvc:interceptor>
            <!-- Cache of HTML pages -->
            <mvc:mapping path="/**"/>
            <bean class="org.springframework.web.servlet.mvc.WebContentInterceptor">
                <property name="cacheSeconds" value="0"/>
            </bean>
        </mvc:interceptor>
    </mvc:interceptors>
    </beans>
    
  2. Login Controller

    @Controller
    public class LoginController {
    
    @RequestMapping(value = {"login", "/"})
    public String login() {
    
        try {
            Authentication auth = SecurityContextHolder.getContext().getAuthentication();
            if (!(auth instanceof AnonymousAuthenticationToken)) {
                return "home";
            }
            return "login";
        } catch (Exception e) {
            return "redirect:/error/500";
        }
    }
    @RequestMapping(value = "/home")
    public String home() {
        return "home";
    }
    }
    

----UPDATED----

  1. Ignite configuration (whole file content)

       <beans xmlns.... >
       <bean abstract="true" id="ignite.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">
        <!-- Set to true to enable distributed class loading for examples, default is false. -->
        <property name="peerClassLoadingEnabled" value="true"/>
    
        <property name="cacheConfiguration">
            <list>
                <bean class="org.apache.ignite.configuration.CacheConfiguration">
                    <property name="name" value="example"/>
                    <property name="cacheMode" value="PARTITIONED"/>
                </bean>
            </list>
        </property>
    
        <!-- Enable task execution events for examples. -->
        <property name="includeEventTypes">
            <list>
                <!--Task execution events-->
                <util:constant static-field="org.apache.ignite.events.EventType.EVT_TASK_STARTED"/>
                <util:constant static-field="org.apache.ignite.events.EventType.EVT_TASK_FINISHED"/>
                <util:constant static-field="org.apache.ignite.events.EventType.EVT_TASK_FAILED"/>
                <util:constant static-field="org.apache.ignite.events.EventType.EVT_TASK_TIMEDOUT"/>
                <util:constant static-field="org.apache.ignite.events.EventType.EVT_TASK_SESSION_ATTR_SET"/>
                <util:constant static-field="org.apache.ignite.events.EventType.EVT_TASK_REDUCED"/>
    
                <!--Cache events-->
                <util:constant static-field="org.apache.ignite.events.EventType.EVT_CACHE_OBJECT_PUT"/>
                <util:constant static-field="org.apache.ignite.events.EventType.EVT_CACHE_OBJECT_READ"/>
                <util:constant static-field="org.apache.ignite.events.EventType.EVT_CACHE_OBJECT_REMOVED"/>
            </list>
        </property>
    
        <!-- Explicitly configure TCP discovery SPI to provide list of initial nodes. -->
        <property name="discoverySpi">
            <bean class="org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi">
                <property name="ipFinder">
                    <!--
                        Ignite provides several options for automatic discovery that can be used
                        instead os static IP based discovery. For information on all options refer
                        to our documentation: http://apacheignite.readme.io/docs/cluster-config
                    -->
                    <!-- Uncomment static IP finder to enable static-based discovery of initial nodes. -->
                    <!--<bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder">-->
                    <bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.multicast.TcpDiscoveryMulticastIpFinder">
                        <property name="addresses">
                            <list>
                                <!-- In distributed environment, replace with actual host IP address. -->
                                <value>127.0.0.1:47500..47509</value>
                            </list>
                        </property>
                    </bean>
                </property>
            </bean>
        </property>
    
    </bean>
    

  2. Spring security configuration

    <beans:beans xmlns.... >
    <http auto-config="true" create-session="always" use-expressions="true" >
       <form-login login-page="/login" default-target-url="/home" authentication-failure-url="/login?error" username-parameter="username" password-parameter="password" always-use-default-target="true"/>
    
       <logout invalidate-session="true" logout-success-url="/login" delete-cookies="JSESSIONID"/>
    
       <session-management session-fixation-protection="newSession" invalid-session-url="/" session-authentication-error-url="/login">
           <concurrency-control session-registry-alias="sessionRegistry" max-sessions="10" expired-url="/" error-if-maximum-exceeded="true"/>
       </session-management>
    
       <access-denied-handler error-page="/403"/>
    </http>
    <authentication-manager>
       <authentication-provider user-service-ref="userDetailsService">
       </authentication-provider>
    </authentication-manager>
    </beans:beans>
    
  3. web.xml

    <web-app xmlns... >
    <listener>
        <listener-class>org.apache.ignite.startup.servlet.ServletContextListenerStartup</listener-class>
    </listener>
    
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <listener>
        <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
    </listener>
    
    <filter>
        <filter-name>IgniteWebSessionsFilter</filter-name>
        <filter-class>org.apache.ignite.cache.websession.WebSessionFilter</filter-class>
    </filter>
    
    <!-- You can also specify a custom URL pattern. -->
    <filter-mapping>
        <filter-name>IgniteWebSessionsFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    <!-- Specify Ignite configuration (relative to META-INF folder or Ignite_HOME). -->
    <context-param>
        <param-name>IgniteConfigurationFilePath</param-name>
        <param-value>example-ignite.xml</param-value>
    </context-param>
    
    <!-- Specify the name of Ignite cache for web sessions. -->
    <context-param>
        <param-name>IgniteWebSessionsCacheName</param-name>
        <param-value>example</param-value>
    </context-param>
    
    <!--SERVLETS-->
    <servlet>
        <servlet-name>mvc-dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:mvc-dispatcher-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>mvc-dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:mvc-dispatcher-servlet.xml,
            classpath:security-config.xml
        </param-value>
    </context-param>
    </web-app>
    

I would be very thankful if you could provide any solution / idea to solve problem.

1
Hi, please share your security config and Ignite Spring Session config.alexmagnus
Hi @alexfedotov thank you very much for the replying. I have updated the question with the file contents you asked. Meanwhile digging little deeper yesterday, I found out Requested session ID 2C3459CB61A89AD4108E733AC8A490F3 is invalid on spring debug log at the time Http 302 received. So I'm guessing it's something related to the httpSession and cookie stored in the browser. But still could not figure out what's actually happening. If you could provide any idea I would be very great full. Thank you very much.Yasitha Thilakaratne
Hi, please check the proposed answer. As well, please check if setting the session creation policy to ifRequired instead of always has any effect.alexmagnus
@YasithaThilakaratne in your Ignite configuration you have abstract="true", hence it shouldn't really have worked. Did you actually remove it previously?alamar
@alexfedotov I copied it from sample provided with apache Ignite. Actually it worked because I used it with <beans xmlns... /> <bean parent="ignite.cfg"/> </beans> (example-ignite.xml file) configuration. So it worked.Yasitha Thilakaratne

1 Answers

2
votes

As both of your deployments are on localhost, they're going to share JSESSIONID cookie. So far so good.

However there seems to be a mismatch between Tomcat, Spring Security and Ignite which causes Spring Security to regard sessions coming from Ignite as invalid when they're anonymous (i.e. not logged on). I don't understand yet how this relates to your case where you're supposed to already be logged on.

You can probably use a workaround: Remove invalid-session-url="/" from your Spring Security configuration. This will prevent the redirect looping behavior. This will also cause users to be silently logged off instead to being guided to /login when their cookie expires.

I've deleted my previous answer as it was missing the point.