spring-session icon indicating copy to clipboard operation
spring-session copied to clipboard

Support persistent cookies for storing session ID

Open theigl opened this issue 1 year ago • 1 comments

Expected Behavior

We would like to store the session ID in a cookie with an expiration date that matches the session timeout. The cookie expiration date should be updated every time the user accesses the session.

Current Behavior

The session ID is stored in a session cookie by default. It is possible to override the cookie's maxAge in CookieSerializer, but there is no way to refresh the cookie whenever the session is accessed and the cookie would simply expire after maxAge, even if the session is still actively used.

Context

We are using Capacitor to bundle our web application as a native app. Many users are closing the application instead of pausing it/moving it to the background, only to re-open it a minute later. Every time the app is closed, all session cookies are cleared and the user is associated with a new session. Using a persistent cookie that is extended on every session access, would be an elegant solution to this issue.

Possible workarounds:

  • Set a very long expiration date on the session cookie (e.g. 24h) and hope that no user uses the app around the clock
  • Manually extend the session cookie in response to user actions or a globally in a filter

theigl avatar Jul 15 '24 09:07 theigl

I created a draft PR that shows what changes would be necessary to support my use case: https://github.com/spring-projects/spring-session/pull/3098

theigl avatar Jul 15 '24 09:07 theigl

Hello @theigl. I'd like to know more about your use case and what you've tried.

Have you tried to do defaultCookieSerializer.setCookieMaxAge(Integer.MAX_VALUE);? This way the cookie expiration time is long and you rely only on the session expiration time.

Manually extend the session cookie in response to user actions or a globally in a filter

This can probably be achieved with a custom filter, have you tried it? I'm unsure if you want to write the Set-Cookie header back on every request the client makes.

marcusdacoregio avatar Aug 05 '24 17:08 marcusdacoregio

Hi @marcusdacoregio!

Have you tried to do defaultCookieSerializer.setCookieMaxAge(Integer.MAX_VALUE);? This way the cookie expiration time is long and you rely only on the session expiration time.

This approach would work, but it feels a bit wrong. My session timeout is set to 30 minutes, so setting a persistent cookie with a very long lifetime will lead to a lot of requests for sessions that definitely do not exist anymore.

This can probably be achieved with a custom filter, have you tried it?

I did a quick prototype but soon realized that this is not trivial to implement. The filter has to be added after the session repository filter and it has to handle the case where the session is created or invalidated in the same request. There is no API for retrieving cookies from the HttpServletResponse so I would need to set some attribute on the request to know that the session has been created or invalidated or wrap the response and expose the cookies that have been added.

I'm unsure if you want to write the Set-Cookie header back on every request the client makes.

I would probably not set in on every request but check the cookie expiration date and only extend it if at least a minute has passed since the last extension.

theigl avatar Aug 06 '24 16:08 theigl

This approach would work, but it feels a bit wrong. My session timeout is set to 30 minutes, so setting a persistent cookie with a very long lifetime will lead to a lot of requests for sessions that definitely do not exist anymore.

I don't follow what you mean, if the session is expired the request will fail and a new session will be created for the client. It looks better than creating multiple sessions if the user closes the app.

I don't think it is trivial for the API to support that either, while it might make sense for your use case I don't feel yet that it is the right place to do it.

There is no API for retrieving cookies from the HttpServletResponse

You can retrieve the cookies from the HttpServletRequest or you can retrieve the Set-Cookie headers from the response.

Have you tried to do something like:

public class SessionExtenderFilter extends OncePerRequestFilter {

	private final Clock clock = Clock.systemUTC();

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
		filterChain.doFilter(request, response);
		HttpSession session = request.getSession(false);
		if (session == null) {
			return;
		}
		for (Cookie cookie : request.getCookies()) {
			if ("SESSION".equals(cookie.getName())) {
				extendSessionCookieIfNeeded(session, response, cookie);
			}
		}
	}

	private void extendSessionCookieIfNeeded(HttpSession session, HttpServletResponse response, Cookie cookie) {
		// you can also check any other attribute from the session here
		int maxAge = cookie.getMaxAge();
		Instant expires = this.clock.instant().plusSeconds(maxAge);
		Instant now = this.clock.instant();
		if (ChronoUnit.SECONDS.between(now, expires) < 60) {
			cookie.setMaxAge(1800);
			response.addCookie(cookie);
		}
	}

}

I would place this filter before the SessionRepositoryFilter since it will run after the response is returned.

marcusdacoregio avatar Aug 09 '24 17:08 marcusdacoregio

If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.

spring-projects-issues avatar Aug 16 '24 18:08 spring-projects-issues

Thanks @marcusdacoregio. I think that will work for our use case. Thanks for taking the time to suggesting a workable solution!

theigl avatar Aug 22 '24 11:08 theigl