目录

Session

在计算机科学领域来说,尤其是在网络领域,会话是一种持久网络协议,在用户(或用户代理)端和服务器端之间创建关联,从而起到交换数据包的作用机制,session在网络协议(例如telnet或FTP)中是非常重要的部分。

在不包含会话层(例如UDP)或者是无法长时间驻留会话层(例如HTTP)的传输协议中,会话的维持需要依靠在传输数据中的高级别程序。例如,在浏览器和远程主机之间的HTTP传输中,HTTP cookie就会被用来包含一些相关的信息,例如session ID,参数和权限信息等。

每个用户(浏览器)首次与web服务器建立连接时,就会产生一个Session,同时服务器会分配一个SessionId给用户的浏览器。

服务器端的会话是快速而高效的,但是在负载均衡系统和高速应用系统中的使用会比较麻烦,而在没有储存能力的系统上更是无法使用。在负载均衡系统中可以通过共享储存或者设立独立的存储服务器来解决,这需要根据系统的效率和加载分布的需求情况。(可以将Session持久化到 Redis 中)

客户端会话使用了Cookie和加密技术来完成上面提到的数据储存需求。

  • 第一:当客户端浏览器窗口发出请求后,该浏览器的Cookie就会被传入服务器;
  • 第二:服务器读取Cookie中的sessionid,这时就存在读到和读不到两种情况
  • 第三:如果读不到,则由容器创建一个session,然后把这个session的sessionid用Cookie写入客户端
  • 第四:下次再进行网络请求时,这个sessionid也同样会被传入服务器
  • 第五:服务器读取到sessionid后,就到容器中查找到了该sessionid所对应的session

如果客户端禁用了Cookie,就不能使用Cookie在客户端浏览器和服务器间进行sessionid的传输了。

  • url重写其实就是把sessionid拼接在url的后面,通过url在客户端和服务器之间传输这个sessionid,只要客户端读取到了客户端传递过来的sessionid了,就可以查找到对应的session了。
  • url 重写使用response.encodeRedirectURL(“页面”)。

session和Cookie的区别:

  • Cookie是存于客户端的,相对而言不够安全,而session是在服务器器上,相对比较安全;
  • Cookie只能存于字符串,而session可以存储对象;
  • 两者的默认有效时间也不同,seesion的默认有效时间为30分钟,浏览器关闭,session也就失效。
  • session 的运行依赖 session id,而 session id 是存在 cookie 中的,也就是说,如果浏览器禁用了 cookie ,同时 session 也会失效(但是可以通过其它方式实现,比如在 url 中传递 session_id)Session可以使用HTML5中的LocalStorage实现,不一定依赖Cookies
  • session 可以放在 文件、数据库、或内存中都可以。
  • 用户验证这种场合一般会用 session

而Cookie的有效期如果设置为0,则将Cookie删除,设置为负数的话,存于浏览器缓存里,浏览器关闭也就失效,设置为正数的话,单位为秒,设置多少就在多少秒内有效。

如果想使用session,客户端浏览器必须通过数据持久化技术来保存sessionId,无论是cookie还是localstorage,都是一种数据持久化的技术手段,如果大家知道有其它手段都可以使用,前提是浏览器支持该技术而且没有禁用该技术。

服务器为每个用户创建一个会话,存储用户的相关信息,以便多次请求能够定位到同一个上下文。

Web开发中,web-server可以自动为同一个浏览器的访问用户自动创建session,提供数据存储功能。最常见的,会把用户的登录信息、用户信息存储在session中,以保持登录状态。

JSESSIONID 是一个Cookie,Servlet容器(tomcat,jetty)用来记录用户session。

客户端首次访问服务器 request.getSession() 会创建 session。 浏览器会将 JSESSIONID 存入 Cookie,下次请求时发送 jsessionid

URL 重写 tomcat等容器有一个URL重写机制。这个机制是客户端Cookie不可用时的一个兜底策略,通过在URL后面加上;jsessionid=xxx来传递session id,这样即使Cookie不可用时,也可以保证session的可用性,但是session暴露在URL里,本身是不安全的,到这里基本网上说法都是一致的

分布式session

粘性 session

原理:粘性Session是指将用户锁定到某一个服务器上,比如上面说的例子,用户第一次请求时,负载均衡器将用户的请求转发到了A服务器上,如果负载均衡器设置了粘性Session的话,那么用户以后的每次请求都会转发到A服务器上,相当于把用户和A服务器粘到了一块,这就是粘性Session机制。

优点:简单,不需要对session做任何处理。

缺点:缺乏容错性,如果当前访问的服务器发生故障,用户被转移到第二个服务器上时,他的session信息都将失效。

适用场景:发生故障对客户产生的影响较小;服务器发生故障是低概率事件。

实现方式:以Nginx为例,在upstream模块配置ip_hash属性即可实现粘性Session。

upstream mycluster{
    #这里添加的是上面启动好的两台Tomcat服务器
    ip_hash;#粘性Session
     server 192.168.22.229:8080 weight=1;
     server 192.168.22.230:8080 weight=1;
}

session 共享(复制)

原理:任何一个服务器上的session发生改变(增删改),该节点会把这个 session的所有内容序列化,然后广播给所有其它节点,不管其他服务器需不需要session,以此来保证Session同步。

优点:可容错,各个服务器间session能够实时响应。

缺点:会对网络负荷造成一定压力,如果session量大的话可能会造成网络堵塞,拖慢服务器性能。

实现方式:

① 设置tomcat ,server.xml 开启tomcat集群功能

Address:填写本机ip即可,设置端口号,预防端口冲突。

② 在应用里增加信息:通知应用当前处于集群环境中,支持分布式 在web.xml中添加选项

利用cookie记录session

session记录在客户端,每次请求服务器的时候,将session放在请求中发送给服务器,服务器处理完请求后再将修改后的session响应给客户端。这里的客户端就是cookie。

利用cookie记录session的也有缺点,比如受cookie大小的限制,能记录的信息有限;每次请求响应都需要传递cookie,影响性能,如果用户关闭cookie,访问就不正常。但是由于

cookie的简单易用,可用性高,支持应用服务器的线性伸缩,而大部分要记录的session信息比较小,因此事实上,许多网站或多或少的在使用cookie记录session。

session 服务器集中存储

利用独立部署的session服务器(集群)统一管理session,服务器每次读写session时,都访问session服务器。

应用服务器的状态分离,分为无状态的应用服务器和有状态的session服务器,然后针对这两种服务器的不同特性分别设计架构。

使用分布式缓存方案比如memcached、Redis

保证session一致性的架构设计常见方法:

  • session同步法:多台web-server相互同步数据
  • **客户端存储法:**一个用户只存储自己的数据
  • **反向代理hash一致性:**四层hash和七层hash都可以做,保证一个用户的请求落在一台web-server上
  • **后端统一存储:**web-server重启和扩容,session也不会丢失

Spring Session

对用户透明的去管理分布式Session

Spring Session不依赖于Servlet容器,而是Web应用代码层面的实现,直接在已有项目基础上加入spring Session框架来实现Session统一存储在Redis中。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
            <version>1.3.0.RELEASE</version>
            <type>pom</type>
        </dependency>
        <dependency>
            <groupId>biz.paluch.redis</groupId>
            <artifactId>lettuce</artifactId>
            <version>3.5.0.Final</version>
</dependency>

添加Spring 配置文件 spring-session.xml

1
2
3
4
5
6
7
8
9
<context:annotation-config/>

<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"/>

<bean class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory">
	<property name="hostName" value="192.168.1.149"/>
     <property name="port" value="6379"/>
     <property name="password" value="123456"/>
</bean>

RedisHttpSessionConfiguration的作用是创建名为springSessionRepositoryFilter 的Spring Bean,继承自Filter。springSessionRepositoryFilter替换容器默认的HttpSession支持为Spring Session,将Session实例存放在Redis中。

RedisHttpSessionConfiguration继承了SpringHttpSessionConfiguration这个类,这个类很重要,SpringHttpSessionConfiguration通过@Bean的方式将springSessionRepositoryFilter注入到容器中:

在web.xml中添加DelegatingFilterProxy

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<filter>
    <filter-name>springSessionRepositoryFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSessionRepositoryFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>ERROR</dispatcher>
</filter-mapping>

DelegatingFilterProxy将通过springSessionRepositoryFilter的名称查找Bean并将其转换为过滤器。对于调用DelegatingFilterProxy的每个请求,也将调用springSessionRepositoryFilter。

常见问题

如何防止用户打开两个浏览器窗口操作导致的session混乱 这个问题与防止表单多次提交是类似的,可以通过设置客户端的令牌来解决。就是在服务器每次生成一个不同的id返回给客户端,同时保存在session里,客户端提交表单时必须把这个id也返回服务器,程序首先比较返回的id与保存在session里的值是否一致,如果不一致则说明本次操作已经被提交过了。

Servlet Session 跟踪

我们都知道HTTP是一种无状态协议。所有请求和响应都是独立的。但有时您需要跨多个请求跟踪客户端的活动。例如。当用户登录您的网站时,无论他登录后访问哪个网页,他的凭据都将保存在服务器上,直到他注销为止。所以这是通过创建会话来管理的。

会话管理Web 容器用来为特定用户存储会话信息的一种机制。Servlet 应用程序使用四种不同的技术进行会话管理。它们如下:

  1. Cookies

    一个 Web 服务器可以分配一个唯一的 session 会话 ID 作为每个 Web 客户端的 cookie,对于客户端的后续请求可以使用接收到的 cookie 来识别。

    这可能不是一个有效的方法,因为很多浏览器不支持 cookie,所以我们建议不要使用这种方式来维持 session 会话。

  2. 隐藏的表单字段 Hidden form field

1
<input type="hidden" name="sessionid" value="12345">

这可能是一种保持 session 会话跟踪的有效方式,但是点击常规的超文本链接()不会导致表单提交,因此隐藏的表单字段也不支持常规的 session 会话跟踪。

  1. URL重写(URL Rewriting)

您可以在每个 URL 末尾追加一些额外的数据来标识 session 会话,服务器会把该 session 会话标识符与已存储的有关 session 会话的数据相关联。

例如,http://w3cschool.cc/file.htm;sessionid=12345,session 会话标识符被附加为 sessionid=12345,标识符可被 Web 服务器访问以识别客户端。

URL 重写是一种更好的维持 session 会话的方式,它在浏览器不支持 cookie 时能够很好地工作,但是它的缺点是会动态生成每个 URL 来为页面分配一个 session 会话 ID,即使是在很简单的静态 HTML 页面中也会如此。

  1. HttpSession

Servlet 还提供了 HttpSession 接口,该接口提供了一种跨多个页面请求或访问网站时识别用户以及存储有关用户信息的方式。

Servlet 容器使用这个接口来创建一个 HTTP 客户端和 HTTP 服务器之间的 session 会话。会话持续一个指定的时间段,跨多个连接或页面请求。

您会通过调用 HttpServletRequest 的公共方法 getSession() 来获取 HttpSession 对象,如下所示:

1
HttpSession session = request.getSession();

你需要在向客户端发送任何文档内容之前调用 request.getSession()。

删除 Session 会话数据

当您完成了一个用户的 session 会话数据,您有以下几种选择:

  • **移除一个特定的属性:**您可以调用 public void removeAttribute(String name) 方法来删除与特定的键相关联的值。
  • **删除整个 session 会话:**您可以调用 public void invalidate() 方法来丢弃整个 session 会话。
  • **设置 session 会话过期时间:**您可以调用 public void setMaxInactiveInterval(int interval) 方法来单独设置 session 会话超时。
  • **注销用户:**如果使用的是支持 servlet 2.4 的服务器,您可以调用 logout 来注销 Web 服务器的客户端,并把属于所有用户的所有 session 会话设置为无效。
  • **web.xml 配置:**如果您使用的是 Tomcat,除了上述方法,您还可以在 web.xml 文件中配置 session 会话超时,如下所示:
1
2
3
  <session-config>
    <session-timeout>15</session-timeout>
  </session-config>

上面实例中的超时时间是以分钟为单位,将覆盖 Tomcat 中默认的 30 分钟超时时间。

在一个 Servlet 中的 getMaxInactiveInterval() 方法会返回 session 会话的超时时间,以秒为单位。所以,如果在 web.xml 中配置 session 会话超时时间为 15 分钟,那么 getMaxInactiveInterval() 会返回 900。

附录