Welcome toTencent Cloud + community, get more Tencent mass technology practice dry goods oh ~

This article is compiled by Tnecesoc.

Session management has always been an important part of Java enterprise applications. But for a long time, we considered this part of the problem solved, and there were no major innovations.

However, the modern trend toward microservices and scalable cloud-native applications reveals some of the design flaws of today’s session management technology, challenging some of the conclusions we’ve drawn about this design over the past 20 years.

This article demonstrates the approach the Spring Session API has taken to help us overcome some of the limitations of the previous Session management approach. We’ll start with a summary of current Session management technology issues and then delve into Spring Session’s strategy for addressing these issues. Finally, we’ll summarize how Spring Session works and how it can be used in specific projects.

Spring Session revolutionizes Session management for enterprise Java applications by making it easy to:

  • Write cloud-native applications that scale horizontally
  • Externalize the storage of session state into a specialized external session store, such as Redis or Apache Geode, which provides a high-quality storage cluster independent of the application server
  • Keep HttpSession online while users make requests through WebSocket
  • Access session data from non-Web request processing directives, such as JMS message processing directives
  • Support for establishing multiple sessions per browser to build a richer end-user experience
  • Controls how session ids are exchanged between the client and the server, so as to write the RESTul API that extracts session IDS in the HTTP header without relying on cookies

Note that the Spring Session project doesn’t actually rely on the Spring framework, so we can use it even in projects that don’t use the Spring framework.

Problems with traditional session management techniques

Spring Session is designed to address various issues with traditional JavaEE Session management techniques. Here are some examples to illustrate some of these issues.

Build cloud-native applications that scale horizontally

From the perspective of cloud native application architecture, an application should be able to scale horizontally by running more Linux containers in a large virtual machine pool to deploy more instances. For example, we can easily deploy a WAR file for one of these applications into Tomcat on Cloud Foundry or Heroku, and scale out 100 application instances in seconds, each with 1GB of RAM. We can also set up the cloud platform to automatically increase or decrease the number of application instances based on user demand.

Many applications store HTTP session state in the JVM where the application code is running. This is easy to implement and fast to access. When an application instance joins or leaves the cluster, the storage for HTTP sessions is redistributed equally across all remaining application instances. In an elastic cloud environment, we run hundreds of application instances, and the number of instances can change rapidly at any time. This raises some questions:

  • Redistribution of HTTP session storage can be a performance bottleneck;
  • The heap space required to store a large number of sessions is too large, causing frequent garbage collection processes and affecting performance;
  • TCP multicast is typically disabled by cloud infrastructure, but session managers often need to use it to discover application instances joining or exiting the cluster.

Therefore, it is more efficient to store HTTP session state in a data store outside of the JVM where the application code is running. For example, you can set up and use Redis to store session state in the above 100 Tomcat instances, and the number of Tomcat instances can be increased or decreased without affecting the mode of session storage in Redis. In addition, because Redis is written in C, it can consume hundreds of gigabytes or even terabytes of memory without garbage collection affecting its operation.

For an open source server like Tomcat, it is easy to find alternative implementations of session management techniques that use external data stores such as Redis or Memcached, but the configuration process to use can be complex and may vary from application server to application server. For closed source products such as WebSphere and Weblogic, it is often impossible to find alternative implementations of session management techniques that are appropriate for them.

Spring Session provides an application-server-independent way to set up plug-in Session data storage, enabling such storage to be implemented within the framework of a Servlet, rather than relying on application-server apis. This means that Spring Session can work with all application servers that implement the Servlet specification (Tomcat, Jetty, WebSphere, WebLogic, JBoss) and be configured in exactly the same and easy way on all application servers.

We can also choose the external session data store that best suits our needs. This makes Spring Session an ideal migration tool to help us move traditional JavaEE applications to the cloud as well as a twelve factor compliant application.

One user, multiple accounts

Suppose you are running a mass-market Web application on example.com, where some human users have created multiple accounts. For example, a user named Jeff Lebowski might have two accounts: [email protected] and [email protected]. As with any Java Web application, you can use HttpSession to track various session states, such as the current logged-in user. Therefore, when users want to switch from [email protected] to [email protected], they must log out of their current account and log in again.

Using Spring Session to configure multiple HTTP sessions for each user is simple. Jeff Lebowski can switch back and forth between [email protected] and [email protected] without logging out or logging in.

Preview at different security levels

Imagine that you want to build a Web application with a complex custom authorization system, where users with different permissions have different application UI styles.

For example, suppose the application has four levels of security: public, Confidential, Secret, and Top Secret. When a user logs in to the application, the system identifies the user’s security level and displays only data that is not higher than the user’s security level. In this way, publicly-level users can see publicly-level documents; Users with the confidentiality level can see the public and private levels, and so on. In order to make the user interface more user-friendly, our application should also be able to preview what the application’S UI would look like at a lower security level. For example, top secret users should be able to preview the appearance of things in an app in secret mode.

A typical Web application stores the current user’s identity and its security level or role in an HTTP session. However, since each user of a Web application has only one session, the role of the user can only be switched by logging out and then logging in, or multiple sessions can be implemented for one user.

With Spring Session, we can easily create multiple independent sessions for each logged-in user, making it easy to implement the preview feature. For example, when a user currently logged in at the top secret level wants to preview an application at the top secret level, a new session with the top secret level can be created and used.

Maintain login status while using Web Sockets

Imagine a scenario where users log in to our Web application via example.com, and they can talk to each other using an HTML5 instant chat client that works over Websockets. However, according to the Servlet specification, requests made over Websockets do not update session expiration dates, so when a user is chatting, no matter how frequently they are chatting, the session may die, and the Websocket connection will be closed, preventing the chat from continuing.

Again, with Spring Sessions, we can easily ensure that Websocket requests as well as regular HTTP requests are updated with Session expiration times.

Access session data for non-Web requests

Imagine also that our application provides two access methods, one HTTP based RESTful API and the other AMQP messages based on RabbitMQ. At this point, the thread executing the AMQP message cannot access the HttpSession of the application server, so we must write a solution to access the data in the HTTP session.

Again, with Spring Sessions, as long as we know the Session ID, we can access Spring Sessions from any thread in the application. Spring Session has a richer API than previous Servlet HTTP Session managers, allowing us to locate the Session we are looking for just by knowing the Session ID. For example, we can use the user id field of the incoming message to find the corresponding session directly.

How Spring Session works

Now that the limitations of traditional application servers in HTTP Session management have been demonstrated in different scenarios, let’s look at how Spring Session addresses these issues.

The Spring Session architecture

There are two key issues that must be addressed when implementing a session manager:

  • How to create an efficient, reliable, and highly available session data storage cluster?
  • How do YOU determine which session instance can be associated with which incoming request (in the form of HTTP, WebSocket, AMQP, and so on)?

But in essence, there is a more critical question: how do you transfer a session ID across different request protocols?

The first problem for Spring Sessions is well addressed by a variety of highly available and scalable cluster stores (Redis, Gemfire, Apache Geode, etc.). Therefore, Spring Session should also define a standard set of interfaces that allow access to the underlying datastore to be implemented from different datastores. Spring Session not only defines the basic key interface such as Session and ExpiringSession, but also defines the key interface SessionRepository for accessing different data stores.

  • org.springframework.session.SessionIs an interface that defines basic session functions, such as setting and deleting properties. This interface is not dependent on the specific underlying technology, so it can be better than that in servletsHttpSessionApply to more situations;
  • org.springframework.session.ExpiringSessionExtends the Session interface. It provides properties that allow us to set up a timed session and query whether the session has expired.RedisSessionIs an example implementation of this interface.
  • org.springframework.session.SessionRepositoryDefines methods for creating, saving, deleting, and finding sessions. The actual logic for saving sessions to the datastore is written into the implementation of this interface. For example,RedisOperationsSessionRepositoryIs an implementation of this interface, which uses Redis to create, save, and delete sessions.

As for associating a request to a specific Session instance, Spring Session assumes that the process of associating depends on a specific protocol, because the client and server need to agree on the Session ID to be transferred during the request/response cycle. For example, if a client sends an HTTP request, the session can be associated with the request through a Cookie or HTTP header. If an HTTPS request comes in, the Session ID field of SSL can be used to associate the request with the Session. If a JMS message is sent, the session ID between the request and response can also be stored in the message header.

Spring sessions define a HttpSessionStrategy interface for associated operations over HTTP, Which have the Cookies with session affinity CookieHttpSessionStrategy and use a custom message first field to manage session HeaderHttpSessionStrategy two implementations.

Here’s a detailed look at how Spring Session works over HTTP.

As of the publication of this article (November 10, 2015), Spring Session 1.0.2 in the current GA release provides a set of implementations of Spring Session using Redis, as well as support for any distributed Map implementation such as Hazelcast. In fact, it is relatively easy to implement Spring Session support for some kind of datastore, and there are already many implementations in the open source community.

Spring Session based on HTTP

The HTTP-based Spring Session is implemented as a standard Servlet filter. This filter should intercept all requests to the Web application and should be the first in a chain of filters. Spring Session of the filters will be responsible for ensuring that all subsequent code. Facing the javax.mail servlet. HTTP. It. The getSession () method call will be handed over a Spinrg Session HttpSession instance, rather than the HttpSession provided by default by the application server.

The easiest way to understand this is to look at the actual source code for Spring Session. Let’s start with the standard Servlet extension points used to implement Spring Sessions.

In 2001, the Servlet 2.3 specification introduced a ServletRequestWrapper. The Javadoc for this class calls the ServletRequestWrapper “a convenient implementation for the ServletRequest interface to be inherited by developers to adapt to a particular Servlet. This class uses the wrapper, or decorator pattern. Calls to the methods of this class’s ServletRequest class are passed to a request object that it encapsulates. The following code extracted from Tomcat shows how the ServletRequestWrapper is implemented.

public class ServletRequestWrapper implements ServletRequest {

    private ServletRequest request;

    /** * Creates a ServletRequest adaptor wrapping the given request object * @throws Java. Lang. IllegalArgumentException if the request is null * will be thrown if the request object is empty null pointer exception * /
    public ServletRequestWrapper(ServletRequest request) {
        if (request == null) {
            throw new IllegalArgumentException("Request cannot be null");   
        }
        this.request = request;
    }

    public ServletRequest getRequest() {
        return this.request;
    }
    
    public Object getAttribute(String name) {
        return this.request.getAttribute(name);
    }

    // For readability purposes, the following code is omitted
}
Copy the code

Servlt 2.3 specification for ServletRequestWrapper also defines a subclass HttpServletRequestWrapper. We can use it to quickly implement a custom HttpServletRequest. This is pumped from the Tomcat code showed HttpServletRequestWrapper of this class is implemented.

public class HttpServletRequestWrapper extends ServletRequestWrapper 
    implements HttpServletRequest {

    public HttpServletRequestWrapper(HttpServletRequest request) {
	    super(request);
    }
    
    private HttpServletRequest _getHttpServletRequest() {
 	   return (HttpServletRequest) super.getRequest();
    }
  
    public HttpSession getSession(boolean create) {
     return this._getHttpServletRequest().getSession(create);
    }
   
    public HttpSession getSession() {
      return this._getHttpServletRequest().getSession();
    }
    
    // For readability purposes, the following code is omitted
}
Copy the code

Therefore, we can use these wrapper classes to write code that extends the HttpServletRequest functionality, overloading the method that returns HttpSession so that it returns the session that we store in the external repository. Here is a copy of the source code from the Spring Session project that corresponds to what is mentioned here. In order to correspond to the explanation here, the source code inside the original comments have been rewritten by me, in this might as well take a look at the comments inside.

/ * * Spring Session project defines a class inherits the standard HttpServletRequestWrapper * it overloaded it inside all methods related to the Session * /
private final class SessionRepositoryRequestWrapper
   extends HttpServletRequestWrapper {

   private HttpSessionWrapper currentSession;
   private Boolean requestedSessionIdValid;
   private boolean requestedSessionInvalidated;
   private final HttpServletResponse response;
   private final ServletContext servletContext;

   / constructor this very simple * * * it will be used after receiving and set some parameters, * and then complete the agent of HttpServletRequestWrapper * /
   private SessionRepositoryRequestWrapper(
      HttpServletRequest request,
      HttpServletResponse response,
      ServletContext servletContext) {
     super(request);
     this.response = response;
     this.servletContext = servletContext;
   }

   /* * Spring Session replaces the proxy call to the default method provided by the application server with its own implementation of returning Session data stored in an external data source. * * The implementation checks to see if it already has a corresponding Session. Otherwise, it checks whether the session ID attached to the current request actually corresponds to a session * if so, it loads the session from SessionRepository with that session ID; * If the session ID does not exist in the external data source, * create a new session and store it in the session datastore. */
   @Override
   public HttpSession getSession(boolean create) {
     if(currentSession ! =null) {
       return currentSession;
     }
     String requestedSessionId = getRequestedSessionId();
     if(requestedSessionId ! =null) {
       S session = sessionRepository.getSession(requestedSessionId);
       if(session ! =null) {
         this.requestedSessionIdValid = true;
         currentSession = new HttpSessionWrapper(session, getServletContext());
         currentSession.setNew(false);
         returncurrentSession; }}if(! create) {return null;
     }
     S session = sessionRepository.createSession();
     currentSession = new HttpSessionWrapper(session, getServletContext());
     return currentSession;
   }

   @Override
   public HttpSession getSession() {
     return getSession(true); }}Copy the code

Spring Session also defines an implementation class of the ServletFilter interface, SessionRepositoryFilter. The source code for the core implementation of this filter is also provided here, along with some comments corresponding to the content of this article, so take a look.

/* * SessionRepositoryFilter is an implementation of the standard ServletFilter. * the purpose is to extend functionality from its base class. */
public class SessionRepositoryFilter < S extends ExpiringSession >
    extends OncePerRequestFilter {

	/* * This method will create an instance of the wrapped request we described above, and then run the wrapped request through the rest of the filter chain. When the application executes the code behind this filter, * to retrieve Session data, the wrapped request returns the HttpServletSession instance held by Spring Session * at the external data source. */
	protected void doFilterInternal(
	    HttpServletRequest request,
	    HttpServletResponse response,
	    FilterChain filterChain) throws ServletException, IOException {

		request.setAttribute(SESSION_REPOSITORY_ATTR, sessionRepository);

		SessionRepositoryRequestWrapper wrappedRequest =
		  new SessionRepositoryRequestWrapper(request,response,servletContext);

		SessionRepositoryResponseWrapper wrappedResponse =
		  new SessionRepositoryResponseWrapper(wrappedRequest, response);

		HttpServletRequest strategyRequest =
		     httpSessionStrategy.wrapRequest(wrappedRequest, wrappedResponse);

		HttpServletResponse strategyResponse =
		     httpSessionStrategy.wrapResponse(wrappedRequest, wrappedResponse);

		try {
			filterChain.doFilter(strategyRequest, strategyResponse);
		} finally{ wrappedRequest.commitSession(); }}}Copy the code

The point of this section is that the HTTP-based Spring Session is really just a classic Servlet filter that uses the standard features of the Servlet specification. Therefore, it should be possible to change an existing Web application’s WAR file to use Spring Session without changing the existing code. In application, however, with the javax.mail. Servlet. HTTP. HttpSessionListener situation are an exception. Spring Session 1.0 does not provide support for the HttpSessionListener, but Spring Session 1.1 M1 adds support for it. See here for details.

Spring Session setup

In Web projects, there are four steps to setting up a Spring Session:

  • Set the datastore to use in Spring Session
  • Add Spring Session’s.jar file to your Web application
  • Add Spring Session filters to the configuration of your Web application
  • Set up the connection from Spring Session to the selected Session datastore

Spring Session has built-in support for Redis. See here for details on installing and setting up Redis.

There are two common ways to complete the Spring Session setup steps described above. One is to use Spring Boot to automatically set up the Spring Session. The other is to manually complete each configuration step.

Dependency management tools like Maven and Gradle make it easy to add Spring Sessions to your application’s dependency projects. For example, if you are using Spring Boot + Maven, you can add the following dependencies to pom.xml:

<dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session</artifactId> < version > 1.0.2. RELEASE < / version > < / dependency > < the dependency > < groupId > org. Springframework. Boot < / groupId > <artifactId>spring-boot-starter-redis</artifactId> </dependency>Copy the code

The spring-boot-starter-redis dependency ensures that the jars needed to interact with Redis are included in the application so that spring Boot can be used for automatic configuration. The Spring-Session dependency project corresponds to the Spring Session JAR.

Setting up the Spring Session Servlet filter can be done automatically by adding the @enableredisHttpSession annotation to the Spring Boot configuration class. Like the following code:

@SpringBootApplication
@EnableRedisHttpSession
public class ExampleApplication {

    public static void main(String[] args) { SpringApplication.run(ExampleApplication.class, args); }}Copy the code

Add the following configuration information to the Spring Boot application.properties file to set up the connection between Spring Session and Redis.

spring.redis.host=localhost
spring.redis.password=secret
spring.redis.port=6379
Copy the code

To set up a connection to Redis, Spring Boot provides a detailed infrastructure that allows you to set up a connection to Redis in any way you want. You can find step-by-step instructions in Spring Session and Spring Boot.

See here for a tutorial on setting up a traditional Web application to use Spring Session using web.xml.

See here for a tutorial on setting up a traditional war file without web.xml to use Spring Session.

By default, Spring Session uses HTTP cookies to store Session ids, but we can also set Spring Session to use custom HTTP header fields (for example, X-Auth-token: 0dc1f6e1-c7f1-41ac-8ce2-32b6b3e57aa3

) to store session ids, which can be useful when building RESTful apis. See the full tutorial here.

The use of Spring Session

After configuring Spring Session, we can use the standard Servlet API to interact with it. For example, this code defines a Servlet that uses the standard Servlet session API to access session data.

@WebServlet("/example")
public class Example extends HttpServlet {
  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

    // Use the standard servlet API to get the corresponding session data
    // This Session data is stored in Redis
    // Or any other session data source we specify

    HttpSession session = request.getSession();
    StringValue = session. The getAttribute (a root someAttributea euro ); }}Copy the code

One browser, multiple sessions

Spring Session tracks multiple sessions per user by using a Session code parameter called _s. If have the incoming request URL is http://example.com/doSomething?_s=0, then the Spring Session will read _s the value of the parameter, and then will think this request is the default Session.

If the incoming request URL is http://example.com/doSomething?_s=1, then the Spring Session will know the corresponding Session request code is 1. If the incoming request does not specify _s, Spring Session treats it as the default conversation (that is, _s = 0).

In order to let every browser to create a new Session, we just like before calling javax.mail. Servlet. HTTP. It. The getSession (), and then the Spring Session will return the corresponding Session, Or create a new session using the semantics of the Servlet specification. The following table shows how the getSession() method behaves with different URL parameters in the same browser:

The HTTP request URL The session code GetSession ()
example.com/resource 0 Return if there is a session associated with code 0, otherwise create a new session and associate it with code 0
example.com/resource?_s=1 1 Return if there is a session associated with code 1, otherwise create a new session and associate it with code 1
example.com/resource?_s=0 0 Return if there is a session associated with code 0, otherwise create a new session and associate it with code 0
example.com/resource?_s=abc abc Return if there is a session associated with the code ABC, otherwise create a new session and associate it with the code ABC

As shown in the table above, session codenames are not limited to integers and can be applied to each new session as long as they are different from all other session aliases published to the user. However, integer Session codenames are probably the easiest to use, and Spring Session provides HttpSessionManager to provide some practical methods for handling Session codenames.

We can use “org. Springframework. The session. Web. HttpSessionManager” this attribute name to find the appropriate attributes, and access to HttpSessionManager. The following code demonstrates the method to get a reference to the httpssession Manager and some of the main methods of the utility method class.

@WebServlet("/example")
public class Example extends HttpServlet {

  @Override
  protected void doGet(HttpServletRequest request,HttpServletResponse response)
  throws ServletException, IOException {

    / * * through the use of the "org. Springframework. The session. Web. HTTP. HttpSessionManager" * this attribute name to look for in the request attributes attribute * to get a Spring session A reference to HttpSessionManager */

    HttpSessionManager sessionManager=(HttpSessionManager)request.getAttribute(
        "org.springframework.session.web.http.HttpSessionManager");

    /* * Use HttpSessionManager to find the session id for the HTTP request. * By default this session id is given by the HTTP request URL parameter _s. * such as http://localhost:8080/example? The _s=1 URL * will make the println() method here print "Requested Session Alias is: 1" */
    String requestedSessionAlias=sessionManager.getCurrentSessionAlias(request);
    System.out.println("Requested Session Alias is: " + requestedSessionAlias);

    /* * returns a unique session code that is not currently used by the browser in the request parameters. * Note that this method does not create a new session, * the new session is still created through request.getSession(). */
    String newSessionAlias = sessionManager.getNewSessionAlias(request);

    /* * Construct a URL with the _s parameter using the newly obtained session id. * For example, if the newssession alias value is 2, the method returns "/ Inbox? _s=3" */
    String encodedURL = sessionManager.encodeURL("/inbox", newSessionAlias);
    System.out.println(encodedURL);

    /* * Returns a Map with session code key and session ID value, * to identify the session corresponding to the request from the browser
    Map <String.String> sessionIds = sessionManager.getSessionIds(request); }}Copy the code

conclusion

Spring Session revolutionizes Session management for enterprise Java applications by making it easy to:

  • Write cloud-native applications that scale horizontally
  • Externalize the storage of session state into a specialized external session store, such as Redis or Apache Geode, which provides a high-quality storage cluster independent of the application server
  • Keep HttpSession online while users make requests through WebSocket
  • Access session data from non-Web request processing directives, such as JMS message processing directives
  • Support for establishing multiple sessions per browser to build a richer end-user experience
  • Controls how session ids are exchanged between the client and the server, so as to write the RESTul API that extracts session IDS in the HTTP header without relying on cookies

If you’re looking for a way to break away from the traditional, clunky application server, but are constrained by the application server’s reliance on Session storage clustering, Spring Session is a great choice for lightweight containers like Tomcat, Jetty, and Undertow.

The resources

The Spring Session project

Spring Session tutorial and guide

  • The HttpSession & Redis
  • Spring integration of the Boot
  • Spring Security integration
  • Restful APIs
  • Multiple User Accounts
  • Web Socket integration

Websocket/HttpSession timeout interaction

  • ASF Bugzilla – Bug 54738
  • WEBSOCKET SPEC-175

Webinar: Introduction to Spring Session


Question and answer

How are authentication, authorization, and session management implemented in traditional Web applications and apis?

reading

Spring Session distributed cluster Session management

Spring Session key class source code analysis

An open source framework for turning Web forms into sessions


Has been authorized by the author tencent cloud + community release, the original link: https://cloud.tencent.com/developer/article/1151972?fromSource=waitui

Welcome toTencent Cloud + communityOr pay attention to the wechat public account (QcloudCommunity), the first time to get more massive technical practice dry goods oh ~