Overview

This plugin provides the logic required to manage sessions for the users of your application.

In summary, a session is an object containing informations about a particular user. Those informations can be saved in the application and later associated back to the user when he performs a new request. The default mechanism used to be able to bind such informations to a specific user is to save a "session id" in a cookie, on the user.

This plugin is mostly useful for your application specific needs, but it can also be used by other plugins that need to keep informations about users. For example, the Spincast Forms Protection plugin uses the sessions provided by this plugin to store "CSRF" tokens required to protect forms, in your application.

Usage

Getting the session of the current visitor

Once the plugin and its associated filters have been properly installed, you simply need to use the SpincastSessionManager to manage sessions.

The most important method provided by this manager is getCurrentSession(), which gives you access to the session of the current visitor:

public class MyClass {

    private final SpincastSessionManager spincastSessionManager;
    
    @Inject
    public MyClass(SpincastSessionManager spincastSessionManager) {
        this.spincastSessionManager = spincastSessionManager;
    }
    
    protected SpincastSessionManager getSpincastSessionManager() {
        return this.spincastSessionManager;
    }
    
    public void myRouteHandler(AppRequestContext context) {
        SpincastSession currentSession = getSpincastSessionManager().getCurrentSession();  
        Long userId = currentSession.getAttributes().getLong("userId");
        if(userId != null) {
            User user = getUserService().getUser(userId);
        }
        
        // ....
    }
}

Explanation :

  • 15 : Gets the session of the current visitor.
  • 16 : Gets an information you associated with this session (here a "user id"). The informations saved in the session are named "attributes" and they are implemented using a JsonObject.

Note that once the plugin has been installed properly, a session returned by SpincastSessionManager#getCurrentSession() is never null! If no session already exists for the current visitor, a new one is automatically created.

Storing and retrieving session attributes

Since the attributes of a session are accessible on a JsonObject object, getting them in a typed way is really easy. Adding new ones too. You simply call getAttributes() on the session to have access to them.

Note that an after filter will take care of saving a session that has been modified during a request, you don't have to do it manually!

Getting the current session using an add-on

If you use sessions a lot in your application, we suggest you add a "session()" add-on to your custom Request Context!

By adding a new "session()" method pointing to SpincastSessionManager#getCurrentSession(), you get easy access to the current session in your routes handlers:

public void myRouteHandler(AppRequestContext context) {

    Long userId = context.session().getAttributes().getLong("userId");
    if(userId != null) {
        User user = getUserService().getUser(userId);
    }
    
    // ....
}

Dependencies

The Spincast Session plugin depends on the Spincast Scheduled Tasks plugin, a plugin which is not provided by default by the spincast-default package. This dependency plugin will be automatically installed, you don't need to install it by yourself in your application (but you can).

Just don't be surprised if you see transitive dependencies being added to your application!

Installation

The installation of this plugin requires a couple of steps. To work properly, it needs a custom implementation of the SpincastSessionRepository to be bound in the Guice context and needs two filters to be added to your router.

But let's start with step one...

Installing the plugin itself

1. Add this Maven artifact to your project:

<dependency>
    <groupId>org.spincast</groupId>
    <artifactId>spincast-plugins-session</artifactId>
    <version>0.9.52</version>
</dependency>

2. Add an instance of the SpincastSessionPlugin plugin to your Spincast Bootstrapper:


Spincast.configure()
        .plugin(new SpincastSessionPlugin())
        // ...

Adding the provided filters to your router

You need to add to the router of your application two filters provided by the plugin:

  • The first one, a before filter, SpincastSessionFilter#before, will make sure the session of the current visitor is loaded and accessible to your application.
  • The second one, an after filter, SpincastSessionFilter#after, will make sure a dirty session (a session which has been modified) will be saved in your database, once the request is over.

For example, in the class where you add the filters for your application:

@Inject
SpincastSessionFilter spincastSessionFilter;

// "Before" filter
router.ALL().pos(-1000).skipResourcesRequests().handle(spincastSessionFilter::before);

// "After" filter
router.ALL().pos(100).skipResourcesRequests().handle(spincastSessionFilter::after);

Those filters are not automatically added to the router by the plugin (even if they could). We decided to do it this way because in our opinion it is clearer when pretty much all the filters of an application are grouped together... Therefore, you are responsible to add them to the router and to choose which "positions" they must be added to, considering your other filters.

In a typical application, a filter that is often created is one to retrieve a logged in User from the database and add it to the request's variables, so it is easily accessible to the application's code.

Such UserFilter would be added at a position after the spincastSessionFilter::before filter! Indeed, the UserFilter would use the session of the current visitor to determine if he is a logged in user or not and, if so, what is his "userId" (an attribute saved in the session!).

Providing a repository implementation

You need to bind an implementation of the SpincastSessionRepository, in the Guice context. The reason is that this plugin is totally agnostic on how the sessions are actually persisted. You decide how and where to save them...

In general, you are going to save them in the same database/data source that your application already uses.

There are four methods to implement:

  • void saveSession(SpincastSession session)

    This method will be called by the plugin when a current session needs to be saved to the database.

    Note that the modification date of the session to save is already updated when this method is called! You don't have to update it by yourself, manually or using a database trigger.

  • SpincastSession getSession(String sessionId)

    This method will be called by the plugin to retrieve a session saved in the database.

    It must return a SpincastSession instance, or null if it's not found.

  • void deleteSession(String sessionId)

    This method will be called by the plugin to delete a session.

    If no session with this sessionId exists, nothing is done.

  • void deleteOldInactiveSession(int sessionMaxInactiveMinutes)

    This method will be called by the plugin to delete sessions that have been inactive for more than sessionMaxInactiveMinutes minutes.

    Your query/code must use the modification date of the saved sessions to determine which sessions to delete.

SQL/JDBC example

Here's an example of an SQL script (targeting PostgreSQL) to create a "sessions" table. You will probably have to adjust this query for your own database! Or even to use something totally different if you are not using a relational database.

CREATE TABLE sessions (
    session_id VARCHAR(36) PRIMARY KEY,
    attributes_serialized TEXT, 
    creation_date TIMESTAMPTZ NOT NULL DEFAULT NOW(), 
    modification_date TIMESTAMPTZ NOT NULL DEFAULT NOW()
)

To serialize and deserialize the session's attributes, you can simply use the JsonObject's toJsonString() and JsonManager's fromString():

Saving a session to the database:

InsertStatement stm = jdbc().statements().createInsertStatement(connection);
stm.sql("INSERT INTO sessions(  // ...
stm.setString("attributes_serialized", session.getAttributes().toJsonString());

Retrieving a session from the database:

SelectStatement stm = jdbc().statements().createSelectStatement(connection);
stm.sql("SELECT session_id, // ...

JsonObject attributes = 
    getJsonManager().fromString(rs.getString("attributes_serialized"));

SpincastSession session =
        getSpincastSessionManager().createSession(rs.getString("session_id"),
                                                  rs.getInstant("creation_date"),
                                                  rs.getInstant("modification_date"),
                                                  attributes);

Configurations

The configuration interface for this plugin is SpincastSessionConfig. To change the default configurations, you can bind an implementation of that interface, extending the default SpincastSessionConfigDefault implementation if you don't want to start from scratch.

Options:

  • int getSessionMaxInactiveMinutes()

    The number of minutes an existing session can stay inactive (not being used) before it is garbage collected by the cleanup scheduled task.

    If a user with a saved session doesn't visit your application for this long, his session will be lost.

    Defaults to 10080 minutes (7 days).

  • int getDeleteOldSessionsScheduledTaskRunEveryNbrMinutes()

    How often should the scheduled task to delete the old sessions (by calling the deleteOldInactiveSession() method that you implemented) run? This configuration returns the number of minutes between two launches of the cleanup scheduled task.

    Defaults to 30 minutes.

  • int getUpdateNotDirtySessionPeriodInSeconds()

    The after filter provided by the plugin (and that you added to your router!) doesn't save a session that is not dirty on each and every request, simply to update its modification date ... This wouldn't be very efficient. The filter rather saves a session that is not dirty only once during the period specified by this configuration.

    Important! This period must be shorter than the period specified by getSessionMaxInactiveMinutes() or otherwise some active sessions may be deleted!

    Defaults to 300 seconds (5 minutes).

  • String getSessionIdCookieName()

    The name of the cookie that is used to store the session, on the user.

    Defaults to "spincast_sid".