You are viewing a plain text version of this content. The canonical link for it is here.
Posted to user@shiro.apache.org by Tamás Cservenák <ta...@cservenak.net> on 2011/02/17 15:01:10 UTC

Sessions and REST

Howdy,

few days ago Sonatype released Nexus 1.9, having one of the "biggest"
change in relation to security: finally updating from JSecurity
0.9-RC2 to Shiro 1.0-incubating.

It seemed all fine, but some behavior did change -- I have to
emphasize: it's not Shiro, but our "glue" (business logic) code to be
blamed -- the Sessions started behaving differently. Nexus has REST
Resources only (does not have UI, the UI is actually a "standalone" JS
application communicating over REST with Nexus), hence, no state
needed on Nexus side. But still, as we know, Shiro always creates
Sessions, and as it turned out, on heavily loaded systems, they would
not be cleaned up it timely fashion (yes, all was working nice, but
the load was higher than timeouts were occurring).

Here is an issue, where it's obvious that a Map of Shiro's
SimpleSession "eats" up the memory of Nexus:
https://issues.sonatype.org/browse/NEXUS-4118

I read some on this mailing least about Shiro + REST + session-less
mode, but concluded that Shiro 1.0-incubating does not offer
out-of-the-box solution for this. Later, I found something I believe
is related:

https://issues.apache.org/jira/browse/SHIRO-266

But is scheduled for Shiro 1.2.0.... bummer.

So, finally the "fix" (quickfix, circumvention, call it as you like) I
ended up was this:
https://sventon.sonatype.org/repos/nexus-oss/info?revision=7850

Simply, at the end of processing (filtering), we log out the subject
always. And it works fine for us.

The culprit was actually "simple" clients, that connect a lot and in
burst to nexus: Maven, wget, ant, etc, that does not pass over cookies
(are also stateless!), and one artifact request from maven (which
boils down to 4 HTTP requests: POM, JAR and SHA1 files for both of
them) resulted in 4 session creation. And in case above (NEXUS-4118),
it is a highly loaded instance, so it received a "huge burst" of
download requests, when it cracked. Simply, the session cleanup was
not being able to catch up.

I just wanted to share this with you guys, and ask for opinions. Les
proposed some alternative solution in comments of NEXUS-4118 too.

--

And finally, we are about to release Nexus 1.9.0.1 to address this issue ;)


Thanks,
~t~

Re: Sessions and REST

Posted by Les Hazlewood <lh...@apache.org>.
This is a helpful thread - thanks for posting the code Jared.

This last bit of code is probably what I would consider a preferred
solution (until we can address this in the framework via SHIRO-266)
because it completely bypasses the default SessionManagement
infrastructure - the sessionDAO is never accessed, nor a session cache
(or overflow to disk involving file IO), nor is there a need for a
session validation thread to start up for orphan sessions (there will
never be an orphan).

The only caveat here is that you're pretty much forced to abandon a
'real' session environment in your application - which is perfectly
fine of course for 100% RESTful applications.  SHIRO-266 will
hopefully address this to allow people to turn on or off this feature
as needed, even at runtime (e.g. REST request -> HttpRequestSession,
MVC request -> normal Session).

Cheers,

-- 
Les Hazlewood
Founder, Katasoft, Inc.
Application Security Products & Professional Apache Shiro Support and Training:
http://www.katasoft.com

On Thu, Feb 17, 2011 at 7:23 AM, Jared Bunting
<ja...@digitalreasoning.com> wrote:
> Here's the code.  Two pretty simple classes.  The one thing that I
> wasn't sure about was the timeout methods in the session - I'm fairly
> certain that they're irrelevant in this situation, so I've basically not
> implemented them.  Hopefully the wrapping doesn't screw this up too much
> - if there's somewhere better to post this stuff, please let me know.
>
> Thanks,
> Jared
>
> /**
>  * Intended to keep session request-scoped and therefore not persist
> them across multiple requests - a user must login
>  * on each request. This necessarily means that a mechanism like
> form-based authentication isn't viable, but the
>  * intention is primarily for uses in stateless apis.
>  */
> public class HttpRequestSessionManager implements SessionManager
> {
>
>        static final String REQUEST_ATTRIBUTE_KEY = "__SHIRO_REQUEST_SESSION";
>
>        @Override
>        public Session start(SessionContext context) throws AuthorizationException
>        {
>                if (!WebUtils.isHttp(context))
>                {
>                        String msg = "SessionContext must be an HTTP compatible implementation.";
>                        throw new IllegalArgumentException(msg);
>                }
>
>                HttpServletRequest request = WebUtils.getHttpRequest(context);
>
>                String host = getHost(context);
>
>                Session session = createSession(request, host);
>                request.setAttribute(REQUEST_ATTRIBUTE_KEY, session);
>
>                return session;
>        }
>
>        @Override
>        public Session getSession(SessionKey key) throws SessionException
>        {
>                if (!WebUtils.isHttp(key))
>                {
>                        String msg = "SessionKey must be an HTTP compatible implementation.";
>                        throw new IllegalArgumentException(msg);
>                }
>
>                HttpServletRequest request = WebUtils.getHttpRequest(key);
>
>                return (Session) request.getAttribute(REQUEST_ATTRIBUTE_KEY);
>        }
>
>        private String getHost(SessionContext context)
>        {
>                String host = context.getHost();
>                if (host == null)
>                {
>                        ServletRequest request = WebUtils.getRequest(context);
>                        if (request != null)
>                        {
>                                host = request.getRemoteHost();
>                        }
>                }
>                return host;
>
>        }
>
>        protected Session createSession(HttpServletRequest request, String host)
>        {
>                return new HttpServletRequestSession(request, host);
>        }
>
> }
>
> /**
>  * Session that is only tied to an HttpServletRequest. This can be used
> for applications that prefer to remain stateless.
>  */
> public class HttpServletRequestSession implements Session
> {
>        private HttpServletRequest request;
>        private String host;
>        private UUID uuid;
>        private Date start;
>
>        public HttpServletRequestSession(HttpServletRequest request, String host)
>        {
>                this.request = request;
>                this.host = host;
>                this.uuid = UUID.randomUUID();
>                this.start = new Date();
>        }
>
>        @Override
>        public Serializable getId()
>        {
>                return uuid;
>        }
>
>        @Override
>        public Date getStartTimestamp()
>        {
>                return start;
>        }
>
>        @Override
>        public Date getLastAccessTime()
>        {
>                // the user only makes one request that involves this session
>                return start;
>        }
>
>        @Override
>        public long getTimeout() throws InvalidSessionException
>        {
>                return -1;
>        }
>
>        @Override
>        public void setTimeout(long maxIdleTimeInMillis) throws
> InvalidSessionException
>        {
>                // ignore this - the session ends with the request and that's that...
>        }
>
>        @Override
>        public String getHost()
>        {
>                return host;
>        }
>
>        @Override
>        public void touch() throws InvalidSessionException
>        {
>                // do nothing - we don't timeout
>        }
>
>        @Override
>        public void stop() throws InvalidSessionException
>        {
>                // do nothing - i don't have a use case for this and the structure to
> support it, while not huge, adds
>                // significant complexity
>        }
>
>        @SuppressWarnings( { "unchecked" })
>        @Override
>        public Collection<Object> getAttributeKeys() throws InvalidSessionException
>        {
>                return EnumerationUtils.toList(request.getAttributeNames());
>        }
>
>        @Override
>        public Object getAttribute(Object key) throws InvalidSessionException
>        {
>                return request.getAttribute(stringify(key));
>        }
>
>        @Override
>        public void setAttribute(Object key, Object value) throws
> InvalidSessionException
>        {
>                request.setAttribute(stringify(key), value);
>        }
>
>        @Override
>        public Object removeAttribute(Object objectKey) throws
> InvalidSessionException
>        {
>                String key = stringify(objectKey);
>                Object formerValue = request.getAttribute(key);
>                request.removeAttribute(key);
>                return formerValue;
>        }
>
>        private String stringify(Object key)
>        {
>                return key == null ? null : key.toString();
>        }
> }
>
>
>
> On 02/17/2011 09:12 AM, Tamás Cservenák wrote:
>> Hi Jared,
>>
>> yes, that's my conclusion -- at least in REST world -- that Shiro
>> sessions are <em>great</em> benefit for Java systems (from Javadoc
>> ;) and I do want to use them. And what you did intrigues my
>> imagination, since in "my" solution, a lot of moving parts (Http
>> Servlet container, etc) are triggered just for nothing, to achieve
>> exactly what you did: to have session lifespan equal to the
>> request's.
>>
>> This is something Shiro should support IMO.
>>
>> +1 for seeing that piece of code :)
>>
>>
>> Thanks, ~t~
>>
>> On Thu, Feb 17, 2011 at 3:30 PM, Jared Bunting
>> <ja...@digitalreasoning.com> wrote:
>>> Tamás,
>>>
>>> I've encountered the same problem and approached it in a slightly
>>> different way.  Basically, I created my own implementation of
>>> SessionManager and Session that are backed only by the
>>> HttpServletRequest.  Each session's lifecycle is tied to the
>>> request.
>>>
>>> I'd also be interested in criticism of this approach, or if anyone
>>> is interested in seeing the code I'd be happy to share it.
>>>
>>> Thanks, Jared

Re: Sessions and REST

Posted by Jared Bunting <ja...@digitalreasoning.com>.
Here's the code.  Two pretty simple classes.  The one thing that I
wasn't sure about was the timeout methods in the session - I'm fairly
certain that they're irrelevant in this situation, so I've basically not
implemented them.  Hopefully the wrapping doesn't screw this up too much
- if there's somewhere better to post this stuff, please let me know.

Thanks,
Jared

/**
 * Intended to keep session request-scoped and therefore not persist
them across multiple requests - a user must login
 * on each request. This necessarily means that a mechanism like
form-based authentication isn't viable, but the
 * intention is primarily for uses in stateless apis.
 */
public class HttpRequestSessionManager implements SessionManager
{

	static final String REQUEST_ATTRIBUTE_KEY = "__SHIRO_REQUEST_SESSION";

	@Override
	public Session start(SessionContext context) throws AuthorizationException
	{
		if (!WebUtils.isHttp(context))
		{
			String msg = "SessionContext must be an HTTP compatible implementation.";
			throw new IllegalArgumentException(msg);
		}

		HttpServletRequest request = WebUtils.getHttpRequest(context);

		String host = getHost(context);

		Session session = createSession(request, host);
		request.setAttribute(REQUEST_ATTRIBUTE_KEY, session);

		return session;
	}

	@Override
	public Session getSession(SessionKey key) throws SessionException
	{
		if (!WebUtils.isHttp(key))
		{
			String msg = "SessionKey must be an HTTP compatible implementation.";
			throw new IllegalArgumentException(msg);
		}

		HttpServletRequest request = WebUtils.getHttpRequest(key);

		return (Session) request.getAttribute(REQUEST_ATTRIBUTE_KEY);
	}

	private String getHost(SessionContext context)
	{
		String host = context.getHost();
		if (host == null)
		{
			ServletRequest request = WebUtils.getRequest(context);
			if (request != null)
			{
				host = request.getRemoteHost();
			}
		}
		return host;

	}

	protected Session createSession(HttpServletRequest request, String host)
	{
		return new HttpServletRequestSession(request, host);
	}

}

/**
 * Session that is only tied to an HttpServletRequest. This can be used
for applications that prefer to remain stateless.
 */
public class HttpServletRequestSession implements Session
{
	private HttpServletRequest request;
	private String host;
	private UUID uuid;
	private Date start;

	public HttpServletRequestSession(HttpServletRequest request, String host)
	{
		this.request = request;
		this.host = host;
		this.uuid = UUID.randomUUID();
		this.start = new Date();
	}

	@Override
	public Serializable getId()
	{
		return uuid;
	}

	@Override
	public Date getStartTimestamp()
	{
		return start;
	}

	@Override
	public Date getLastAccessTime()
	{
		// the user only makes one request that involves this session
		return start;
	}

	@Override
	public long getTimeout() throws InvalidSessionException
	{
		return -1;
	}

	@Override
	public void setTimeout(long maxIdleTimeInMillis) throws
InvalidSessionException
	{
		// ignore this - the session ends with the request and that's that...
	}

	@Override
	public String getHost()
	{
		return host;
	}

	@Override
	public void touch() throws InvalidSessionException
	{
		// do nothing - we don't timeout
	}

	@Override
	public void stop() throws InvalidSessionException
	{
		// do nothing - i don't have a use case for this and the structure to
support it, while not huge, adds
		// significant complexity
	}

	@SuppressWarnings( { "unchecked" })
	@Override
	public Collection<Object> getAttributeKeys() throws InvalidSessionException
	{
		return EnumerationUtils.toList(request.getAttributeNames());
	}

	@Override
	public Object getAttribute(Object key) throws InvalidSessionException
	{
		return request.getAttribute(stringify(key));
	}

	@Override
	public void setAttribute(Object key, Object value) throws
InvalidSessionException
	{
		request.setAttribute(stringify(key), value);
	}

	@Override
	public Object removeAttribute(Object objectKey) throws
InvalidSessionException
	{
		String key = stringify(objectKey);
		Object formerValue = request.getAttribute(key);
		request.removeAttribute(key);
		return formerValue;
	}

	private String stringify(Object key)
	{
		return key == null ? null : key.toString();
	}
}



On 02/17/2011 09:12 AM, Tamás Cservenák wrote:
> Hi Jared,
> 
> yes, that's my conclusion -- at least in REST world -- that Shiro 
> sessions are <em>great</em> benefit for Java systems (from Javadoc
> ;) and I do want to use them. And what you did intrigues my
> imagination, since in "my" solution, a lot of moving parts (Http
> Servlet container, etc) are triggered just for nothing, to achieve
> exactly what you did: to have session lifespan equal to the
> request's.
> 
> This is something Shiro should support IMO.
> 
> +1 for seeing that piece of code :)
> 
> 
> Thanks, ~t~
> 
> On Thu, Feb 17, 2011 at 3:30 PM, Jared Bunting 
> <ja...@digitalreasoning.com> wrote:
>> Tamás,
>> 
>> I've encountered the same problem and approached it in a slightly 
>> different way.  Basically, I created my own implementation of 
>> SessionManager and Session that are backed only by the 
>> HttpServletRequest.  Each session's lifecycle is tied to the
>> request.
>> 
>> I'd also be interested in criticism of this approach, or if anyone
>> is interested in seeing the code I'd be happy to share it.
>> 
>> Thanks, Jared
>> 
>> 



Re: Sessions and REST

Posted by Tamás Cservenák <ta...@cservenak.net>.
Hi Jared,

yes, that's my conclusion -- at least in REST world -- that Shiro
sessions are <em>great</em> benefit for Java systems (from Javadoc ;)
and I do want to use them. And what you did intrigues my imagination,
since in "my" solution, a lot of moving parts (Http Servlet container,
etc) are triggered just for nothing, to achieve exactly what you did:
to have session lifespan equal to the request's.

This is something Shiro should support IMO.

+1 for seeing that piece of code :)


Thanks,
~t~

On Thu, Feb 17, 2011 at 3:30 PM, Jared Bunting
<ja...@digitalreasoning.com> wrote:
> Tamás,
>
> I've encountered the same problem and approached it in a slightly
> different way.  Basically, I created my own implementation of
> SessionManager and Session that are backed only by the
> HttpServletRequest.  Each session's lifecycle is tied to the request.
>
> I'd also be interested in criticism of this approach, or if anyone is
> interested in seeing the code I'd be happy to share it.
>
> Thanks,
> Jared
>
>

Re: Sessions and REST

Posted by Jared Bunting <ja...@digitalreasoning.com>.
Tamás,

I've encountered the same problem and approached it in a slightly
different way.  Basically, I created my own implementation of
SessionManager and Session that are backed only by the
HttpServletRequest.  Each session's lifecycle is tied to the request.

I'd also be interested in criticism of this approach, or if anyone is
interested in seeing the code I'd be happy to share it.

Thanks,
Jared