关于浏览器存储与登录鉴权我所知道的。

1,279 阅读10分钟

Cookie LocalStorage SessionStorage 异同

CookielocalStoragesessionStorage
生命周期一般由服务器生成,可设置失效时间。如果是浏览器生成(会话期cookie),默认关闭浏览器失效除非被清除,否则永久保存仅在当前浏览器窗口关闭前有效
存放数据4K5M5M
http每次请求都会携带在http头中,如果使用cookie保存过多数据会有性能问题仅在客户端保存,不参与服务器通信同左
应用场景由于http请求,尽可能少用,浪费带宽。只能指定作用域,不可跨域使用。在识别用户有优势页面传递参数保存临时数据,放置用户刷新页面丢失参数

拓展

Q:什么是 XSS 跨站脚本攻击?如何解决?
A:在返回的 html 中嵌入 JS。在 http 头部配置 set-cookie. httponlyxss,禁止 js 访问 cookiesecurehttps 发送 cookie

鉴权

HTTP 身份验证

http 提供的身份验证框架,http Basic auth 是最常见的认证方案。

什么是 HTTP 身份验证


展示交互:输入 url ,浏览器出现弹窗或者表单。完善信息,登录。
前后端交互:

  1. 服务器端向客户端返回 401 状态码,并在 WWW-Authenticate 首部提供如何进行验证的信息,其中至少包含有一种质询方式。
  2. 客户端会弹出一个密码框让用户填写,然后发送包含有恰当的 Authorization 首部的请求。客户端在新的请求中添加 Authorization 首部字段进行验证,字段值为身份验证凭证信息。

由于http是无状态协议,所以客户端每次发出请求,本次请求无法得知上一次请求包含的状态数据。

适用场景

内部安全性不高的系统上,例如路由器网页管理接口

问题

  • 请求上携带验证信息,容易被嗅探到
  • 无法注销
  • 适合一次性验证,例如注册激活链接

Session 会话

首先我们需要分清楚 SessionCookie 是两个维度的东西,Session是一种会话机制,Cookie是一种存储方式。
Session 的出现解决了 http 身份验证 无法注销的问题。

什么是 Session

Session 是一种会话机制。通过在 Cookie 中保存的用户id请求后端获取数据。本质是不想在cookie中保存太多数据,为了绕开cookie的各种限制。
Cookie 在客户端,有被窃取的可能,编码方式:encodeURI()

简而言之:
浏览器通过账号密码让服务端校验用户并返回 sessionId,把 sessionId 存在 cookie 上,利用请求头自动带上 cookie 值的特点,每次请求都会校验 sessionId 并返回结果。

适用场景

适合传统系统独立鉴权

问题

如果服务是分布式的,需要文件单独存储 sessionId,多个服务器之间存在同步 sessionId 的问题,高并发情况下错误读写锁。

Token 身份验证

什么是 Token

现在服务端都是集群,用户请求会走负载均衡,如果接口请求与登录请求的机器不一致,session 就失效了。所以只能把 sessionId 集中储存。sessionId 的维护就给服务器带来困扰,需要单独有个地方存储。

为了解决这个问题,就有了 token 这个解决方案:与 sessionId 相比,① 不需要单独存储。② 不用每次请求 session 库校验。服务器端生成,只要加密方法一致,不管那台机器都可以校验 token 是否有效,服务器认证鉴权业务方便拓展。

什么是 JWT

我们常说的 JWT 其实是对 token 进行格式化(规范)。
服务器认证之后生成一个JSON对象,为了防止用户篡改数据,在生成对象的时候带上签名,保存在浏览器端,每次请求都带上这个token。header(头部),payload(裁荷), signature (签证) 这三部分以小数点连接起来组成 JWT。校验非常简单,服务端计算出签名与 JWT 中的签名部分比对就可以了。

适用场景

  • 适合做 RESTful API 无状态认证
  • 适合一次性验证,例如注册激活链接
  1. 前端每次路由跳转,判断 localStroage 或者 Cookie 有无 token,没有则跳转到登录页。有则请求获取用户信息,改变登录状态。
  2. 前端每次向服务端请求资源的时候需要在请求头里携带服务端签发的 token。服务端收到请求,然后去验证前端请求里面带着的 token。没有或者 token 过期,返回 401。如果验证成功,就向前端返回请求的数据,前端得到 401 状态码,重定向到登录页面。

问题

  • 使用过程中无法废弃某个 token,有效期内 token 一直有效。
  • payload 信息更新时,已下发的 token 无法同步
  • 为了减少 token 泄露风险,一般有效期会设置的比较短。这样就会存在 Token 过期的情况,我们不可能让用户频繁去登录获取新的 roken。和 session 的问题点类似。

拓展

Refresh Session

不能在用户登录之后由于 SessionId 失效就把用户踢出去,如果在 SessionId 有效期间用户一直在操作,这时候失效时间应该更新。

  1. 前后端交互需要封装一层middleware,默认情况下所有请求都会经过该 middleware。如果校检 SessionId 有效,就更新 SessionIdexpines: 当前时间 + 过期时间。
  2. 频繁更新 SessionId 会影响性能,可以在 SessionId 快过期的时候再更新过期时间。
  3. 如果某个用户一直在操作,同一个 sessionId 会长期有效,如果相关 cookie 泄露,存在较大安全风险。可以在生成 sessionId 的同时生成一个 refreshId ,在 sessionId 过期之后使用 refreshId 请求服务端生成新的 sessionId (这个方案需要前端判断 sessionId 失效,并携带 refreshId 发请求)。

如果 refreshId 如果也过期,用户就该重新登录了。

Refresh token

和 Refresh Session 原理相同。
可以同时生成 Token 与 Refresh Token,其中 Refresh Token 的有效时间长于 Token,这样当 Token 过期之后,使用 Refresh Token 获取新的 Token 与 Refresh Token,其中 Refresh Token 只能使用一次。

举例
token 是钥匙,我们拿钥匙去开锁(请求数据),发现钥匙破损了(过期),这时候我们还有把备用钥匙(Refresh Token),备用钥匙是一次性的。我们拿备用钥匙去开锁,进屋之后把屋里的钥匙和备用钥匙一起拿走出门。

单设备登录

有些情况下,只允许一个帐号在一个端下登录,如果换了一个端,需要把之前登录的端踢下线。
这时候可以借助一个服务保存用户唯一标识 uuid**sessionId** 值的对应关系,如果同一个用户,但 sessionId 不一样,则不允许登录或者把之前的踢下线(删除旧 sessionId)。
通过token去实现也是一样的原理。

Oauth

Oauth 是一个开放标准,允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容。

OAuth 使用第三方账号登录举例:

  1. leetcode 网站让用户跳转到 GitHub,请求授权码,GitHub要求用户登录,然后询问“网站要求获得xx权限,你是否同意?”
  2. 用户同意,GitHub就会重定向回 leetcode,同时发回一个授权码;
  3. leetcode 使用授权码,向GitHub请求令牌;
  4. GitHub 返回令牌,leetcode 使用令牌,向 GitHub 请求用户数据

Oauth 分为下面四种模式:

  • 简化模式,不安全,适用于纯静态页面应用,令牌存储在前端
  • 授权码模式,功能最完整、流程最严密的授权模式,通常使用在公网的开放平台中
  • 密码模式,一般在内部系统中使用,调用者是以用户为单位。高度信任某个应用,允许用户把用户名和密码,直接告诉该应用。该应用就使用你的密码,申请令牌。
  • 客户端模式,一般在内部系统之间的 API 调用。两个平台之间调用,以平台为单位。适用于没有前端的命令行应用,即在命令行下请求令牌。

单点登录(SSO)

例如:登录QQ空间之后,我们去访问QQ邮箱不需要重新登录。
对浏览器来说,SSO 域下返回的数据要怎么存,才能在访问 A 的时候带上?
浏览器对跨域有严格限制,cookie、localStorage 等方式都是有域限制的。关键的技术点在于提供 SSO 域下存储凭证的能力。

有两种实现方式

  1. cookie 配置:Domain/Path,例如:设置为 .baidu.com 则所有以此为结尾的域名都可以访问cookie。
  2. 独立的认证中心,只有认证中心能接受用户的用户名密码等安全信息,其他系统不提供登录入口,只接受认证中心的间接授权。

独立的认证中心详细描述:
认证中心的间接授权通过令牌实视

  1. SSO 认证中心验证用户的用户名密码没问题,创建授权令牌。
  2. 在接下来的跳转过程中,授权令牌作为参数发送给各个子系统,子系统拿到令牌,即得到了授权
  3. 可以借此创建局部会话,局部会话登录方式与单系统的登录方式相同。

步骤举例:

  1. 用户访问系统1的受保护资源,系统1发现用户未登录,跳转至 SSO 认证中心,并将自己的地址作为参数。
  2. SSO 认证中心发现用户未登录,将用户引导至登录页面
  3. 用户输入用户名密码提交登录申请
  4. SSO 认证中心校验用户信息,创建用户与 SSO 认证中心之间的会话,称为全局会话,同时创建授权令牌
  5. SSO 认证中心带着令牌跳转回最初的请求地址
  6. 系统1收到令牌,去 SSO 认证中心校验令牌是否有效
  7. SSO 认证中心校验令牌,返回有效,注册系统1
  8. 系统1使用该令牌创建与用户的会话,称为局部会话,返回受保护资源
  9. 用户访问系统2的受保护资源
  10. 系统2发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数
  11. SSO 认证中心发现用户已登录,跳转回系统2的地址,并附上令牌
  12. 系统2拿到令牌,去sso认证中心校验令牌是否有效
  13. SSO 认证中心校验令牌,返回有效,注册系统2
  14. 系统2使用该令牌创建与用户的局部会话,返回受保护资源