一文搞懂常见鉴权方案

1,887 阅读16分钟

鉴权方式

HTTP Basic Authentication

概述

HTTP 提供一个用于权限控制和认证的通用框架。最常用的 HTTP 认证方案是 HTTP Basic authentication。服务器可以用来针对客户端的请求发送 challenge(质询信息),客户端则可以用来提供身份验证凭证。

鉴权流程

image.png

  • 客户端向服务端发起请求
  • 服务端接收到客户端的请求后,进行身份凭证验证,向客户端返回 401(Unauthorized,未被授权的)状态码,并在 WWW-Authenticate 首部提供如何进行验证的信息
  • 客户端会弹出一个密码框让用户填写
  • 客户端在新的请求中添加 Authorization 首部字段进行验证,字段值为身份验证凭证信息。

凭证生成

"Basic" HTTP 验证方案是在 RFC 7617中规定的,在该方案中,使用用户的 ID/密码作为凭证信息,并且使用 base64 算法进行编码。

安全性

由于用户 ID 与密码是是以明文的形式在网络中进行传输的(尽管采用了 base64 编码,但是 base64 算法是可逆的),所以基本验证方案并不安全。基本验证方案应与 HTTPS / TLS 协议搭配使用。

存在缺陷

安全性差,敏感信息容易被窃取

session-cookie

概述

什么是Cookie?

Cookie 是用户浏览器保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。 Cookie 主要用于以下三个方面: 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息) 个性化设置(如用户自定义设置、主题等)

什么是Session?

Session 是用于保持状态的基于Web服务器的方法。 Session允许通过将对象存储在Web服务器的内存中在整个用户会话过程中保持任何对象。 存储需要在整个用户会话过程中保持其状态的信息,例如登录信息或用户浏览Web应用程序时需要的其它信息。

鉴权流程

image.png

  • 客户端发起 HTTP 请求
  • 服务端接收到客户端的请求后,没有鉴权则返回401
  • 提示用户输入用户名和密码
  • 客户端发起登录验证
  • 服务端进行用户名和密码校验,若正确则将创建一个 sessionID 并进行存储,然后把带有sessionID 的 cookie 返回给客户端
  • 客户端接收到服务器端发来的请求之后,看见响应头中的 Set-Cookie 字段,将 Cookie 进行存储
  • 后续 HTTP 请求,都会在header里带上 cookie
  • 若用户登出,则会在客户端和服务端将session进行摧毁

凭证生成

根据需求制定过期时间、域、路径、有效期、适用站点等信息生成cookie

  • Domain 属性:指定了哪些主机可以接受 Cookie。如果不指定,默认为 origin不包含子域名。如果指定了Domain,则一般包含子域名。因此,指定 Domain 比省略它的限制要少。但是,当子域需要共享有关用户的信息时,这可能会有所帮助。

  • Path 属性:指定了主机下的哪些路径可以接受 Cookie(该 URL 路径必须存在于请求 URL 中)。以字符 %x2F ("/") 作为路径分隔符,子路径也会被匹配。

  • SameSite 属性:允许服务器要求某个 cookie 在跨站请求时不会被发送,(其中  Site (en-US) 由可注册域定义),从而可以阻止跨站请求伪造攻击(CSRF)。

    SameSite 可以有下面三种值:
    None 浏览器会在同站请求、跨站请求下继续发送 cookies,不区分大小写。
    Strict 浏览器将只在访问相同站点时发送 cookie。(在原有 Cookies 的限制条件上的加强,如上文 “Cookie 的作用域” 所述)
    Lax 与 Strict 类似,但用户从外部站点导航至 URL 时(例如通过链接)除外。 在新版本浏览器中,为默认选项,Same-site cookies 将会为一些跨站子请求保留,如图片加载或者 frames 的调用,但只有当用户从外部站点导航到 URL 时才会发送。如 link 链接

  • Secure 属性:标记为 Secure 的 Cookie 只应通过被 HTTPS 协议加密过的请求发送给服务端,因此可以预防 man-in-the-middle 攻击者的攻击

  • HttpOnly 属性:JavaScript Document.cookie API 无法访问带有 HttpOnly 属性的 cookie;此类 Cookie 仅作用于服务器。例如,持久化服务器端会话的 Cookie 不需要对 JavaScript 可用,而应具有 HttpOnly 属性。此预防措施有助于缓解跨站点脚本(XSS) (en-US)攻击。

安全性

本地保存的cookie容易被窃取,攻击者可以利用本地 Cookie 进行欺骗和 CSRF 攻击。当机器处于不安全环境时,切记不能通过 HTTP Cookie 存储、传输敏感信息。

存在缺陷

  • 扩展性差,当服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。
  • 存在在本地cookie容易被窃取

JWT

概述

JWT(Json web token), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

鉴权流程

image.png

  • 客户端发起 HTTP 请求
  • 服务端接收到客户端的请求后,没有鉴权则返回401
  • 提示用户输入用户名和密码
  • 客户端发起登录验证
  • 服务端进行用户名和密码校验,若正确则将用户的一些信息作为 payload,生成 JWT,并返回给客户端
  • 客户端接收到服务器端返回登录成功结果后,将JWT 保存在 localStorage/sessionStorage 中,登出时删除 JWT 即可。(最好不要保存在 Cookie 中,用了 Cookie 就不能设置 HTTPonly,并且存在跨域问题)
  • 后续 HTTP 请求,都会在header里带上 Authorization,例如:Authorization: Bearer

凭证生成

JWT是由Header、Payload和Signature等三部分组成,他们之间通过.连接起来,例如:header.payload.signature

image.png

  • Header 是一个json对象,定义了JWT的元数据,例如
    { 
        "alg": "指定加密算法,例如:HMAC、SHA256或RSA.", 
        "typ": "令牌类别,这里统一为JWT" 
    }
    
  • Payload 是一个json对象,定义需要传递的数据。官方预定义了以下几个字段可以供选择:

    iss (issuer):签发人
    exp (expiration time):过期时间
    sub (subject):主题
    aud (audience):受众 \ nbf (Not Before):生效时间
    iat (Issued At):签发时间
    jti (JWT ID):编号)可供选择
    同时也支持定义字段,例如

    {
      "event_id": "001",
      "event_type": "git"
    }
    
  • Signature 是对 Header 和 Payload 部分进行签名,防止数据篡改。使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名:
    HMACSHA256( 
        base64UrlEncode(header) + "." + 
        base64UrlEncode(payload), 
        secret)
    

安全性

  • JWT 默认是不加密的,若没有加密的情况则不要携带敏感信息。若需要携带敏感信息则需要进行加密
  • JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。

存在缺陷

  • 由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
  • 安全性差,一旦泄露,任何人都可以获得该令牌的所有权限

OAuth2

概述

OAuth 协议是一种三方授权协议,该协议被广泛应用于第三方授权登录中,借助第三方授权登录,用户无需再次注册即可快速使用网站或APP 提供的功能和服务。 OAuth 2.0 协议标准在OAuth 1.0 协议的基础上取消了所有Token 的加密过程,并简化了授权流程,但因强制使用HTTPS 协议,安全性更高。

鉴权流程

不带上refresh token 流程

image.png

  • 【1】客户端向用户发起鉴权授权请求
  • 【2】用户同意鉴权授予
  • 【3】客户端使用上一步获得的授权,向鉴权服务器申请 token
  • 【4】鉴权服务器验证通过之后,生成 token,并返回给客户端
  • 【5】客户端根据获取的 token 后,向资源服务器申请获取资源
  • 【6】资源服务器验证令牌通过之后,返回给客户端资源

带上refresh token 流程

image.png

  • 【1】客户端向用户发起鉴权授权请求
  • 【2】用户同意鉴权授予
  • 【3】客户端使用上一步获得的授权,向鉴权服务器申请 token
  • 【4】鉴权服务器验证通过之后,生成 token,并返回给客户端
  • 【5】客户端根据获取的 token 后,向资源服务器申请获取资源
  • 【6】资源服务器验证 token 通过之后,返回给客户端资源
  • 【7】客户端根据获取的 token 后,向资源服务器申请获取资源
  • 【8】资源服务器验证 token 无效,并将错误返回给客户端
  • 【9】客户端根据 Refresh Token 向鉴权服务进行 token 刷新
  • 【10】鉴权服务返回新的有效 token

用户授权模式

从以上鉴权流程可以看到,客户端必须在用户授权之后才可以获取token。OAuth2 定义了四种授权的方式,分别为:授权码模式(authorization code)、简化模式(implicit)、密码模式(resource owner password credentials)和客户端模式(client credentials)

  • 授权码模式(authorization code)

    授权码模式是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与"服务提供商"的认证服务器进行互动。

    image.png

    • 【1】客户端向用户代理(浏览器)发起鉴权请求
    • 【2】用户代理(浏览器)将客户端 ID 和 重定向 URI 发送给鉴权服务
    • 【3】用户代理(浏览器)发起用户的授权确认
    • 【4】用户代理(浏览器)将用户的授权确认发送给鉴权服务
    • 【5】鉴权服务生成鉴权码,并将鉴权码发送给用户代理(浏览器)
    • 【6】用户代理(浏览器)将鉴权码发送给客户端
    • 【7】客户端将鉴权码和重定向 URI 发送给鉴权服务
    • 【8】鉴权服务生成 token 返回给客户端
  • 简化模式(implicit)

    不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请 token,跳过了"授权码"这个步骤,因此得名。所有步骤在浏览器中完成,token 对访问者是可见的,且客户端不需要认证。 image.png

    • 【1】客户端向用户代理(浏览器)发起鉴权请求
    • 【2】用户代理(浏览器)将客户端 ID 和 重定向 URI 发送给鉴权服务
    • 【3】用户代理(浏览器)发起用户的授权确认
    • 【4】用户代理(浏览器)将用户的授权确认发送给鉴权服务
    • 【5】鉴权服务将用户导向客户端指定的”重定向URI“,并在 URI 的 Hash 部分包含了访问 token
    • 【6】用户代理(浏览器)向资源服务器发起请求,不包含上一个步骤中获取的 hash 值中的 token
    • 【7】资源服务器返回一个网页,其中包含的代码可以获取Hash值中的 token
    • 【8】用户代理(浏览器)执行上一步获取的脚本,并获取 token
    • 【9】将 token 发送给客户端
  • 密码模式(resource owner password credentials)

    用户向客户端提供自己的用户名和密码。客户端使用这些信息,向"服务商提供商"索要授权。在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分,或者由一个著名公司出品。而认证服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式。 image.png

    • 【1】用户向客户端提供用户名和密码
    • 【2】客户端将用户名和密码发送给鉴权服务,请求 token
    • 【3】鉴权服务验证通过后,向客户端返回 token
  • 客户端模式(Client Credentials Grant)

    客户端以自己的名义,而不是以用户的名义,向"服务提供商"进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求"服务提供商"提供服务,其实不存在授权问题。

    image.png

    • 【1】客户端向鉴权服务进行身份验证,并请求获取 token
    • 【2】鉴权服务验证通过之后,返回给客户端 token

备注:四种模式间相互比较

模式优点缺点应用场景
授权码模式Access token通过服务器之间进行交换,比较安全请求次数比较多开放系统间授权:社交联合登录、开放API平台
简化模式请求次数比较少,简单1.没有获取code的过程, Access token直接从授权服务器返回给client客户端,令牌容易因为被拦截窃听而泄露2.无法存储refresh token,不支持刷新令牌: 要么access token有效性给很长 要么access token过期后,让用户重新认证适用于公开的浏览器单页应用
密码模式请求次数比较少,简单1.Client会获得用户的登录信息,除非是非常信任的应用,否则可能导致登录信息泄露。2.没有多因子认证这样的机制1. 可以用来做遗留项目升级为oauth2的适配方案 2. Client是自家应用的场景
客户端模式请求次数比较少,简单不支持refresh token适用于服务器间通信场景,直接根据client的id和密钥即可获取token,无需用户参与

凭证生成

对于构成token的元素,各个业务都有自己的需求,不过仍然存在一些基本通用的元素,例如:

  • clientId:客户端ID,当前token隶属的客户端
  • userId:用户的ID,表示当前token来自哪个用户授权
  • scope: 权限范围,该token允许换取的用户受保护资源范围
  • issueTime:下发时间,用于控制token的生命周期
  • tokenType:token的类型,不同类型可能会采用不同的验证措施

安全性

1、令牌是短期的,到期会自动失效,用户自己无法修改。
2、令牌可以被数据所有者撤销,会立即失效。
3、令牌权限有限制,密码一般是完整权限。
保证了令牌既可以让第三方应用获得权限,同时又随时可控,不会危及系统安全

存在缺陷

  • 协议框架太宽泛,造成各种实现的兼容性和相互操作性差
  • 和OAuth1.0不兼容
  • OAuth2.0不是一个认证协议(是授权协议),OAuth2.0本身并不能告诉你任何用户信息.

鉴权方案对比

鉴权方式优点缺点应用场景
HTTP Basic Authentication简单安全性差HTTP Basic Auth 的认证方式适合应用于内部系统之间,只需要通过账密就能访问的情况
session-cookie可以保存客户相关信息和状态,这对于无状态的http请求来说是很重要的1、扩展性差,当服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。 2、存在在本地cookie容易被窃取。单点登陆
JWT1、可扩展性好 应用程序分布式部署的情况下,不需要解决跨域的问题。 2、jwt的载荷中可以存储一些常用信息,用于交换信息,有效地使用 JWT,可以降低服务器查询数据库的次数。1、安全性差,JWT 默认是不加密的,若没有加密的情况则不要携带敏感信息。若需要携带敏感信息则需要进行加密JWT 主要用于需要跨域进行认证的场景,例如服务A在认证了用户身份后,颁发一个很短过期时间的JWT给客户端,客户端在向服务B的请求中带上该JWT,则服务B可以通过验证该JWT来判断用户是否有权执行服务B上的相关操作
OAuth21、更安全,客户端不接触用户密码,服务器端更易于集中保护。 2、资源服务器和授权服务器解耦。 3、集中式授权,简化客户端 4、考虑多种客户端架构场景 5、提供不同的信任级别的授权方案。1、协议框架太宽泛,造成各种实现的兼容性和相互操作性差。 2、OAuth2.0不是一个认证协议(是授权协议),OAuth2.0本身并不能告诉你任何用户信息。1、开放系统间授权:社交联合登录、开放API平台 2、现代微服务安全:单页浏览器App(HTML5/JS/无状态)、无线原生App 3、企业内部应用认证授权(IAM/SSO),前后端分离单页面应用(spa)

参考文献

【1】developer.mozilla.org/zh-CN/docs/…
【2】developer.mozilla.org/zh-CN/docs/…
【3】jwt.io/introductio…
【4】datatracker.ietf.org/doc/html/rf…
【5】help.aliyun.com/document_de…