SpringSession系列-sessionId解析和Cookie读写策略

栏目: Java · 发布时间: 5年前

内容简介:首先需求在这里说明下,SpringSession的版本迭代的过程中肯定会伴随着一些类的移除和一些类的加入,目前本系列使用的版本是github上对象的master的代码流版本。如果有同学对其他版本中的一些类或者处理有疑惑,欢迎交流。本篇将来介绍下

首先需求在这里说明下,SpringSession的版本迭代的过程中肯定会伴随着一些类的移除和一些类的加入,目前本系列使用的版本是github上对象的master的代码流版本。如果有同学对其他版本中的一些类或者处理有疑惑,欢迎交流。

本篇将来介绍下 SpringSession 中两种 sessionId 解析的策略,这个在之前的文章中其实是有提到过的,这里再拿出来和 SpringSessionCookie 相关策略一起学习 下。

sessionId 解析策略

SpringSession 中对于 sessionId 的解析相关的策略是通过 HttpSessionIdResolver 这个接口来体现的。 HttpSessionIdResolver 有两个实现类:

SpringSession系列-sessionId解析和Cookie读写策略

这两个类就分别对应 SpringSession 解析 sessionId 的两种不同的实现策略。再深入了解不同策略的实现细节之前,先来看下 HttpSessionIdResolver 接口定义的一些行为有哪些。

HttpSessionIdResolver

HttpSessionIdResolver 定义了 sessionId 解析策略的契约( Contract )。允许通过请求解析sessionId,并通过响应发送sessionId或终止会话。接口定义如下:

public interface HttpSessionIdResolver {
	List<String> resolveSessionIds(HttpServletRequest request);
	void setSessionId(HttpServletRequest request, HttpServletResponse response,String sessionId);
	void expireSession(HttpServletRequest request, HttpServletResponse response);
}
复制代码

HttpSessionIdResolver 中有三个方法:

  • resolveSessionIds :解析与当前请求相关联的 sessionIdsessionId 可能来自 Cookie 或请求头。
  • setSessionId :将给定的 sessionId 发送给客户端。这个方法是在创建一个新 session 时被调用,并告知客户端新 sessionId 是什么。
  • expireSession :指示客户端结束当前 session 。当 session 无效时调用此方法,并应通知客户端 sessionId 不再有效。比如,它可能删除一个包含 sessionIdCookie ,或者设置一个 HTTP 响应头,其值为空就表示客户端不再提交 sessionId

下面就针对上面提到的两种策略来进行详细的分析。

基于Cookie解析sessionId

这种策略对应的实现类是 CookieHttpSessionIdResolver ,通过从 Cookie 中获取 session ;具体来说,这个实现将允许使用 CookieHttpSessionIdResolver#setCookieSerializer(CookieSerializer) 指定 Cookie 序列化策略。默认的 Cookie 名称是“ SESSION ”。创建一个 session 时, HTTP 响应中将会携带一个指定 Cookie namevaluesessionIdCookieCookie 将被标记为一个 session cookieCookiedomain path 使用 context path ,且被标记为 HttpOnly ,如果 HttpServletRequest#isSecure() 返回 true ,那么 Cookie 将标记为安全的。如下:

关于 Cookie ,可以参考: 聊一聊session和cookie

HTTP/1.1 200 OK
Set-Cookie: SESSION=f81d4fae-7dec-11d0-a765-00a0c91e6bf6; Path=/context-root; Secure; HttpOnly
复制代码

这个时候,客户端应该通过在每个请求中指定相同的 Cookie 来包含 session 信息。例如:

GET /messages/ HTTP/1.1
 Host: example.com
 Cookie: SESSION=f81d4fae-7dec-11d0-a765-00a0c91e6bf6
复制代码

当会话无效时,服务器将发送过期的 HTTP 响应 Cookie ,例如:

HTTP/1.1 200 OK
 Set-Cookie: SESSION=f81d4fae-7dec-11d0-a765-00a0c91e6bf6; Expires=Thur, 1 Jan 1970 00:00:00 GMT; Secure; HttpOnly
复制代码

CookieHttpSessionIdResolver 类的实现如下:

public final class CookieHttpSessionIdResolver implements HttpSessionIdResolver {
	private static final String WRITTEN_SESSION_ID_ATTR = CookieHttpSessionIdResolver.class
			.getName().concat(".WRITTEN_SESSION_ID_ATTR");
	// Cookie序列化策略,默认是 DefaultCookieSerializer
	private CookieSerializer cookieSerializer = new DefaultCookieSerializer();

	@Override
	public List<String> resolveSessionIds(HttpServletRequest request) {
		// 根据提供的cookieSerializer从请求中获取sessionId
		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);
		// 根据提供的cookieSerializer将sessionId回写到cookie中
		this.cookieSerializer
				.writeCookieValue(new CookieValue(request, response, sessionId));
	}

	@Override
	public void expireSession(HttpServletRequest request, HttpServletResponse response) {
		// 这里因为是过期,所以回写的sessionId的值是“”,当请求下次进来时,就会取不到sessionId,也就意味着当前会话失效了
		this.cookieSerializer.writeCookieValue(new CookieValue(request, response, ""));
	}
  
   // 指定Cookie序列化的方式
	public void setCookieSerializer(CookieSerializer cookieSerializer) {
		if (cookieSerializer == null) {
			throw new IllegalArgumentException("cookieSerializer cannot be null");
		}
		this.cookieSerializer = cookieSerializer;
	}
}
复制代码

这里可以看到 CookieHttpSessionIdResolver 中的读取操作都是围绕 CookieSerializer 来完成的。 CookieSerializerSpringSession 中对于 Cookie 操作提供的一种机制。下面细说。

基于请求头解析sessionId

这种策略对应的实现类是 HeaderHttpSessionIdResolver ,通过从请求头 header 中解析出 sessionId 。具体地说,这个实现将允许使用 HeaderHttpSessionIdResolver(String) 来指定头名称。还可以使用便利的工厂方法来创建使用公共头名称(例如 “X-Auth-Token”“authenticing-info” )的实例。创建会话时, HTTP 响应将具有指定名称和 sessionId 值的响应头。

// 使用X-Auth-Token作为headerName
public static HeaderHttpSessionIdResolver xAuthToken() {
	return new HeaderHttpSessionIdResolver(HEADER_X_AUTH_TOKEN);
}
// 使用Authentication-Info作为headerName
public static HeaderHttpSessionIdResolver authenticationInfo() {
	return new HeaderHttpSessionIdResolver(HEADER_AUTHENTICATION_INFO);
}
复制代码

HeaderHttpSessionIdResolver 在处理 sessionId 上相比较于 CookieHttpSessionIdResolver 来说简单很多。就是围绕 request.getHeader(String)request.setHeader(String,String) 两个方法来玩的。

HeaderHttpSessionIdResolver 这种策略通常会在无线端来使用,以弥补对于无 Cookie 场景的支持。

Cookie 序列化策略

基于 Cookie 解析 sessionId 的实现类 CookieHttpSessionIdResolver 中实际对于 Cookie 的读写操作都是通过 CookieSerializer 来完成的。 SpringSession 提供了 CookieSerializer 接口的默认实现 DefaultCookieSerializer ,当然在实际应用中,我们也可以自己实现这个接口,然后通过 CookieHttpSessionIdResolver#setCookieSerializer(CookieSerializer) 方法来指定我们自己的实现方式。

PS:不得不说,强大的用户扩展能力真的是 Spring 家族的优良家风。

篇幅有限,这里就只看下两个点:

  • CookieValue 存在的意义是什么
  • DefaultCookieSerializer 回写 Cookie 的的具体实现,读 CookieSpringSession系列-请求与响应重写 这篇文章中有介绍过,这里不再赘述。
  • jvm_router的处理

CookieValue

CookieValueCookieSerializer 中的内部类,封装了向 HttpServletResponse 写入所需的所有信息。其实 CookieValue 的存在并没有什么特殊的意义,个人觉得作者一开始只是想通过 CookieValue 的封装来简化回写 cookie 链路中的参数传递的问题,但是实际上貌似并没有什么减少多少工作量。

Cookie 回写

Cookie 回写我觉得对于分布式 session 的实现来说是必不可少的;基于标准 servlet 实现的 HttpSession ,我们在使用时实际上是不用关心回写 cookie 这个事情的,因为 servlet 容器都已经做了。但是对于分布式 session 来说,由于重写了 response ,所以需要在返回 response 时需要将当前 session 信息通过 cookie 的方式塞到 response 中返回给客户端-这就是 Cookie 回写。下面是 DefaultCookieSerializer 中回写 Cookie 的逻辑,细节在代码中通过注释标注出来。

@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());
}
复制代码

这上面就是拼凑字符串,然后塞到Header里面去,最终再浏览器中显示大体如下:

Set-Cookie: SESSION=f81d4fae-7dec-11d0-a765-00a0c91e6bf6; Path=/context-root; Secure; HttpOnly
复制代码

jvm_router的处理

Cookie 的读写代码中都涉及到对于 jvmRoute 这个属性的判断及对应的处理逻辑。

1、读取 Cookie 中的代码片段

if (this.jvmRoute != null && sessionId.endsWith(this.jvmRoute)) {
	sessionId = sessionId.substring(0,
			sessionId.length() - this.jvmRoute.length());
}
复制代码

2、回写 Cookie 中的代码片段

if (this.jvmRoute != null) {
	actualCookieValue = requestedCookieValue + this.jvmRoute;
}
复制代码

jvm_routeNginx 中的一个模块,其作用是通过 session cookie 的方式来获取 session 粘性。如果在 cookieurl 中并没有 session ,则这只是个简单的 round-robin 负载均衡。其具体过程分为以下几步:

  • 1.第一个请求过来,没有带 session 信息, jvm_route 就根据 round robin 策略发到一台 tomcat 上面。
  • 2. tomcat 添加上 session 信息,并返回给客户。
  • 3.用户再次请求, jvm_route 看到 session 中有后端服务器的名称,它就把请求转到对应的服务器上。

从本质上来说, jvm_route 也是解决 session 共享的一种解决方式。这种和 SpringSession系列-分布式Session实现方案 中提到的基于 IP-HASH 的方式有点类似。那么同样,这里存在的问题是无法解决宕机后 session 数据转移的问题,既宕机就丢失。

DefaultCookieSerializer 中除了 Cookie 的读写之后,还有一些细节也值得关注下,比如对 Cookie 中值的验证、 remember-me 的实现等。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Machine Learning

Machine Learning

Kevin Murphy / The MIT Press / 2012-9-18 / USD 90.00

Today's Web-enabled deluge of electronic data calls for automated methods of data analysis. Machine learning provides these, developing methods that can automatically detect patterns in data and then ......一起来看看 《Machine Learning》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

MD5 加密
MD5 加密

MD5 加密工具