浏览器系列 -- cookie、session、Token、JWT及对应的登录态机制

1,924 阅读12分钟

前言

首先清楚三个概念:

认证

  • 通俗地讲就是验证当前用户的身份,证明“你是你自己”
  • 互联网中的认证:
    • 用户名密码登录
    • 邮箱发送登录链接
    • 手机号接收验证码
    • 只要你能收到邮箱/验证码,就默认你是账号的主人

授权

  • 用户授予第三方应用访问该用户某些资源的权限
    • 你在安装手机应用的时候,APP 会询问是否允许授予权限(访问相册、地理位置等权限)
    • 你在访问微信小程序时,当登录时,小程序会询问是否允许授予权限(获取昵称、头像、地区、性别等个人信息)
  • 实现授权的方式有:cookie、session、token、OAuth

凭证

  • 实现认证和授权的前提是需要一种媒介(证书) 来标记访问者的身份
    • 相当于每个人的身份证
比如掘金会有两种模式:`游客模式``登录模式`。
游客模式下,可以正常浏览网站上面的文章,一旦想要点赞/收藏/分享文章,就`需要登录或者注册账号`。
当用户登录成功后,服务器会给该用户使用的浏览器颁发一个令牌(`token`),此令牌用来`表明你的身份``每次`浏览器发送请求时`会带上`这个令牌,就可以使用游客模式下无法使用的功能

用户登录方案

  • Cookie + Session 作登录态
  • Token 作登录态
  • OAuth 第三方登录

Cookie + Session 作登录态

前言引入

我们以 <商城及购物车> 为例子作引入:

对于商城首页,所有用户浏览的商品都是一样的情况下,用户不用登录也可以访问。当时如果用户选择商品加入购物车添加到收藏夹的时候,我们就需要有可以标识用户身份的凭证,这就可以采用 cookie + session 的机制来作登录态

什么是 session

用户在某个网站首次登录,此时浏览器发起请求给服务端,服务端会为这次请求临时开辟一块内存空间,存的便是session对象,session对象存的内容就是记录用户的一些状态行为记录

那么问题来了,服务端如何知道收到的请求是哪个用户操作浏览器发起的呢?于是cookie就出场了

什么是 cookie

简单来说,cookie是服务器通过在响应头中的Set-Cookie字段发送到用户的浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。它用来保持用户登录状态,和session配合使用可以用来交流清楚是哪位用户

关于cookie的详细内容见 浏览器系列 -- 本地存储

cookie 和 session 的区别

  • 存放位置:cookie在浏览器,session在服务端(一般是在内存中)
购物车的数据一般放在`数据库`中,session的目的不是存购物车的数据,而是作为让服务端知道是哪位用户的`工具`罢了。
为什么session不存购物车的数据,因为cookie到期用户被退出登录,重新登录后session被替代掉,意味着购物车的数据将被清空
  • 安全系数:cookie安全系数低,session安全系数高
  • 有效时间:cookie看设置的有效期,如果没有设置则是窗口关闭就消失,session在内存也是窗口关闭就消失,在数据库里理论上可以很久
  • 性能方面:cookie不会造成服务端压力大,用户数过大时,session会造成服务器压力大
  • 存储内容:cookie存的数据一般是用户昵称账号等一些非隐私数据,而session一般存密码、一些ID之类的隐私数据
  • 存储的数据类型:cookie是字符串类型,而session可以是任何类型,主要是键值对的哈希表类型Object类型

cookie和session的联系

两者都是为了跟踪浏览器用户身份的会话方式,两者相互配合可以作登录态

接下来看一下 cookie 和 session 是如何配合作登录态的:

实现流程

  • 用户首次登录 image.png
  1. 用户首次登录后,服务端会开辟一段内存空间作为创建该用户对应的session,以及生成对应的session idsession就好比是一把session id就是开启这把锁的钥匙
  2. 然后服务端把这把钥匙通过发送响应头的方式(将session id放在响应头 header 的Set-Cookie字段里面)
  3. 浏览器收到响应后把 session id 存进cookie里面,这时cookie的有效期就等于这把钥匙的有效期,也就是说cookie一旦过期,登录态就作废,表示用户退出登录(属于被动退出)
  • 登录之后的访问

image.png

  1. 用户当前处于登录态,然后访问该网站并发起请求(比如将商品加入购物车)
  2. 此时浏览器在这个请求中根据请求域名自动携带归属于该域名下这个用户的cookie,(其中含有session id
  3. 服务端这边要是得知这把锁被打开了(内存中的session和收到的session id对上了),就跑去数据库拿该用户存在数据库的数据(购物车)并返回。
  4. 但要是打不开,比如cookie过期,服务器会返回状态码401,浏览器收到之后会要求用户重新输入账号密码登录

Token 作登录态

为什么选择使用token代替session+cookie作登录态?

  • 服务器 压力大:由于通常session是存储在内存空间的(因为访问的时候比较快),用户数增多时,内存空间被挤爆
  • cookie 容易遭受 CSRF 攻击(GET、POST请求方式都一样可以伪造,而且 仅仅只是靠 浏览器看域名自动携带的 cookie 来完成身份验证那就太不安全了,得靠一个黑客 无法伪造的 : token 放入 POST 表单中才安全一些)
  • cookie 过期代表用户被退出登录,体验感极差
  • 如果浏览器/网页禁用 cookie,就用 token 来作登录态

分类

  • acesss token:访问资源接口时所需要的资源凭证
access token 基本组成部分:
1. uid (用户唯一的身份标识)
2. time (当前时间的时间戳)
3. sign(签名,token 的前几位以哈希算法压缩成的一定长度的十六进制字符串)
  • refresh token:专用于刷新 access token 的 token
如果没有 refresh token,也可以刷新 access token,但每次刷新都要用户`重新输入`登录用户名与密码,会很麻烦

如何使用 access token 作登录态

image.png

  1. 用户首次登录,服务器对用户名和密码进行检验,验证成功后生成加密access token放在响应头传送给浏览器(最好是以 HTTPS 传输 access token),浏览器收到将token后放在cookielocalstorage里面【注意此时服务器不保存access token 但保存 refresh token】
  2. 用户当前处于登录态,然后访问该网站并发起请求(比如将商品加入购物车),此时携带token放在请求头里面发送给服务器
  3. 服务器根据JWT算法解密和检验 token ,验证成功,保持登录态此时为了延长用户的登录时间,可以刷新一下 access token 的超时时间),要是验证失败,服务器会使用 refresh token 刷新 token 获取最新的 access token 然后发送给浏览器

这是用解析 token 的时间 session 的存储空间,减轻服务器的内存空间压力

关于 JWT 算法详见 JSON Web Token 入门教程

如何使用 refresh token 刷新 access token

要是 access token 验证失败,则浏览器这边需要对 access token 进行更新,方法是使用 refresh token

image.png

  • access token 的有效期比较短(比如是一周), refresh token 的有效期比较长(比如一个月),当 acesss token 过期失效但 refresh token 未过期,则使用 refresh token 就可以获取到新的 token
  • 如果 refresh token 也失效了,服务器会在响应头返回状态码401,浏览器收到该状态码后要求用户重新登录
  • refresh token 及过期时间是存储在服务器的数据库中,只有在需要申请新的 acesss token 时才会验证,不会一直请求,也不需要向 session 一样一直保持在内存中以应对大量的请求(这就是使用token作登录态能够减轻服务器内存压力的体现)

记住密码功能怎么做?

由于账号要求的安全系数不高,所以账号可以以明文字符串的形式存在cookie或localstorage中,如果用户有勾选记住账号,则下次登录时前端JS代码会通过document.cookiewindow.localstorage.getItem获取到账号后添加到<input>中去,完成记住账号的功能。

由于密码要求的安全系数极高,所以我们一般不会把密码存在本地(无论是明文还是密文),或者确切地说,我们不会做记住密码的功能,我们采用token机制代替。token可以存在本地,然后下次登录时客户端只需要发送token给服务端去判断。

  • 如果用户勾选“记住密码”或说“记住用户”,那么从本质上讲就是让refresh token的有效期设置为很长
  • 如果用户不勾选“记住密码”,本质上就是refresh token有效期同access token,假设access token的有效期为2个小时,那么2个小时后如果用户重新上线此时因为没有refresh token来刷新access token,所以用户登录态作废,需要重新输入密码登录。

找回密码功能怎么做?

用户如果忘记密码,想找回密码,此时我们最重要的就是要确认用户身份,如果用户之前有绑定手机号可以用手机号发送手机验证码验证,如果用户之前有绑定邮箱,我们可以通过邮箱发送带链接的邮件给用户,用户收到邮件后打开链接完成身份验证。完成身份验证后服务端再将密码发送给客户端。(验证码都是短期内有效的)

注意,服务端不能明文或密文保存和发送密码,最佳的解决方案是利用维护一个哈希表来存密码,然后服务端再把哈希值交给客户端保存备用。

重置密码功能怎么做?

还是和找回密码一样得先进行与用户的强制交互验证身份,然后给个重置密码的链接(由于链接是明文传输,所以必须短期内有效)

OAuth 第三方登录

image.png

OAuth 机制实现流程

这里以网站应用的微信登录接入流程为例:(官方文档image.png

  • 一、网站运营者申请账户
  1. 首先,a.com 的运营者需要在微信开放平台注册账号,并向微信申请使用微信登录功能。
  2. 申请成功后,得到申请的 appidappsecret
  • 二、用户登录并授权
  1. 用户在 a.com 上选择使用微信登录。
  2. 这时会跳转微信的 OAuth 授权登录,并带上 a.com 的回调地址。
(弹出一个页面:https:/ /open.weixin.qq.com/connect/qrconnect?appid=`APPID`&redirect_uri=`www.a.com`
&response_type=`code`&scope=`snsapi_login`&state=STATE#wechat_redirect,该页面显示一个二维码)
  1. 用户打开微信APP扫码后,需要选择具体的授权范围,如:授权用户的头像、昵称等。
  • 三、微信生成临时票据
  1. 授权之后,微信会重定向redirect_uri网址上,并且带上codestate参数
  • 四、网站向微信索取 access token
  1. 获取 code 之后, a.com 会携带 code 、appid、appsecret,调用微信官方提供的接口向微信服务器申请 token,验证成功后,微信会下发 access token和对应的 refresh token(用来刷新 access token)。
access_token有效期目前为`2个小时`
refresh_token目前有效期为`30天`
  1. 获取 access token 后, a.com 就可以携带 access token 调用微信接口,获取该用户的微信用户头像,用户昵称等信息了。
  2. a.com 提示用户登录成功,并将登录状态(包括用户头像昵称和 access token 和 refresh token)写入 cookielocalstorage,以作为后续访问的凭证。
  • 五、下次登录
  1. 用户退出登录或登录态失效后用户选择重新登录(此时不用重复授权),浏览器会向服务器发起请求并携带上次保存的access token,服务器收到token后验证token是否有效,如果 access token 未过期,则 a.com 就可以直接拿它去调用微信接口获取用户头像、昵称;
  2. 如果 access token 过期,则服务器拿之前保存的 refresh token 和 appid 调用微信接口获取最新的 access token并替换旧的access token。
  3. 如果 refresh token 也过期,服务器会收到错误码{"errcode":40030,"errmsg":"invalid refresh_token"},此时需要回到第二步需要用户重新授权登录了。

其他平台的接入方式可以去对应得官方文档查看,流程基本类似。

参考文章