目录

  1. 概述
  2. 常见的认证机制
  3. 传统 Cookie Auth
    1. 认证详述
    2. Cookie-Session 改进
  4. HTTP Basic Auth
  5. OAuth
  6. Token Auth
  7. JWT
    1. JWT 数据结构
      1. 头部
      2. 负载
      3. 签名
    2. 使用方式
    3. FAQ
      1. 为什么 JWT 是安全的?
      2. JWT 如何抵御中间人等攻击?
      3. 弊端
  8. 附录

概述

❓什么是认证

认证就是证明真身的过程

有下面这样一个场景:在平时使用某宝或者某东时,如果尼想要下单,首先需要注册一个账号,拥有了账号之后,需要输入用户名和密码完成登录过程,这里用户输入用户名和密码请求登录系统的过程就是用户认证的过程

常见的认证机制

有五种常见的认证机制:HTTP Basic Auth、Cookie Auth、OAuth、Token Auth 和 JWT,其中目前最常用的就是 JWT,本文也将详细阐述 JWT 的相关概念

早期互联网以 web 为主,客户端是浏览器 ,所以 Cookie-Session 方式是早期最常用的认证方式,直到现在,一些 web 网站依然用这种方式做认证

认证详述

Cookie 认证机制是:每当服务器收到一个请求认证时,就创建一个 Session 对象,同时在客户端的浏览器端创建一个 Cookie 对象;通过客户端带上来 Cookie 对象来与服务器端的 Session 对象匹配来实现状态管理的。默认当关闭浏览器的时候,cookie 会被删除,可以通过修改 cookie 的 expire time 使 cookie 永不失效

  1. 用户输入用户名、密码或者用短信验证码方式登录系统
  2. 服务端验证后,创建一个 Session 信息,并且将 SessionID 存到 cookie,发送回浏览器
  3. 下次客户端再发起请求,自动带上 cookie 信息,服务端通过 cookie 获取 Session 信息进行校验

对于处于移动互联网,这样的方式不太适合,主要存在以下几个问题:

  1. Cookie-Session 只能在 web 场景下使用,APP 不能存储 cookie,所以造成,现在的产品基本上都同时提供 web 端和 APP 两种使用方式,有点产品甚至只有 APP
  2. 就是单独的 web 客户端使用 cookie 认证,也存在问题:请求跨域十分常见,但是 Cookie 是不能跨域的
  3. 如今的系统大部分都是分布式系统,一旦使用 Cookie 就需要考虑 Session 的同步问题,相同的请求有可能被负载均衡器将请求分发到不同的主机,如果 Session 没有同步,用户会登录失败
  4. 使用 Cookie 就会存在 CSRF(跨域请求伪造) 的风险

❓cookie 删除后,服务端的 session 如何处理?

根据 Session 机制,如果长时间没有访问,服务器端的 Session 会自动删除(Session 钝化)

对于上述存在诸多问题的认证进行改进,得到了另一种基于 Cookie-Session 认证方式。

  • 由于移动应用不能存储 cookie 所以就不存储 cookie 作为用户认证过的标准,web 中可以使用 local storage,APP 中使用客户端数据库,这样既能这样就实现了跨域,并且避免了 CSRF。
  • 服务端也不存 Session 了,把 Session 信息拿出来存到 Redis 等内存数据库中,这样即提高了速度,又避免了 Session 同步问题

改造之后变成了如下的认证过程:

  1. 用户输入用户名、密码或者用短信验证码方式登录系统;
  2. 服务端经过验证,将认证信息构造好的数据结构存储到 Redis 中,并将 key 值返回给客户端;
  3. 客户端拿到返回的 key,存储到 local storage 或本地数据库;
  4. 下次客户端再次请求,把 key 值附加到 header 或者 请求体中;
  5. 服务端根据获取的 key,到 Redis 中获取认证信息
    📖这种改进之后的 Cookie-Session 认证方式是一种思想,JWT 就是这种思想的一种具体实现,在下文会详细讲解 JWT

HTTP Basic Auth

HTTP Basic Auth 就是每次请求 API 时都提供用户的 username 和 password。Basic Auth 是配合 RESTful API 使用的最简单的认证方式,只需提供用户名密码即可,但由于存在用户名密码暴露给第三方客户端的风险(仅使用 Base64 编码),在生产环境下被使用的越来越少。因此,在开发对外开放的 RESTful API 时,尽量避免采用 HTTP Basic Auth

OAuth

OAuth(开放授权)是一个开放的授权标准,允许用户让第三方应用访问该用户在某一 web 服务上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。

OAuth 允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的第三方系统(例如,视频编辑网站)在特定的时段(例如,接下来的 2 小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth 让用户可以授权第三方网站访问他们存储在另外服务提供者的某些特定信息,而非所有内容这种基于 OAuth 的认证机制适用于个人消费者类的互联网产品,如社交类 APP 等应用,但是不太适合拥有自有认证权限管理的企业应用

Token Auth

基于 token 的鉴权机制类似于 http 协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于 token 认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利

⛵大概的流程是这样的:

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

👍Token Auth 的优点

  • 支持跨域访问:Cookie 是不允许垮域访问的,这一点对 Token 机制不存在。前提是传输的用户认证信息通过 HTTP 头传输
  • 无状态(也称:服务端可扩展行):Token 机制在服务端不需要存储 session 信息,因为 Token 自身包含了所有登录用户的信息,只需要在客户端的 cookie 或本地介质存储状态信息
  • 更适用 CDN: 可以通过内容分发网络请求服务端的所有资料(如:javascript,HTML,图片等),而服务端只要提供 API 即可
  • 去耦: 不需要绑定到一个特定的身份验证方案,Token 可以在任何地方生成,只要在 API 被调用的时候,可以进行 Token 生成调用即可
  • 更适用于移动应用: 当客户端是一个原生平台(iOS, Android,Windows 8 等)时,Cookie 是不被支持的(需要通过 Cookie 容器进行处理),这时采用 Token 认证机制就会简单得多
  • CSRF:因为不再依赖于 Cookie,所以就不需要考虑对 CSRF(跨站请求伪造)的防范
  • 性能: 一次网络往返时间(通过数据库查询 session 信息)总比做一次 HMACSHA256 计算的 Token 验证和解析要费时得多
  • 不需要为登录页面做特殊处理: 如果使用 Protractor 做功能测试的时候,不再需要为登录页面做特殊处理
  • 基于标准化:你的 API 可以采用标准化的 JSON Web Token (JWT). 这个标准已经存在多个后端库(.NET, Ruby,Java,Python, PHP)和多家公司的支持(如:Firebase,Google, Microsoft)

token 必须要在每次请求时传递给服务端,它应该保存在请求头里, 另外,服务端要支持CORS(跨来源资源共享)策略,一般我们在服务端这么做就可以了Access-Control-Allow-Origin: *

JWT

JWT 全称是 JSON Web Token,是目前非常流行的跨域认证解决方案,在单点登录场景中经常使用到,然而使用者对 JWT 的评价褒贬不一。JWT 就是一种 Cookie-Session 改造版的具体实现,JWT 还有个好处,可以不用在服务端存储认证信息,完全由客户端提供,服务端只要根据 JWT 自身提供的解密算法就可以验证用户合法性,而且这个过程是安全的

✨JWT 有如下特点

  • JWT 脱离了 session 的束缚,服务端不需要再维持 session 状态,但也无法控制 token 的状态,JWT 一旦签发,在到期之前始终有效,除非服务器有额外的验证逻辑
  • JWT 可以很方便的支持跨域
  • JWT 本身包含了验证信息,如果发生泄漏,任何人都可以使用令牌的权限进行请求
  • JWT 本身不加密,需要通过更安全的 HTTPS 协议传输

JWT 数据结构

JWT 最后的形式就是个字符串,它由头部负载签名这三部分组成,中间以.分隔。像下面这样:

头部

头部以 JSON 格式表示,用于指明令牌类型和加密算法

例如下面的这个例子,表示使用 JWT 格式,加密算法采用 HS256,对应上图的红色 header 部分,需要 Base64 编码

1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}

负载

用来存储服务器需要的数据,比如用户信息,例如姓名、性别、年龄等,要注意的是重要的机密信息最好不要放到这里,比如密码等

1
2
3
4
{
"name": "pineapple-man",
"age": "23"
}

另外,JWT 还规定了 7 个字段供开发者选用,这部分信息也是需要用 Base64 进行编码的

规定字段含义
iss (issuer)签发人
exp (expiration time)过期时间
sub (subject)主题
aud (audience)受众
nbf (Not Before)生效时间
iat (Issued At)签发时间
jti (JWT ID)编号

签名

签名使用HMACSHA256算法计算得出,这个方法有两个参数,前一个参数是 (base64 编码的头部 + base64 编码的负载)用点号相连,后一个参数是自定义的字符串密钥(密钥不要暴露给客户端,应该只有服务器知道)

1
2
3
4
5
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
Secret
)

使用方式

⛵JWT 使用步骤如下

  1. 在用户登录网站的时候,需要输入用户名、密码或者短信验证的方式登录,登录请求到达服务端的时候,服务端对账号、密码进行验证,然后计算出 JWT 字符串,返回给客户端

  2. 客户端拿到这个 JWT 字符串后,存储到 cookie 或者 浏览器的 LocalStorage 中

  3. 再次发送请求,比如请求用户设置页面的时候,在 HTTP 请求头中加入 JWT 字符串,或者直接放到请求主体中

  4. 服务端拿到这串 JWT 字符串后,解密 base64 的头部和 base64 的负载部分,随后根据头部提到的哈希算法,计算出头部和负载部分的摘要。比较计算结果和传来的签名部分是否一致,如果一致,说明此次请求没有问题,如果不一致,说明请求过期或者是非法请求

使用 JWT 方式进行初次登录的流程如下:

用户登录过一次之后,再次登录的过程如下:

FAQ

❓JWT 为什么可以完全依靠客户端就能实现认证功能,认证信息全部存在客户端,怎么保证安全性?

为什么 JWT 是安全的?

保证安全性的关键就是 HMACSHA256 或者与它同类型的哈希算法,因为哈希过程是不可逆的,所以不能根据传到前端的 JWT 字符串反解到密钥信息;另一方面,不同的头部和负载加密之后得到的签名是不同的,所以,如果有人改了负载部分的信息,那最后加密出的结果肯定就和改之前的不一样的,所以,最后验证的结果就是不合法的请求

JWT 如何抵御中间人等攻击?

假设载荷部分存储了权限级别相关的字段,攻击者拿到了 JWT 后修改为更高权限的级别,这种情况肯定是不会得逞的,因为之前说的哈希特性能够保证

但是如果攻击拿到了 JWT 后,不进行修改直接使用,仍然会出现安全问题,所以为了避免 JWT 信息泄露,应当使用 HTTPS 协议而不是 HTTP 协议,这样可以有效的防止中间劫持攻击

弊端

JWT 有个问题,导致很多开发团队放弃使用它,那就是一旦颁发一个 JWT 令牌,服务端就没办法废弃掉它,除非等到它自身过期。有很多应用默认只允许最新登录的一个客户端正常使用,不允许多端登录,JWT 就没办法做到,因为颁发了新令牌,但是老的令牌在过期前仍然可用。这种情况下,就需要服务端增加相应的逻辑

附录

『JWT』,你必须了解的认证登录方案
JWT 认证方案学习
什么是 JWT – JSON WEB TOKEN
RFC7519