session共享问题

一。session会话机制

由于http 协议的特性是无状态性 ,http 协议本身是无状态的,客户端只需要向服务器请求下载某些文件,无论是客户端还是服务器都没必要记录彼此过去的行为,每一次请求之间对于服务端来说是独立的,如果能够提供一些按照需要生成的动态信息会使 web 变得更加有用。假如一个需要登录授权才能执行的动作,因为 http 协议没办法保存这个登录的用户的状态,因此当下一次再执行一个需要授权操作时,还需要再次登录,这将导致用户体验非常差。所以需要一种机制能够识别每次请求的用户,来实现会话保存的目的。针对这种情况提供的解决方法是session和cookie。
1.Session
服务端提供了一种叫 Session 的机制,对于每个用户的请求,会生成一个唯一的标识。当程序需要为某个客户端的请求创建一个 session 的时候,服务器首先检查这个客户端的请求是否包含了一个 session 标识session id;如果已包含一个 session id 则说明以前已经为客户端创建过 session,服务器就按照 session id 把这个 session 检索出来使用(如果检索不到,会新建一个);如果客户端请求不包含 sessionid,服务端会为此客户端创建一个session 并且生成一个与此 session 相关联的 session id,session id 的值是一个既不会重复,又不容易被找到规律的仿造字符串。
2.Cookie
客户端是如何存储 sessionid 的呢?利用cookie保存。浏览器提供了一种叫 cookie 的机制,保存当前会话的唯一标识。每次 HTTP 请求,客户端都会发送相应的 Cookie 信息到服务端。客户端第一次请求,由于 cookie 中并没有携带 sessionid,服务端会创建一个 sessionid,写入到客户端的 cookie 中。以后每次请求,都会携带这个 id 给到服务器端。这样一来,便解决了无状态的问题。如果客户端浏览器禁用了 cookie,一般会通过 URL 重写的方式来进行会话,也就是在 url 中携带 sessionid。

二。session共享问题是如何产生的

如果网站请求流量较大,那么单台 服务器(tomcat) 设备是无法承接这些流量的,这个时候就需要开始对服务器做集群,采用了多台机器做集群以后,就要通过一种机制来实现请求的路由。因为对于用户来说,访问的是一个域名,至于后端请求到哪一台服务器,用户并不需要关心,这里一般会使用负载均衡设备。当有多台web服务器时就需要用到session共享,
例如:当我们有两台服务器时,用户登录后在服务器1存下一个用户登录的session,而我们负载均衡的方式又不是采用的哈希算法,当用户访问别的网页时,恰好使用的服务器不是服务器1而是服务器2,这样服务器2就取不到用户的session,会提示用户要登录,这样不符合常理,所以就有了session共享。

如果你的网站是存放在一个机器上,那么是不存在这个问题的,因为会话数据就在这台机器,但是如果你使用了负载均衡把请求分发到不同的机器呢?这个时候会话id在客户端是没有问题的,但是如果用户的两次请求到了两台不同的机器,而它的session数据可能存在其中一台机器,这个时候就会出现取不到session数据的情况,于是session的共享就成了一个问题。

三。Session 共享问题的解决方法

Session 共享问题,已经有非常多的解决方案
1.session sticky ,会话粘制
session sticky(粘性) , 保证同一个会话的请求都在同一个web 服务器上处理,这样的话,就完全不需要考虑到会话的问题了。比如前面说的负载均衡算法中,哈希算法就是一个典型的实现手段。
这种实现方式会有些问题:
(1)如果一台 web 服务器宕机或者重启,那么这台机器上保存的会话数据都会丢失,会造成用户暂时无法访问的问题,或者用户之前的授权操作需要再执行一次。
(2)通过这种方式实现的 session 保持,没有办法进行 4 层网络转发,只能在 7 层网络上进行解析并转发,通过相关技术实现 session 复制,使得集群中的各个服务器相互保存各自节点存储的 session 数据。
2.session复制
让两台服务器的session相互备份。tomcat 本身通过配置文件就可以实现 session 复制的功能,基于 IP 组播放方式。
这种实现方式存在的问题:
(1)同步 session 数据会造成网络开销,随着集群规模越大,同步 session 带来的带宽影响也越大。
(2)每个节点需要保存集群中所有节点的 session 数据,就需要比较大的内存来存储。
3.session 统一存储
集群中的各个节点的 session 数据,统一存储到一个存储设备中。每个节点去拿 session 的时候,就不是从自己的内存中去获得,而是从相应的第三方存储中去拿。对于这个方案来说,无论是哪个节点新增或者修改了 session 数据,最终都会发生在这个集中存储的地方。这个存储设备可以是 redis、也可以是 mysql。
session共享问题
这种实现方式存在的问题:
(1)读写 session 数据需要进行网络操作,存在不稳定性和延迟性。
(2)如果存储 session 的服务器出现故障,将大规模的影响到应用
4.Cookie Based
Cookie Based 方法,简单来说,就是不依赖容器本身的Session 机制,而是服务端基于一定的算法,生成一个 token 给到客户端,客户端每次请求,都会携带这个 token。当服务端收到 token 以后,先验证 token 是否有效,再解密这个 token 获取关键数据进行处理session 统一存储实践。
Cookie Based 实现方式:
基于纯 Cookie 的方式,也就是客户端每次请求都携带身份信息给到服务端。比较典型的方式是 JWT,全称是 JSONWeb Tokens,是一种简洁的并且在两个计算机之间安全传递信息的表述性声明规范。JWT 的声明一般被用来在客户端和服务端之间传递被认证的用户身份信息,以便于从资源服务器获取资源。比如在用户登录上,基于 JWT 的交互过程和最原始的 cookie+session 方式是不一样的,JWT 强调的是服务端不对 token进行存储,而是直接通过签名算法验证并解密 token 得到相应数据进行处理。
session共享问题
JWT token 的组成
jwt token 由三个部分组成,头部(header)、有效载荷(playload)、签名(signature),打开网站:https://jwt.io/
session共享问题
header
格式如下,typ 和 alg 分别对应的全称是 type(类型)和algorithm(算法),类型可以自定义。alg:hs256 表示当前 token是使用 HS256 算法来进行加密的。zip表示如果内容太多的话,生成的签名就会较长,可以采用gzip/DEFLATE压缩算法

{ 
“typ”: “JWT”(默认), 
“alg”: “HS256” ,
“zip”:“gzip/DEFLATE”
}
playload
{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

这是标准的声明,sub代表当前JWT的主体,即所有者,name不是标准的,可以自定义;iat代表当前JWT的签发时间,是一个时间戳;还可以自定义其他的内容,如”uid”:”1”;exp(过期时间)等
Payload 里面是 Token 的具体内容,也是一个 json 字符串,这些内容里面有一些是标准字段,也可以添加其它需要的内容;payload 的 json 结构并不像 header 那么简单,payload 用来承载要传递的数据,它的 json 结构实际上是对 JWT 要传递的数据的一组声明,这些声明被 JWT标准称为 claims , JWT 默认提供了一些标准的 Claim,具体内容如下:
每一个 claim 都有特定的含义和作用,

iss(Issuser):代表这个 JWT 的签发主体;
sub(Subject):代表这个 JWT 的主体,即它的所有人;
aud(Audience):代表这个 JWT 的接收对象;
exp(Expiration time):是一个时间戳,代表这个 JWT 的过期时间;
nbf(Not Before):是一个时间戳,代表这个 JWT 生效的开始时间,意味着在这个时间之前验证 JWT是会失败的;
iat(Issued at):是一个时间戳,代表这个 JWT 的签发时间;
jti(JWT ID):是 JWT的唯一标识。按照 JWT 标准的说明:标准的 claims 都是可选的。

在生成playload 不强制用上面的那些 claim,可以完全按照自己的想法来定义 payload 的结构,不过这样搞根本没必要:
第一,如果把 JWT 用于认证, 那么 JWT 标准内规定的几个claim就足够用了,甚至只需要其中一两个就可以了,假如想往 JWT 里多存一些用户业务信息,比如角色和用户名等,这倒是用自定义的 claim 来添加;第二,JWT 标准里面针对它自己规定的 claim 都提供了有详细的验证规则描述,每个实现库都会参照这个描述来提供 JWT 的验证实现,所以如果是自定义的 claim 名称,那么用到的实现库就不会主动去验证这些 claim。
signature(签名)

  HMACSHA256(
      base64UrlEncode(header) + "." +
      base64UrlEncode(payload),
      your-256-bit-secret
    ) secret base64 encoded

header,payload用base64编码,再把header,payload用HMACSHA256算法生成一个签名,
在这个签名里面用your-256-bit-secret生成 一个秘钥

创建签名需要使用编码后的 header 和 payload 以及一个**,使用 header 中指定签名算法进行签名。组成格式如下:
header (base64 后的)
payload (base64 后的)
secret
这个部分需要 base64 加密后的 header 和 base64 加密后的 payload 使用.连接组成的字符串,然后通过 header 中声明的加密方式进行加盐 secret 组合加密,然后就构成了jwt 的第三部分。最后将这 3 个部分组成一个完整的字符串构成了 JWT:
base64(header)+”.”+base64(payload)+”.”+sinaturesecret 是保存在服务器端的,jwt 的签发生成也是在服务器端的,secret 就是用来进行 jwt 的签发和 jwt 的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个 secret, 那就意味着客户端是可以自我签发 jwt 了。也就是说,只有在算法被攻破和秘钥泄露的情况下才会有秘钥丢失。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
cThIIoDvwdueQB468K5xDc5633seEFoqwxjF_xSJyQQ
这是生成的整个字符串,包括三部分head,payload,秘钥。

jwt 实际上就是定义了一套数据加密以及验签的算法的规范(或者说是封装),根据这个规范来实现单点登录,以及数据传输及验签功能。但是这个方案会存在一些问题,比如:不能传递敏感信息,因为 jwt 中的部分内容可以解密,只是不能修改而已。

上一篇:分布式事务
下一篇:负载均衡介绍