First of all, the iteration of the SpringSession version will definitely be accompanied by the removal of some classes and the addition of some classes. Currently, the version used in this series is the code flow version of the Master object on Github. If you have any questions about some of the classes in other versions, please feel free to talk.

In this article, we will look at the two strategies for sessionId resolution in SpringSession. This strategy has been mentioned in the previous article, and we will study the strategies for Cookie resolution in SpringSession.

SessionId parsing policy

The policy for resolving the sessionId in SpringSession is represented by the HttpSessionIdResolver interface. HttpSessionIdResolver has two implementation classes:

These two classes correspond to two different implementations of SpringSession resolution sessionids. Before diving into the implementation details of the different strategies, let’s take a look at some of the behaviors defined by the HttpSessionIdResolver interface.

HttpSessionIdResolver

HttpSessionIdResolver defines the Contract of the sessionId resolution policy. Allows session ids to be resolved by request and sent or terminated by response. The interface is defined as follows:

public interface HttpSessionIdResolver {
	List<String> resolveSessionIds(HttpServletRequest request);
	void setSessionId(HttpServletRequest request, HttpServletResponse response,String sessionId);
	void expireSession(HttpServletRequest request, HttpServletResponse response);
}
Copy the code

There are three methods in HttpSessionIdResolver:

  • resolveSessionIds: Resolves what is associated with the current requestsessionId.sessionIdMay come fromCookieOr request headers.
  • setSessionId: will be givensessionIdSend to the client. This method is creating a newsessionIs called and tells the client the newsessionIdWhat it is.
  • expireSession: instructs the client to end the currentsession. whensessionThis method is called when invalid and the client should be notifiedsessionIdNo longer valid. For example, it might remove a containsessionIdtheCookieOr set oneHTTPThe response header, whose value is null, indicates that the client is no longer committingsessionId.

The following is a detailed analysis of the two strategies mentioned above.

SessionId parsing based on cookies

This strategy is a CookieHttpSessionIdResolver corresponding implementation classes, extracted from through the Cookie session; In particular, the implementation will be allowed to use CookieHttpSessionIdResolver# setCookieSerializer (CookieSerializer) specified Cookie serialization strategy. The default Cookie name is “SESSION”. When a session is created, the HTTP response will carry a Cookie with a specified Cookie name and value as the session ID. The Cookie will be marked as a session Cookie with the domain path of the Cookie using context path and marked as HttpOnly. If HttpServletRequest#isSecure() returns true, The Cookie will then be marked as secure. As follows:

About cookies: Talk about sessions and cookies.

HTTP/1.1 200 OK set-cookie: SESSION= f81d4FAe-7dec-11d0-a765-00a0c91e6bf6; Path=/context-root; Secure; HttpOnly
Copy the code

At this point, the client should include session information by specifying the same Cookie in each request. Such as:

GET /messages/ HTTP/1.1 Host: example.com Cookie: SESSION= f81d4FAe-7dec-11d0-a765-00a0c91e6bf6
Copy the code

When the session is invalid, the server sends an expired HTTP response Cookie, such as:

HTTP/1.1 200 OK set-cookie: SESSION= f81d4FAe-7dec-11d0-a765-00a0c91e6bf6; Expires=Thur, 1 Jan 1970 00:00:00 GMT; Secure; HttpOnly
Copy the code

The realization of the CookieHttpSessionIdResolver class is as follows:

public final class CookieHttpSessionIdResolver implements HttpSessionIdResolver {
	private static final String WRITTEN_SESSION_ID_ATTR = CookieHttpSessionIdResolver.class
			.getName().concat(".WRITTEN_SESSION_ID_ATTR");
	// Cookie serialization policy. Default is DefaultCookieSerializer
	private CookieSerializer cookieSerializer = new DefaultCookieSerializer();

	@Override
	public List<String> resolveSessionIds(HttpServletRequest request) {
		// Get the sessionId from the request based on the provided cookieSerializer
		return this.cookieSerializer.readCookieValues(request);
	}

	@Override
	public void setSessionId(HttpServletRequest request, HttpServletResponse response, String sessionId) {
		if (sessionId.equals(request.getAttribute(WRITTEN_SESSION_ID_ATTR))) {
			return;
		}
		request.setAttribute(WRITTEN_SESSION_ID_ATTR, sessionId);
		// Write the sessionId back into the cookie according to the provided cookieSerializer
		this.cookieSerializer
				.writeCookieValue(new CookieValue(request, response, sessionId));
	}

	@Override
	public void expireSession(HttpServletRequest request, HttpServletResponse response) {
		// the sessionId is "", so the sessionId will not be available when the request comes in, which means the current session is invalid
		this.cookieSerializer.writeCookieValue(new CookieValue(request, response, ""));
	}
  
   // Specify how the Cookie is serialized
	public void setCookieSerializer(CookieSerializer cookieSerializer) {
		if (cookieSerializer == null) {
			throw new IllegalArgumentException("cookieSerializer cannot be null");
		}
		this.cookieSerializer = cookieSerializer; }}Copy the code

Here you can see the read operations are around CookieSerializer CookieHttpSessionIdResolver. CookieSerializer is a mechanism provided for Cookie operations in SpringSession. More on that.

Resolve the sessionId based on the request header

This strategy is a HeaderHttpSessionIdResolver corresponding implementation classes, through analyze the sessionId from the request header. Specifically, the implementation will be allowed to use HeaderHttpSessionIdResolver (String) to specify the first name. You can also use convenient factory methods to create instances that use common header names such as “X-Auth-Token” and “authenticing-info”. When a session is created, the HTTP response will have a response header with the specified name and sessionId value.

// Use x-auth-token as headerName
public static HeaderHttpSessionIdResolver xAuthToken(a) {
	return new HeaderHttpSessionIdResolver(HEADER_X_AUTH_TOKEN);
}
// Use authentication-info as headerName
public static HeaderHttpSessionIdResolver authenticationInfo(a) {
	return new HeaderHttpSessionIdResolver(HEADER_AUTHENTICATION_INFO);
}
Copy the code

HeaderHttpSessionIdResolver in handling sessionId photogenic than CookieHttpSessionIdResolver is much simpler. GetHeader (String) and Request. setHeader(String,String).

HeaderHttpSessionIdResolver this strategy is often used in the wireless terminal, to make up the support for the no Cookie scene.

Cookie serialization strategy

Based on analytical sessionId Cookie implementation class CookieHttpSessionIdResolver actual for cookies in reading and writing operations are done through CookieSerializer. SpringSession provides the default implementation of DefaultCookieSerializer interface. Of course, in practical applications, we can also implement this interface ourselves. Then through CookieHttpSessionIdResolver# setCookieSerializer (CookieSerializer) method to specify our own way.

PS: I have to say, the strong user extension capability is really the Spring family’s fine heritage.

I’m running out of space, so here are just two points:

  • CookieValueWhat is the meaning of existence
  • DefaultCookieSerializerWrite backCookieThe concrete implementation of, readCookieinSpringSession series – Request and response rewritingIt was introduced in this article and will not be repeated here.
  • The processing of jvm_router

CookieValue

The CookieValue is an internal class in CookieSerializer that encapsulates all the information needed to write to HttpServletResponse. In fact, the existence of CookieValue does not have any special significance. Personally, I think the author just wanted to simplify the problem of writing back the parameters of cookie link through the encapsulation of CookieValue at the beginning, but in fact, it seems that it did not reduce much workload.

The Cookie back to write

Cookie write back I think is essential for distributed session implementations; With the standard servlet-based HttpSession implementation, we actually don’t have to worry about writing back cookies, because the servlet container already does that. However, for distributed session, the response has been rewritten, so the current session information needs to be inserted into the response in the way of cookie and returned to the client when the response is returned – this is cookie write back. Below is the logic for writing back cookies in DefaultCookieSerializer. The details are annotated in the code.

@Override
public void writeCookieValue(CookieValue cookieValue) {
	HttpServletRequest request = cookieValue.getRequest();
	HttpServletResponse response = cookieValue.getResponse();
	StringBuilder sb = new StringBuilder();
	sb.append(this.cookieName).append('=');
	String value = getValue(cookieValue);
	if(value ! =null && value.length() > 0) {
		validateValue(value);
		sb.append(value);
	}
	int maxAge = getMaxAge(cookieValue);
	if (maxAge > -1) {
		sb.append("; Max-Age=").append(cookieValue.getCookieMaxAge()); OffsetDateTime expires = (maxAge ! =0)? OffsetDateTime.now().plusSeconds(maxAge) : Instant.EPOCH.atOffset(ZoneOffset.UTC); sb.append("; Expires=")
				.append(expires.format(DateTimeFormatter.RFC_1123_DATE_TIME));
	}
	String domain = getDomainName(request);
	if(domain ! =null && domain.length() > 0) {
		validateDomain(domain);
		sb.append("; Domain=").append(domain);
	}
	String path = getCookiePath(request);
	if(path ! =null && path.length() > 0) {
		validatePath(path);
		sb.append("; Path=").append(path);
	}
	if (isSecureCookie(request)) {
		sb.append("; Secure");
	}
	if (this.useHttpOnlyCookie) {
		sb.append("; HttpOnly");
	}
	if (this.sameSite ! =null) {
		sb.append("; SameSite=").append(this.sameSite);
	}

	response.addHeader("Set-Cookie", sb.toString());
}
Copy the code

This is just a patchwork string, which is then inserted into the Header and displayed in the browser like this:

Set-Cookie: SESSION=f81d4fae-7dec-11d0-a765-00a0c91e6bf6; Path=/context-root; Secure; HttpOnly
Copy the code

The processing of jvm_router

The read and write code of Cookie involves the judgment of the attribute jvmRoute and the corresponding processing logic.

1. Read the code snippet in the Cookie

if (this.jvmRoute ! =null && sessionId.endsWith(this.jvmRoute)) {
	sessionId = sessionId.substring(0,
			sessionId.length() - this.jvmRoute.length());
}
Copy the code

Write back the code snippet in the Cookie

if (this.jvmRoute ! =null) {
	actualCookieValue = requestedCookieValue + this.jvmRoute;
}
Copy the code

Jvm_route is a module in Nginx that uses session cookies to obtain session stickiness. If there are no sessions in cookies and urls, this is a simple round-robin load balancing. The specific process is divided into the following steps:

  • 1. The first request came without bringing itsessionInformation,jvm_routeAccording toround robinPolicy sending to OnetomcatThe above.
  • 2.tomcataddsessionInformation and return it to the customer.
  • 3. The user requests again.jvm_routeseesessionWith the name of the back-end server, it forwards the request to the corresponding server.

Jvm_route is essentially a workaround for session sharing. This is similar to the IP-hashing approach mentioned in the SpringSession series-Distributed Session implementations. Again, the problem here is that the session data cannot be transferred after the outage, even if the outage is lost.

DefaultCookieSerializer in addition to Cookie reading and writing, there are also some details worth paying attention to, such as the verification of Cookie median value, the implementation of remember-me, etc.

reference

  • The original address
  • Official documentation of SpringSession
  • Jvm_router principle
  • The SpringSession Chinese annotation continuously updates the code branch