Java Web学习(五)session、cookie、token

文章更新时间:2020/09/14

一、引言

  动态网页兴起后,会话管理变成开发者需要考虑的一个问题,由于HTTP请求是无状态的,为了区分每个用户,此时引入了会话标识(sessionId)的概念,但是存储机制也会产生不同的问题,下面就详细分析一下三种机制的优缺点以及使用方式。

什么是认证(Authentication)

  通俗地讲就是验证当前用户的身份,证明“你是你自己”(比如:你每天上下班打卡,都需要通过指纹打卡,当你的指纹和系统里录入的指纹相匹配时,就打卡成功)

  互联网中的认证:

  • 用户名密码登录
  • 邮箱发送登录链接
  • 手机号接收验证码

只要你能收到邮箱/验证码,就默认你是账号的主人。

什么是授权(Authorization)

  用户授予第三方应用访问该用户某些资源的权限

  • 你在安装手机应用的时候,APP 会询问是否允许授予权限(访问相册、地理位置等权限)
  • 你在访问微信小程序时,当登录时,小程序会询问是否允许授予权限(获取昵称、头像、地区、性别等个人信息)

实现授权的方式有:cookie、session、token、OAuth

什么是凭证(Credentials)

实现认证和授权的前提是需要一种媒介(证书) 来标记访问者的身份

  • 在战国时期,商鞅变法,发明了照身帖。照身帖由官府发放,是一块打磨光滑细密的竹板,上面刻有持有人的头像和籍贯信息。国人必须持有,如若没有就被认为是黑户,或者间谍之类的。
  • 在现实生活中,每个人都会有一张专属的居民身份证,是用于证明持有人身份的一种法定证件。通过身份证,我们可以办理手机卡/银行卡/个人贷款/交通出行等等,这就是认证的凭证。
  • 在互联网应用中,一般网站(如掘金)会有两种模式,游客模式和登录模式。游客模式下,可以正常浏览网站上面的文章,一旦想要点赞/收藏/分享文章,就需要登录或者注册账号。当用户登录成功后,服务器会给该用户使用的浏览器颁发一个令牌(token),这个令牌用来表明你的身份,每次浏览器发送请求时会带上这个令牌,就可以使用游客模式下无法使用的功能。

二、cookie

基础概念

定义:服务器生成一小段文本信息,发送给浏览器,浏览器把 cookie 以kv形式保存到本地某个目录下的文本文件内,下一次请求同一网站时会把该 cookie 发送给服务器。

存储:保存在本地客户端

原理:

  • 1、客户端发送一个请求(http请求+用户认证信息)到服务器
  • 2、认证成功,服务器发送一个HttpResponse响应到客户端,其中包含Set-Cookie的头部
  • 3、客户端提取并保存 cookie 于内存或磁盘
  • 4、再次请求时,HttpRequest请求中会包含一个已认证的 Cookie 的头部
  • 5、服务器解析cookie,获取 cookie 中客户端的相关信息
  • 6、服务器返回响应数据

删除:

  • (1)超时(设置了过期时间,cookie过期后会存储在硬盘里面) 
  • (2)手动删除

存在的问题:

  • (1)cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗。
  • (2)cookie有安全隐患,通过拦截或本地文件找得到你的cookie后可以进行攻击。
  • (3)cookie有大小限制以及浏览器在存cookie的个数也有限制。

小结

  • HTTP 是无状态的协议(对于事务处理没有记忆能力,每次客户端和服务端会话完成时,服务端不会保存任何会话信息):每个请求都是完全独立的,服务端无法确认当前访问者的身份信息,无法分辨上一次的请求发送者和这一次的发送者是不是同一个人。所以服务器与浏览器为了进行会话跟踪(知道是谁在访问我),就必须主动的去维护一个状态,这个状态用于告知服务端前后两个请求是否来自同一浏览器。而这个状态需要通过 cookie 或者 session 去实现。
  • cookie 存储在客户端: cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。
  • cookie 是不可跨域的: 每个 cookie 都会绑定单一的域名,无法在别的域名下获取使用,一级域名和二级域名之间是允许共享使用的(靠的是 domain)。

重要属性

三、session

基础概念

  定义:会话,打开web应用时产生,浏览器第一次访问服务器会在服务器端生成一个 session ,有一个 sessionId 和它对应,用于记录服务器和客户端会话状态

  创建:session 在访问 tomcat 服务器时调用HttpServletRequest.getSession(true)创建。(此时对应的sessionId也同时产生)

  删除:

  • 超时
  • 程序调用HttpSession.invalidate()
  • 程序关闭

  PS:session销毁只能通过invalidate或超时失效,关掉浏览器并不会关闭session。

  存储:session的状态信息等储存在服务器端,但是sessionId是保存客户端的cookie中,客户端不保存session。

认证流程

  • 1、浏览器向服务器发送登录请求(post),携带账号和密码。
  • 2、登录成功,服务器记录登录的状态,并创建session
  • 3、服务器返回请求响应给浏览器,响应头中携带服务器生成的 sessionId(并存放于cookie中),同时 Cookie 记录此 SessionId 属于哪个域名,作为身份标识。
  • 4、浏览器再次访问服务器时,请求会自动判断此域名下是否存在 Cookie 信息,如果存在会将 Cookie 中携带的信息包括 SessionId 发送给服务端。
  • 5、服务器获取到浏览器发送的cookie信息后,会从中寻找 SessionId ,如果找不到,则未登录。
  • 6、如果找到 SessionId ,根据 SessionId 查找对应的对象,登录成功。

  根据以上流程可知,SessionID 是连接 Cookie 和 Session 的一道桥梁,大部分系统也是根据此原理来验证用户登录状态。

Cookie 和 Session 的区别

  • 安全性: Session 比 Cookie 安全,Session 是存储在服务器端的,Cookie 是存储在客户端的。
  • 存取值的类型不同:Cookie 只支持存字符串数据,想要设置其他类型的数据,需要将其转换成字符串,Session 可以存任意数据类型。
  • 有效期不同: Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能,Session 一般失效时间较短,客户端关闭(默认情况下)或者 Session 超时都会失效。
  • 存储大小不同: 单个 Cookie 保存的数据不能超过 4K,Session 可存储数据远高于 Cookie,但是当访问量过多,会占用过多的服务器资源。

存在的问题

  • 负载均衡后,每台机器间的session都需要相互复制,很烦杂
  • 单独把session拿出来存,又会面临这台存session的服务器一宕机,全部的用户都得重新登陆,凉凉...

关于session失效时间的解析

Q:浏览器关闭时,session会同时关闭么?

A:session并不会因为浏览器的关闭而删除!关闭浏览器和session失效没有任何关系, session本身有一个存活时间,在tomcat中默认的是30分钟,打个比方即使浏览器一直开着,如果在30分钟内没有发出任何请求, 那原来存在服务器上的session域内的东西就全没有了, 再次访问的时候,服务器会新建一个session。它的改变是通过session.getMaxInactiveInterval()改变的,当关闭浏览器,再打开浏览器访问的时候,服务器会新建一个session,可以通过session的ID来判断是不是新的session,session的失效除了上述的超时,还有调用invalidate() 或者服务器重启或者中断,所以如果当设置session的MaxInactiveInterval为-1(永不超时)时,并且关闭了浏览器,那么你的session会一直存在,除非重启服务器。

分布式架构下 session 共享方案

1. session 复制

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

  • 优点: 可容错,各个服务器间 session 能够实时响应。
  • 缺点: 会对网络负荷造成一定压力,如果 session 量大的话可能会造成网络堵塞,拖慢服务器性能。

2. 粘性 session /IP 绑定策略

  采用 Ngnix 中的 ip_hash 机制将某个 ip的所有请求都定向到同一台服务器上,即将用户与服务器绑定。 用户第一次请求时,负载均衡器将用户的请求转发到了 A 服务器上,如果负载均衡器设置了粘性 session 的话,那么用户以后的每次请求都会转发到 A 服务器上,相当于把用户和 A 服务器粘到了一块,这就是粘性 session 机制。

  • 优点: 简单,不需要对 session 做任何处理。
  • 缺点: 缺乏容错性,如果当前访问的服务器发生故障,用户被转移到第二个服务器上时,他的 session 信息都将失效。

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

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

3. session 共享(常用)

  使用分布式缓存方案比如 Memcached 、Redis 来缓存 session,但是要求 Memcached 或 Redis 必须是集群

  把 session 放到 Redis 中存储,虽然架构上变得复杂,并且需要多访问一次 Redis ,但是这种方案带来的好处也是很大的:

  • 实现了 session 共享
  • 可以水平扩展(增加 Redis 服务器)
  • 服务器重启 session 不丢失(不过也要注意 session 在 Redis 中的刷新/失效机制)
  • 不仅可以跨服务器 session 共享,甚至可以跨平台(例如网页端和 APP 端)

4. session 持久化

  将 session 存储到数据库中,保证 session 的持久化。

  • 优点: 服务器出现问题,session 不会丢失
  • 缺点: 如果网站的访问量很大,把 session 存储到数据库中,会对数据库造成很大压力,还需要增加额外的开销维护数据库。

四、token

基础概念

  定义:访问资源接口(API)时所需要的资源凭证

  目的:为了减少服务器压力,避免客户端频繁向服务端请求数据,从而频繁查询数据库。

  存储:token一般储存在客户端的cookie中,服务端生成后不保存token(可以存在缓存中),服务端处理每次请求,只做token的校验工作而已。

  常见组成方式:uid(用户唯一的身份标识) + time(当前时间的时间戳) + sign(签名,由token的前几位+盐以哈希算法压缩成一定长的十六进制字符串,可以防止恶意第三方拼接token请求服务器)。

  特点:

  • 服务端无状态化、可扩展性好
  • 支持移动端设备
  • 安全
  • 支持跨程序调用

认证流程

  • 1、客户端使用用户名跟密码请求登录
  • 2、服务端收到请求,去验证用户名与密码
  • 3、验证成功后,服务端签发一个 token 并把这个 token 发送给客户端
  • 4、客户端收到 token 以后,会把它存储起来,比如放在 cookie 里或者 localStorage 里
  • 5、客户端每次向服务端请求资源的时候需要带着服务端签发的 token
  • 6、服务端收到请求,然后去验证客户端请求里面带着的 token ,如果验证成功,就向客户端返回请求的数据

常见的验证方式

方式一:

  • 第一次登陆时,客户端传账号和密码到服务器,服务器先去查询数据库,查询到用户信息后服务器再根据自己的规则生成token并缓存(如redis等技术),再把token回传给客户端(客户端可以把token存到cookie中)。 
  • 第二次登陆时,直接传token给服务器验证缓存中是否存在该token;也可以传账号密码给服务器,让服务器再次生成一次token,用这次生成的token去缓存中校验是否存在。

方式二:

  • 每一次请求都需要携带 token,需要把 token 放到 HTTP 的 Header 里。
  • 基于 token 的用户认证是一种服务端无状态的认证方式,服务端不用存放 token 数据。
  • 用解析 token 的计算时间换取 session 的存储空间【时间换空间】,从而减轻服务器的压力,减少频繁的查询数据库。
  • token 完全由应用管理,所以它可以避开同源策略

  PS:同源策略:浏览器的行为,是为了保护本地数据不被JavaScript代码获取回来的数据污染,如果非同源,那么在请求数据时,浏览器会在控制台中报一个异常,提示拒绝访问。

五、Refresh Token

基础概念

  定义:专用于刷新 access token 的 token。

  优势:

  • 如果没有 refresh token,也可以刷新 access token,但每次刷新都要用户输入登录用户名与密码,会很麻烦【重新输入用户密码去生成access token】。
  • 有了 refresh token,可以减少这个麻烦,客户端直接用 refresh token 去更新 access token,无需用户进行额外的操作。

使用方式

  • Access Token 的有效期比较短,当 Acesss Token 由于过期而失效时,使用 Refresh Token 就可以获取到新的 Token,如果 Refresh Token 也失效了,用户就只能重新登录了。
  • Refresh Token 及过期时间是存储在服务器的数据库中,只有在申请新的 Acesss Token 时才会验证,不会对业务接口响应时间造成影响,也不需要向 Session 一样一直保持在内存中以应对大量的请求。

Token 和 Session 的区别

  • Session 是一种记录服务器和客户端会话状态的机制,使服务端有状态化可以记录会话信息
  • Token 令牌,访问资源接口(API)时所需要的资源凭证,使服务端无状态化不会存储会话信息
  • Session 和 Token 并不矛盾,作为身份认证 Token 安全性比 Session 好,因为每一个请求都有签名还能防止监听以及重放攻击,而 Session 就必须依赖链路层来保障通讯安全了。如果你需要实现有状态的会话仍然可以在使用 Token 的基础上增加 Session 来在服务器端保存一些状态
  • 所谓 Session 认证只是简单的把 User 信息存储到 Session 里,因为 SessionID 的不可预测性,暂且认为是安全的。而 Token ,如果指的是 OAuth Token 或类似的机制的话,提供的是 认证 和 授权 ,认证是针对用户,授权是针对 App 。其目的是让某 App 有权利访问某用户的信息。这里的 Token 是唯一的。不可以转移到其它 App上,也不可以转到其它用户上。Session 只提供一种简单的认证,即只要有此 SessionID ,即认为有此 User 的全部权利。是需要严格保密的,这个数据应该只保存在站方,不应该共享给其它网站或者第三方 App。
  • 简单归纳使用场景:
  • Token :如果你的用户数据可能需要和第三方共享,或者允许第三方调用 API 接口 。
  • Token和session都可以 :如果永远只是自己的网站自己的 App,用什么就无所谓了。

  PS:OAUTH的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此OAUTH是安全的。

六、使用须知

使用 cookie 时需要考虑的问题

  • 因为存储在客户端,容易被客户端篡改,使用前需要验证合法性
  • 不要存储敏感数据,比如用户密码,账户余额
  • 使用 httpOnly 在一定程度上提高安全性
  • 尽量减少 cookie 的体积,能存储的数据量不能超过 4kb
  • 设置正确的 domain【所属域名】 和 path【生效路由】,减少数据传输
  • cookie 无法跨域
  • 一个浏览器针对一个网站最多存 20 个Cookie,浏览器一般只允许存放 300 个Cookie
  • 移动端对 cookie 的支持不是很好,而 session 需要基于 cookie 实现,所以移动端常用的是 token

使用 session 时需要考虑的问题

  • 将 session 存储在服务器里面,当用户同时在线量比较多时,这些 session 会占据较多的内存,需要在服务端定期的去清理过期的 session。
  • 当网站采用集群部署的时候,会遇到多台 web 服务器之间如何做 session 共享的问题。因为 session 是由单个服务器创建的,但是处理用户请求的服务器不一定是那个创建 session 的服务器,那么该服务器就无法拿到之前已经放入到 session 中的登录凭证之类的信息了。
  • 当多个应用要共享 session 时,除了以上问题,还会遇到跨域问题,因为不同的应用可能部署的主机不一样,需要在各个应用做好 cookie 跨域的处理。
  • sessionId 是存储在 cookie 中的,假如浏览器禁止 cookie 或不支持 cookie 怎么办? 一般会把 sessionId 跟在 url 参数后面即重写 url,所以 session 不一定非得需要靠 cookie 实现。
  • 移动端对 cookie 的支持不是很好,而 session 需要基于 cookie 实现,所以移动端常用的是 token

使用 token 时需要考虑的问题

  • 如果你认为用数据库来存储 token 会导致查询时间太长,可以选择放在内存当中。比如 redis 很适合你对 token 查询的需求。
  • token 完全由应用管理,所以它可以避开同源策略
  • token 可以避免 CSRF 攻击(因为不需要 cookie 了)
  • 移动端对 cookie 的支持不是很好,而 session 需要基于 cookie 实现,所以移动端常用的是 token

使用加密算法时需要考虑的问题

  • 绝不要以明文存储密码
  • 永远使用 哈希算法 来处理密码,绝不要使用 Base64 或其他编码方式来存储密码,这和以明文存储密码是一样的,使用哈希,而不要使用编码。编码以及加密,都是双向的过程,而密码是保密的,应该只被它的所有者知道, 这个过程必须是单向的。哈希正是用于做这个的,从来没有解哈希这种说法, 但是编码就存在解码,加密就存在解密。
  • 绝不要使用弱哈希或已被破解的哈希算法,像 MD5 或 SHA1 ,只使用强密码哈希算法。
  • 绝不要以明文形式显示或发送密码,即使是对密码的所有者也应该这样。如果你需要 “忘记密码” 的功能,可以随机生成一个新的 一次性的(这点很重要)密码,然后把这个密码发送给用户。

 

 

参考资料:

posted @ 2019-08-23 18:16  有梦想的肥宅  阅读(1308)  评论(0编辑  收藏  举报