4

I am currently a budding Java developer that wants to get into Vaadin development and currently trying to implement User Session login for my application. I have read about the content regarding the usage of VaadinServlets to do so : https://vaadin.com/docs/v10/flow/advanced/tutorial-application-lifecycle.html.

After relentlessly digging through API documentations and sample codes, I still am not able to understand how to implement the User Sessions for a specific user that logs into my platform. From what I understand is that, I can initialize my user session by using what i have implemented below.

However my aims for the application are slightly different:

[Use Case]

1.User logs in with their specific credentials.

2.Gets Redirected to a SecuredPage (which will create a User Session storing the user's username and retrieves a token?)

3.After 2-3mins of inactivity, the User will get forced out of the SecuredPage and Session closes?

@WebServlet(urlPatterns = "/*", name = "VaadinFlowServlet", asyncSupported = true)
@VaadinServletConfiguration(heartbeatInterval = 5, productionMode = false)
public class LoginServlet extends VaadinServlet implements SessionInitListener, SessionDestroyListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(LoginServlet.class);

    // <Method> ::servletInitialized():: -> handles most of the servlet customization. (write my servlet customization under this function.
    //          ::getService()::         -> returns a VaadinServletService type?
    //          ::addSessionInitListener(this)::    -> An event listener that can be registered to a VaadinService to get an event -> when a new Vaadin service session is initialized for that service.
    //          ::addSessionDestroyListener(this):: -> A listener that gets notified when a Vaadin service session is no longer used.
    @Override
    protected void servletInitialized() throws ServletException {
        super.servletInitialized();
        getService().addSessionInitListener(this);
        getService().addSessionDestroyListener(this);

    }

    // <Method>     ::sessionInit::      -> Starts Session?
    // <Parameter>  ::SessionInitEvent:: -> Event gets fired when a new Vaadin service session is initialized for a Vaadin service.
    @Override
    public void sessionInit(SessionInitEvent event) throws ServiceException{
        // Do Session start stuff here
        // Creates a Session?

        LOGGER.info("session init() "
                + " Session-ID: " + event.getSession().getSession().getId()
                + " CSRF: " + event.getSession().getCsrfToken());

    }

    // <Method>     ::sessionDestroy:: -> Stops Session?
    // <Parameter>  ::SessionDestroyEvent:: -> Event fired when a Vaadin service session is no longer in use.
    @Override
    public void sessionDestroy(SessionDestroyEvent event) {
        // Do session end stuff here
        LOGGER.info("session destory()");
    }

    }

1 So I was wondering if anybody can help me understand this matter better? Fully Appreciated

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154

1 Answers1

6

tl;dr

The mere existence of a custom-defined user-login object stored as an attribute in the key-value store of your VaadinSession represents the user having successfully authenticated. No need for all the session-listener code you wrote.

Let Vaadin do the heavy-lifting

I suspect you are working too hard.

There is no need for your session listeners. Vaadin handles nearly all the Java Servlet details on our behalf.

No need for the redirects. As a Vaadin developer, you are in full control of the content displayed in the browser tab/window, so you can switch between login form and main app content. Caveat: I am new to the @Route feature in Vaadin Flow, so there may be a niftier way with that feature to flip between login and main-content. And if you are using @Route for multiple views, each of those views should test for the authentication as described below.

VaadinSession

At the entry point of your Vaadin app code, retrieve the current VaadinSession object. This VaadinSession is a wrapper around the javax.servlet.http.HttpSession class defined by the Java Servlet spec. Vaadin automatically instantiates a session when the user’s browser first connects to your Vaadin web app (actually, Vaadin wraps the session instantiated by your web container). The session is automatically closed when the browser closes its tab/window, a time-out of inactivity occurs, or you programmatically close the session.

VaadinSession vaadinSession = VaadinSession.getCurrent() ; 

Session attributes (key-value store)

Interrogate that session object’s key-value store known as “attributes”. The key is of type String and the value is of type Object (the superclass of all Java objects). After retrieving the Object object, you cast to the known class. You know the class, because it is your code that stored the attribute.

Your user-login class

You would have defined a class to store your user-login related info. Perhaps you named it UserLogin.

Something like:

public class UserLogin {
    // Member values.
    String userName ;
    Instant whenAuthenticated ;

    // Constructor.
    public UserLogin( String userNameArg , Instant whenAuthenticatedArg ) {
        this.userName = userNameArg ;
        this.whenAuthenticated = whenAuthenticatedArg ;
    }
}

Attempt to retrieve object of your user-login class from the session’s key-value store

Retrieve such an object of that type from the session attributes key-value store.

String attributeName = "my-user-login" ;
UserLogin userLogin = vaadinSession.getAttribute( attributeName ) ;

Rather than invent an attribute name, you could just use the class name. The Class class lets you ask for the name of a class as text.

String attributeName = UserLogin.class.getName() ;
UserLogin userLogin = vaadinSession.getAttribute( attributeName ) ;

If you want to use the class name as the key in this way, the VaadinSession class provides a shortcut.

UserLogin userLogin = vaadinSession.getAttribute( UserLogin.class ) ;

Check to see if your UserLogin object is null. If you retrieved a null, then you know you have not yet stored an attribute (or willfully stored a null).

If not null, it means your user already has an active UserLogin object stored. How could they be logged-in already if the entry point of your app is executing? This can happen if the user hits the Reload button on their browser window. (Train your user not to do so on a single-page web app such as Vaadin.)

Outline of code to write

UserLogin userLogin = vaadinSession.getAttribute( UserLogin.class ) ;
if( Objects.isNull( userLogin ) ) {
    … display login form …
    … when authenticated, instantiate a `UserLogin` and store as attribute …
    if( authenticationSuccessful ) {  // Testing some did-user-authenticate variable you defined in your login-form.
        Instant whenAuthenticated = Instant.now() ;  // Capture the current moment in UTC. 
        UserLogin userLogin = new UserLogin( userName , whenAuthenticated ) ;
        VaadinSession.getCurrent().setAttribute( UserLogin.class , userLogin ) ;  // Using class name as the `String` key tracking this `userLogin` object.
        … switch content of the tab/window from authentication form to your main app content …
    } 
} else {  // Else not null. User already authenticated. User may have hit "Reload" button in browser. 
    … display app content … 
    … perhaps log this event … maybe user needs to be trained to not hit Reload on a Single-Page Web App …
}

By the way… the discussion above about sessions is scoped to each user’s own connection to your web app in a single web browser tab/window.

At some point you may look for a hook into the lifecycle of your entire web app, before the first user connects and/or after the last user disconnects, learn about the hook defined in the Java Servlet spec. This hook is the ServletContextListener interface, where “context” means your web app as a whole. This is standard Java Servlet stuff, not at all specific to Vaadin, but Vaadin is actually a Servlet (perhaps the most sophisticated Servlet ever) so this context listener paradigm applies.

You write a class implementing that interface, by writing the before-first-user and the after-last-user methods. Identify your class to the web container by annotating @WebListener (or alternative means). Search Stack Overflow as this has been covered multiple times already.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154