引入

  • 场景
    • 场景一:登录京东,选了iphone x放入购物车后关闭浏览器,再次打开时,发现又要重新登录;
    • 场景二:登录淘宝,在一个页面逗留了很久,终于决定要买了,却提示你重新登录;
  • 服务端“贵人多忘事”,被太多客户端访问,而无法记住有谁,在跟谁交谈,出于安全考虑,服务器只能强制访问者每次发起会话都必须重新登录,并验证是否是合法用户;
  • 思考:Web 请求与响应是基于 HTTP 协议,而 HTTP 是一种无状态协议,即服务端不能判断接收的哪些消息属于同一个客户端/浏览器端,那么如何实现购物车行为呢?

    1. 会话机制

    1.1 概念

  • 会话:从打开一个浏览器访问某个站点,到关闭这个浏览器的整个过程,称为一次会话;

    • 浏览器是怎么访问到站点的?

Cookie 与 Session - 图1

  • 发起会话后,服务端会有什么反应,或如何同意建立会话?即建立会话的过程
  • 为什么浏览器一关闭,会话就结束了?
  • 关闭时是什么操作导致会话结束的?为什么?
  • 服务端如何判断会话结束?
  • 延伸:会话内容可以是什么,是如何传输的?
    • 会话机制(标识用户,跟踪状态)
  • 为了跟踪会话对象,确保会话过程,便引入了会话机制;
  • 通过会话机制,服务器可以记住并区分客户端,把同一个客户端的操作归类在一起;

    1.2 会话的建立与结束

  • 一个会话不同于一次请求,这个“一”体现在对话双方的确定,确定的两方发起的多次请求同属于一个会话;
    • 服务端如何断定请求来自同一个客户端,即保证每个对话的独立性?(如何建立会话管理机制)
      • 解决一:隐藏域
        • 由浏览器在每次请求时主动告诉服务器多次请求间的必要信息;
        • 局限:关掉网页之后,就会遗失信息,而且查看网页源代码时,容易暴露信息,安全性不高;
      • 解决二:存在客户端的 cookie
      • 解决三:存在服务端的 session
  • 一个会话可以帮助服务器断定一个客户端,反过来说,当服务器无法断定客户端时,一次会话就结束了;

    • 服务器在什么情况下无法断定客户端?
      • 会话是依赖于对话双方才建立的,所以当双方中的任意一方 或 两方无法识别时,会话结束;
    • 每次会话中都有一个互相确定的身份信息,同样的对话双方,如果由于某些原因,重新申请或修改了身份信息,那么就会开启另一个新的会话;

      2. 客户端 cookie

      2.1 什么是 cookie

  • cookie 技术是客户端的解决方案,cookie 是由服务器响应给客户端的代表身份证明的数据信息,传送时存在 http 响应头中,并以文本形式存储在在客户端的指定位置,之后客户端每次向服务器发送请求的时候都会自动带上 cookie,服务器通过 cookie 可以区分客户端;

    • cookie 是存储在浏览器中的纯文本,浏览器的安装目录下会专门有一个 cookie 文件夹来存放各个域下设置的cookie
      • 文本体量小,数据通常会进行加密处理,当用户连接到服务器,web 站点可以访问 cookie 信息;
      • 格式:键值对 key=value 构成,键值对之间由一个分号和一个空格隔开;
      • 实例:”key=name; expires=Thu, 25 Feb 2016 04:18:00 GMT; domain=ppsc.sankuai.com; path=/; secure; HttpOnly”;
    • 当网页要发 http 请求时,浏览器会先检查是否有相应的 cookie,有则自动添加在 request header 中的cookie 字段中;
    • cookie 中适合存放的数据:每次发起请求时都需要携带的信息,比如身份认证信息;
    • cookie 中也包含计算机和浏览器信息,如果在一台计算机中安装多个浏览器,则每个浏览器都会在各自独立的空间存放 cookie,一个用户用不同的浏览器登录或计算机登录,都会得到不同的 cookie 信息,注意:对于在同一台计算机上使用同一浏览器的多用户群,除非使用不同用户名,否则cookie 不会区分他们的身份;
  • cookie 的应用场景
    • 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息);
    • 个性化设置(如用户自定义设置、主题等);
    • 浏览器行为跟踪(如跟踪分析用户行为等);
  • cookie 的属性选项
    • name:一个唯一确定的 cookie 名称,通常来讲 cookie 的名称不区分大小写;
    • value:存储在 cookie 中的字符串值,最好为 cookie 的 name 和 value 进行url编码
    • expires/max-age:设置日期,必须是 GMT 格式的时间(可通过 new Date().toGMTString() 或 new Date().toUTCString() 来获得);
      • 对于失效的 cookie,浏览器会自动清空,若不设置,默认是会话 cookie,浏览器关闭即失效;
      • expires 的值是一个时间点(cookie 失效时刻 = expires),而 max-age 的值是一个以秒为单位时间段(cookie 失效时刻= 创建时刻+ max-age);
      • max-age:负数(-1 等同于会话 cookie)、0(删除 cookie)、正数(有效时间段);
    • domain(域名)& path(路径):组合成 URL,决定 cookie 能被哪些 URL 访问;
      • domain 默认为设置该 cookie 的网页所在的域名,path 默认为设置该 cookie 的网页所在的目录;
      • 决定 cookie 何时被自动放在 request header 中发送出去;
    • secure:设置 cookie 只在确保安全的请求中才会发送,当请求是 HTTPS 或其他安全协议时,包含 secure 选项的 cookie 才能被发送至服务器;
      • 仅限定在安全情况下才可以传输给服务端,但并不代表在浏览器端不能看到有此选项的 cookie;
    • httpOnly:用于设置 cookie 是否能通过 js 去访问,当 cookie 带此选项时,客户端则无法通过 js 代码去访问(包括读取:document.cookie 、修改、删除等)这个 cookie,从而提供安全保障;
  • cookie 的局限
    • cookie`数量和长度的限制:每个 domain 最多只能有20条 cookie,每个 cookie 长度不能超过 4KB,否则会被截掉;
    • cookie 中只能保管 ASCII 字符串,若要存取 Unicode 字符或者二进制数据,需先进行编码;
    • 用户可配置为禁用:有些用户禁用了浏览器或客户端设备接收 cookie 的能力,因此限制了这一功能;
    • 由于在 HTTP 请求中的 cookie 是明文传递的,存在潜在的可能会被篡改的安全风险;
    • 有些状态不可能保存在客户端;
    • cookie 会被附加在每个 HTTP 请求中,无形中增加流量;
    • cookie 一般不可跨域使用;
    • 没有封装好的setCookie和getCookie方法,需要开发者自行封装;
  • 补充

    • name/domain/path 这3个字段都相同的时候,cookie 会被覆盖;

      2.2 cookie 是如何建立一次会话的

  • cookie 既可以由服务端来设置,也可以由客户端来设置;

    2.2.1 服务端设置 cookie

  • 浏览器访问到服务器这边的 SendCookieServlet,Servlet 通过 Response 向客户端发送 cookie;

  • response header 中会存多个 set-cookie 字段,每个字段对应一个 cookie(不能将多个 cookie 放在一个set-cookie字段中),set-cookie 字段的值是普通的字符串,每个 cookie 会设置相关属性选项;
    • cookie 的名称和值可以由服务器端开发者自定义;

Cookie 与 Session - 图2

  • 服务器端如何获得浏览器端保存的 cookie:request.getCookie()

Cookie 与 Session - 图3

2.2.2 客户端设置 cookie

  • 案例:在某个用例流程中,由 A 页面跳至 B 页面,若在 A 页面中采用 JS 用变量 temp 保存了某一变量的值,在B 页面的时候,同样需要使用 JS 来引用 temp 的变量值,对于 JS 中的全局变量或者静态变量的生命周期是有限的,当发生页面跳转或者页面关闭的时候,这些变量的值会重新载入,即没有达到保存的效果,解决这个问题的最好的方案是采用 cookie 来保存该变量的值,那么如何来设置和读取 cookie;
  • 户端可以设置cookie 的下列选项:expires、domain、path、secure(有条件:只有在 https 协议的网页中,客户端设置secure类型的 cookie 才能成功),但无法设置HttpOnly选项;
  • 用 JS 设置多个 cookie:重复执行 document.cookie = “key=name”;

document.cookie = “name=Jonh”;
document.cookie = “age=12”;
document.cookie = “class=111”;

2.3 cookie 是如何结束会话的

  • 客户端的 cookie 失效
    • cookie 有个 maximum age 属性,如果不设置或 过期时间设置为 datetime.minvalue,默认关闭浏览器时,cookie 会被自动清除,失效;
  • 修改或删除 cookie

    • 修改一个 cookie,只需要对旧值重新赋值;
    • 删除一个cookie 也是重新赋值,将新 cookie 的 expires 选项设置为一个过去的时间点;
    • 注意:path/domain 这几个选项一定要旧 cookie 保持一样,否则不会修改旧值,而是添加了一个新的 cookie;

      3. 服务器端 session

      3.1 什么是 session

      3.1.1 session 建立会话的机制

  • session 机制是一种服务器端的机制,服务器使用一种类似于散列表的结构来保存信息,当程序需要为某个客户端的请求创建一个 session 时,服务器首先检查这个客户端的请求里是否已包含一个 session ID,如果已包含则说明以前已经为此客户端创建过 session,服务器就按照 session ID 把这个 session 检索出来使用,否则为此客户端创建一个 session 并生成相应的的session ID,自动 new 一个 cookie 并设置 set-cookie 为 session ID,通过 Response 返回给客户端,客户端会记录此 sessionID 属于哪个域名;

    • 不同 Servlet 获取到的 Session 的 id 号都是不同的;

Cookie 与 Session - 图4

3.1.2 双方如何保存 session 信息及有何补偿机制

  • 服务器端如何存储 session
    • 存储地址:内存、数据库、文件,集群时要考虑 session 的转移;
    • session 序列化:服务器重启时,为防止 session 丢失,服务器会将当前内存中的 session 序列化到磁盘中,等重启完毕,又重新读取回内存;
  • 客户端如何保存 seeesion ID
    • url、cookie、request header 等
    • 由于 cookie 可被人为禁止,必须有补偿机制,可以保存并传递session ID;
      • URL重写:把 session id 直接附加在URL路径的后面;
        • HttpServletResponse类提供了两个URL地址重写的方法:

encodeURL(String url)
encodeRedirectURL(String url)

  1. - 表单隐藏字段:服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把 session id 传递回服务器;

3.1.3 服务器端如何防止过多 session 造成内存负担

  • 钝化与活化机制:如果一个 session 长时间无人访问,为了减少内存占用,会被序列化到磁盘中(钝化),当该 session 再次被访问时,才会被反序列化(活化);
  • 可在 Tomcat 的 conf 目录下的 context.xml 中配置(对所有项目生效)

Cookie 与 Session - 图5

3.1.4 对比 cookie 与 session

  • 可存储的数据
    • cookie 中不能直接存取 Java 对象 或 复杂信息;
    • Session 中能够存取任何类型的数据,包括且不限于 String、Integer、List、Map 等,也能保管 Java Bean、类对象等,即 Session 可被看做是一个 Java 容器类;
  • 隐私策略
    • cookie 为保证安全,一般会搭配加密手段,而 session 存在服务器上,没有被用户篡改等较大风险;
  • 有效期
    • cookie 可设置较长的失效时间,而 session 依赖名为 JSESSIONID 的 cookie,而此 cookie 的过期时间默许为 –1,浏览器一关闭,session 就会失效,而服务器端,session 有效时间不能过长,否则会造成服务器端的内存负担,造成溢出;
  • 服务器压力
    • 若并发访问的用户很多,在服务器端会产生很多 session,导致耗费大量内存,而 cookie 保存在客户端,不占用服务器资源,所以 cookie 适用于并发用户较多的场景;
  • 跨域支持

    3.2 基本方法

    ```java boolean isNew() //返回是否为一个新的客户端,或者客户端是否拒绝加入session void invalidate() //将session无效化,解绑任何与该session绑定的对象 Enumeration getAttributeNames() //返回Session对象中存储的每一个属性对象,其结果为一个类举的实例. long getCreationTime() //返回session对象被创建的时间, 以毫秒为单位,最小单位为千分之一秒,从1970年1月1号凌晨开始算起 String getId() //此方法返回惟一的标识,这些标识为每个Session而产生.当只有一个单一的值与一个Session联合时,或当日志信息与先前的Session有关时,它被当做键名用. long getLastAccessedTime() //返回客户端最后访问的时间,以毫秒为单位,最小单位为千分之一秒,从1970年1月1号凌晨开始算起 void setMaxInactiveInterval(int interval) //用来指定时间,以秒为单位,servlet容器将会在这段时间内保持会话有效 int getMaxInactiveInterval() //返回最大时间间隔,以秒为单位,servlet 容器将会在这段时间内保持会话打开,负值表示Session永远不会超时 void setAttribute(String name, Object value) //使用指定的名称和值来产生一个对象并绑定到session中 Object getAttribute(String name) //返回session对象中与指定名称绑定的对象,如果不存在则返回null void removeAttribute(String name) //移除session中指定名称的对象

// 删除 Session 数据

  1. 移除一个特定session属性: void removeAttribute(String name)
  2. 删除整个会话: public void invalidate()
  3. 设置会话有效期: void setMaxInactiveInterval(int interval)
  4. 退出登录: 支持 servlet2.4版本的服务器,可以调用 logout()方法来登出用户,并且使所有相关的session无效。
  5. 设置服务器的超时时间,自动删除session: 如果使用的是Tomcat,可以向下面这样配置web.xml文件: 15 超时以分钟为单位,Tomcat中的默认的超时时间是30分钟。 ```

    3.3 如何保证分布式 session 的一致性

  • 案例:在互联网公司为了可以支撑更大的流量,后端往往需要多台服务器共同来支撑前端用户请求,那如果用户在 A 服务器登录了,第二次请求跑到服务 B 就会出现登录失效问题;
  • 解决方案

    • Nginx ip_hash 策略:服务端使用 Nginx 代理,每个请求按访问 IP 的 hash 分配,这样来自同一 IP 固定访问一个后台服务器,避免了在服务器 A 创建 Session,第二次分发到服务器 B 的现象;
    • Session 复制:任何一个服务器上的 Session 发生改变(增删改),该节点会把这个 Session 的所有内容序列化,然后广播给所有其它节点;
    • 共享 Session:服务端无状态话,将用户的 Session 等信息使用缓存中间件来统一管理,保障分发到每一个服务器的响应结果都一致,推荐;

      3.4 实战篇_实现用户登录和购物车

      4. 延伸

      4.1 跨域问题

      4.4.1 如何理解“跨域”

  • 浏览器的同源策略 SOP(Same origin policy):一种约定,由 Netscape 公司 1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到 XSS、CSFR 等攻击;

    • 所谓同源是指”协议+域名+端口”三者相同,即便两个不同的域名指向同一个 ip 地址,也非同源;
  • 具体可参考 web跨域及cookie相关知识总结

    4.4.2 cookie 不可跨域性

  • 案例

    • 运行 javascript:document.cookie=’myname=laihuamin;path=/;domain=.baidu.com’;

    javascript:document.cookie=’myname=huaminlai;path=/;domain=.google.com’;

    • 结果:真正能把 cookie 设置上去的只有 domain 是 .baidu.com 的 cookie 绑定到了域名上;

Cookie 与 Session - 图6

  • cookie 的不可跨域性是指:不能在不同的域名下用,每个 cookie 都会绑定单一的域名;

    4.2 Token

  • 产生背景

    • 客户端频繁向服务端请求数据,服务端频繁的去数据库查询用户名和密码并进行对比,判断用户名和密码正确与否,并作出相应提示,频繁查找和 session 表维护给服务器的造成内存压力;
  • 定义:Token 是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个 Token 便将此 Token 返回给客户端,以后客户端只需带上这个 Token 前来请求数据即可,无需再次带上用户名和密码;
  • 使用
    • 客户端:得到 token 后,可存储在 cookie 或 localstorage 中;
    • 每次发送请求都要带着这个 token;
    • 发送时可放在 cookie 、HTTP 请求头信息 Authorization 字段或 post 请求求数据体中;
  • token 与 session
    • session, 要求服务端存储信息,并根据id能够检索,而 token 不需要;
    • 服务端要通过 token 来解析用户身份,也需要定义好相应的协议;
    • session 一般使用 cookie 来交互,token,可以是 cookie,也可以是其他heaser,甚至可以放在请求的内容中,其中不使用 cookie 可以带来跨域的便利性;
    • token 技术对应的标准,就是 JWT,详见 Java Web 基于 Token 的身份验证
  • JWT(JSON Web Tokens)
    • Header(头部)(是一个JSON 对象)

{
“alg”: “HS256”, // 表示签名的算法,默认是 HMAC SHA256(写成 HS256)
“typ”: “JWT” // 表示Token的类型,JWT 令牌统一写为JWT
}

  • Payload(负载)(也是一个JSON 对象— 用来存放实际需要传递的数据)

{
// 7个官方字段
“iss”: “a.com”, // issuer:签发人
“exp”: “1d”, // expiration time:(必须设置)
“sub”: “test”, // subject: 主题
“aud”: “xxx”, // audience: 受众
“nbf”: “xxx”, // Not Before:生效时间
“iat”: “xxx”, // Issued At: 签发时间
“jti”: “1111”, // JWT ID:编号
// 可以定义私有字段
“name”: “John Doe”,
“admin”: true
}

  • Signature(签名)对前两部分的签名,防止数据被篡改,需要指定一个密钥(secret),采用的公式:header 中的签名算法(base64UrlEncode(header) + ‘.’ + base64UrlEncode(payload), secret)JWT = Base64(Header) + “.” + Base64(Payload) + “.” + $Signature;
    • JWT 的特点
  • JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分;
  • 发送 JWT, 要使用 https,否则 JWT 里面不要写入秘密数据;
  • 在使用过程中无法废除 某个 token,或者更改 token 的权限,也就是一旦签发就会始终有效;
  • LocalStorage 与 SessionStorage
    • 用于存储客户端临时信息,储存的对象类型均为字符串类型;
    • sessionStorage 用于本地存储一个会话(session)中的数据,这些数据只有在同一个会话中的页面才能访问,并且当会话结束后数据也随之销毁;
    • localStorage 用于持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的,同源(相同浏览器的不同页面间)可以读取并修改 localStorage 数据;
    • 不同浏览器无法共享 localStorage 或 sessionStorage 中的信息;
    • 如果一个标签页包含多个 iframe 标签且其属于同源页面,则他们之间可以共享 sessionStorage;

Cookie 与 Session - 图7

5. 问题与思考