Jen-Ming Chung

Apache Shiro NullPointerException After Logout on GlassFish 4.1

GlassFish  stuffed fish (Photo Credit - Michael Coté)

I’m currently working on a web application on Java EE7 stack and I’ve integrated Apache Shiro with CAS for security. Everything goes well, except the GlassFish 4.1 (build 13) server logs keep getting filled with following errors when calling logout() method:

Info:   Session event listener threw exception
java.lang.NullPointerException
    at org.jboss.weld.servlet.WeldTerminalListener.getSessionContext(WeldTerminalListener.java:65)
    at org.jboss.weld.servlet.WeldTerminalListener.sessionDestroyed(WeldTerminalListener.java:57)
    at org.apache.catalina.session.StandardSession.expire(StandardSession.java:910)
    at org.apache.catalina.session.StandardSession.expire(StandardSession.java:854)
    at org.apache.catalina.session.StandardSession.expire(StandardSession.java:842)
    at org.apache.catalina.session.StandardSession.invalidate(StandardSession.java:1603)
    at org.apache.catalina.session.StandardSessionFacade.invalidate(StandardSessionFacade.java:204)
    at org.apache.shiro.web.session.HttpServletSession.stop(HttpServletSession.java:113)
    at org.apache.shiro.session.ProxiedSession.stop(ProxiedSession.java:107)
    at org.apache.shiro.subject.support.DelegatingSubject$StoppingAwareProxiedSession.stop(DelegatingSubject.java:419)
    at org.apache.shiro.mgt.DefaultSecurityManager.stopSession(DefaultSecurityManager.java:581)
    at org.apache.shiro.mgt.DefaultSecurityManager.logout(DefaultSecurityManager.java:567)
    at org.apache.shiro.subject.support.DelegatingSubject.logout(DelegatingSubject.java:363)
    at org.apache.shiro.web.filter.authc.LogoutFilter.preHandle(LogoutFilter.java:71)
    at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:131)
    at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
    at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66)
    at org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:449)
    at org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:365)
    at org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90)
    at org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83)
    at org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:383)
    at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:362)
    at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:256)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:214)
    at org.jasig.cas.client.session.SingleSignOutFilter.doFilter(SingleSignOutFilter.java:76)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:256)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:214)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:316)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:160)
    at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:734)
    at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:673)
    at com.sun.enterprise.web.WebPipeline.invoke(WebPipeline.java:99)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:174)
    at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:415)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:282)
    at com.sun.enterprise.v3.services.impl.ContainerMapper$HttpHandlerCallable.call(ContainerMapper.java:459)
    at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:167)
    at org.glassfish.grizzly.http.server.HttpHandler.runService(HttpHandler.java:201)
    at org.glassfish.grizzly.http.server.HttpHandler.doHandle(HttpHandler.java:175)
    at org.glassfish.grizzly.http.server.HttpServerFilter.handleRead(HttpServerFilter.java:235)
    at org.glassfish.grizzly.filterchain.ExecutorResolver$9.execute(ExecutorResolver.java:119)
    at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeFilter(DefaultFilterChain.java:284)
    at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeChainPart(DefaultFilterChain.java:201)
    at org.glassfish.grizzly.filterchain.DefaultFilterChain.execute(DefaultFilterChain.java:133)
    at org.glassfish.grizzly.filterchain.DefaultFilterChain.process(DefaultFilterChain.java:112)
    at org.glassfish.grizzly.ProcessorExecutor.execute(ProcessorExecutor.java:77)
    at org.glassfish.grizzly.nio.transport.TCPNIOTransport.fireIOEvent(TCPNIOTransport.java:561)
    at org.glassfish.grizzly.strategies.AbstractIOStrategy.fireIOEvent(AbstractIOStrategy.java:112)
    at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.run0(WorkerThreadIOStrategy.java:117)
    at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.access$100(WorkerThreadIOStrategy.java:56)
    at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy$WorkerThreadRunnable.run(WorkerThreadIOStrategy.java:137)
    at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:565)
    at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.run(AbstractThreadPool.java:545)
    at java.lang.Thread.run(Thread.java:745)

There are also differences among different application servers such as GlassFish 4.1 and WildFly 8.1.0.Final integrate with Apache Shiro 1.2.3. This brings two problems: the NullPointerException (NPE) and the logout will not work properly.

Different Ways to Logout

In web environments, Shiro’s default session manager SessionManager implementation is the ServletContainerSessionManager. This very simple implementation delegates all session management duties (including session clustering if the servlet container supports it) to the runtime Servlet container.

A benefit of using this default is that apps that work with existing servlet container session configuration (timeout, any container-specific clustering mechanisms, etc) will work as expected.

A downside of this default is that you are tied to the servlet container’s specific session behavior.

Ideally, when we explicitly call the Shiro’s SecurityUtil.getSubject().logout() or javax.servlet.http.HttpSession.invalidate() will trigger the Shiro’s Session.stop to invalidate this session and releases all associated resources. However, I got inconsistent outcomes among the different Java EE application servers. Let NPE denotes the session event listener threw NullPointerException, NWP stands for logout not working properly and FUN represents the logout process is functional, which means the current subject will become unauthenticated. Subsequently, we evaluate several logout ways to invalidate the session, let SESS_INVAL denotes HttpServletRequest.getSession().invalidate(), REQ_LOGOUT for HttpServletRequest.logout() and SEC_LOGOUT represents Shiro’s SecurityUtils.getSubject().logout(). Here are the results of several combinations of application servers and logout approaches.

SESS_INVAL REQ_LOGOUT SEC_LOGOUT
GlassFish 4.1 NPE / FUN NWP NPE / FUN
WildFly 8.1.0.Final FUN NWP FUN

Table 1: The results of different logout approaches among Java EE application servers.

Workaround Solution

The following context is derived from the authenticated session:

Info:   org.jboss.weld.context.conversation.ConversationIdGenerator >> org.jboss.weld.context.conversation.ConversationIdGenerator@45fcb33c
Info:   org.jboss.weld.context.ConversationContext.conversations >> {}
Info:   org.apache.shiro.subject.support.DefaultSubjectContext_AUTHENTICATED_SESSION_KEY >> true
Info:   org.apache.shiro.web.session.HttpServletSession.HOST_SESSION_KEY >> 127.0.0.1
Info:   org.apache.shiro.subject.support.DefaultSubjectContext_PRINCIPALS_SESSION_KEY >> johnsmith@xyz.com,{}

Our idea is to remove attributes with regard to Shiro (i.e., org.apache.shiro.*) from the authenticated-session to avoid raising NullPointerException. Here is an example a logout-servlet to prevent the exception from session event listener in servlet containers.

LogoutServlet.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class LogoutServlet extends HttpServlet {
    ...
    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        ...
        HttpSession session = request.getSession();
        for (Enumeration e = session.getAttributeNames(); e.hasMoreElements();) {
            String attribName = (String) e.nextElement();
            if (attribName.startsWith("org.apache.shiro")) {
                session.removeAttribute(attribName);
            }
        }

        response.sendRedirect(https://cas.somewhere.com/logout?service=http://myapp.com);

    }
    ...

}

(Photo via Michael Coté, CC License)

Comments