认证、授权、凭证

认证(Authentication)、授权(Authorization)、凭证(credentials)

认证是指以当前用户的身份登录过后,系统便能追踪到他的身份,并做出符合相应业务逻辑的操作。即使用户没有登录,大多数系统也会追踪他的身份,只是当做来宾或匿名用户来处理,认证技术解决的是**我是谁?**的问题。

授权是指什么样的身份被允许访问某些资源,在获取用户身份后继续检查用的权限。单一的系统授权往往是伴随认证来完成的,但是开放API的多系统结构之下,授权可以有不同的系统来完成。例如OAuth。授权是解决我能做什么?的问题。

凭证,实现认证和授权的基础是需要一种媒介(credentials)来标记访问者的身份或权利,在现实生活中每个人都需要一张身份证才能访问自己的银行账户、结婚和办理养老保险等,这就是认证的凭证。在互联网世界中,服务器为每一个访问者颁发 session ID存放到cookie,这就是一种凭证技术。数字凭证还表现在方方面面,SSH登录的秘钥、JWT令牌、一次性密码等。用户账户也不一定存放在数据库中的一张表,在一些企业的IT系统中,对账户管理的权限有了更多的要求。所以账户技术(accounting)可以帮助我们使用不同的方式管理用户账户,同时具有不同系统之间共享账户的能力。

访问控制策略(AC),如果我们需要把资源的权限划分到一个很细的粒度,就不得不考虑用户以何种身份来访问受限的资源,选择基于访问控制列表(ACL),还是基于用户角色的访问控制(RBAC),或者其他的访问控制策略

HTTP Basic Authentication

这种方式实现起来非常简单,在大量场景下被采用。当然缺点也很明显,Base64 只能称为编码,而不是加密 (实际上无需配置密匙的客户端并没有任何可靠地加密方式,我们都依赖 TSL协议)。这种方式的致命弱点是编码后的密码如果明文传输则容易在网络传输中泄露,在密码不会过期的情况下,密码一旦泄露,只能通过修改密码的方式。

HMAC(AK/SK)认证

在我对接一些PaaS平台和支付平台时会要求我们提前生成一个Access Key(AK) and Secure Key(SK), 然后通过签名的方式完成认证请求,这种方式可以避免传输secure key, 且大多数情况下签名只允许一次,避免了重放攻击。这种AK/SK的认证方式主要是利用了散列的消息认证码 (Hash-based MessageAuthentication Code) 来实现的。为了让每一次请求的签名变得独一无二,从而实现重放攻击,我们需要在签名时放入一些干扰信息。在业界标准中有两种典型的做法,质疑/应答算法(OCRA: OATH Challenge-Response Algorithm)、基于时间的一次性密码算法(TOTP:Time-based One-time Password Algorithm)。

质疑/应答算法需要客户端先请求一次服务器,获得一个 401 未认证的返回,并得到一个随机字符串(nonce)。将 nonce 附加到按照上面说到的方法进行 HMAC 签名,服务器使用预先分配的 nonce 同样进行签名校验,这个 nonce 在服务器只会被使用一次,因此可以提供唯一的摘要。

基于时间的一次性密码认证,为了避免额外的请求来获取 nonce,还有一种算法是使用时间戳,并且通过同步时间的方式协商到一致,在一定的时间窗口内有效(1分钟左右)。

这里的只是利用时间戳作为验证的时间窗口,并不能严格的算作基于时间的一次性密码算法。标准的基于时间的一次性密码算法在两步验证中被大量使用,例如 Google 身份验证器不需要网络通信也能实现验证(但依赖准确的授时服务)。原理是客户端服务器共享密钥然后根据时间窗口能通过 HMAC 算法计算出一个相同的验证码。

OAuth2 和 Open ID

OAuth(开放授权)是一个开放标准,允许用户授权第三方网站访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方网站或分享他们数据的所有内容。OAuth 是一个授权标准,而不是认证标准。提供资源的服务器不需要知道确切的用户身份(session),只需要验证授权服务器授予的权限(token)即可。

上图只是 OAuth 的一个简化流程,OAuth 的基本思路就是通过授权服务器获取 access tokenrefresh tokenrefresh token 用于重新刷新access token),然后通过 access token 从资源服务器获取数据 。如果需要获取用户的认证信息,OAuth 本身没有定义这部分内容,如果需要识别用户信息,则需要借助另外的认证层,例如 OpenID ConnectOAuth 负责解决分布式系统之间的授权问题,即使有时候客户端和资源服务器或者认证服务器存在同一台机器上。OAuth没有解决认证的问题,但提供了良好的设计利于和现有的认证系统对接。Open ID 解决的问题是分布式系统之间身份认证问题,使用Open ID token 能在多个系统之间验证用户,以及返回用户信息,可以独立使用,与 OAuth 没有关联。OpenID Connect 解决的是在 OAuth 这套体系下的用户认证问题,实现的基本原理是将用户的认证信息(ID token)当做资源处理。在 OAuth 框架下完成授权后,再通过 access token获取用户的身份。这三个概念之间的关系有点难以理解,用现实场景来说,如果系统中需要一套独立的认证系统,并不需要多系统之间的授权可以直接采用 Open ID。如果使用了 OAuth 作为授权标准,可以再通过 OpenID Connect 来完成用户的认证。

JWT

OAuth 等分布式的认证、授权体系下,对凭证技术有了更多的要求,比如包含用户 ID、过期等信息,不需要与外部存储关联。因此业界对 token 做了进一步优化,设计了一种自包含令牌,令牌签发后无需从服务器存储中检查是否合法,通过解析令牌就能获取令牌的过期、有效等信息,这就是JWT (JSON Web Token)JWT 是一种包含令牌(self-contained token),或者叫值令牌 (value token),我们以前使用关联到 session 上的 hash 值被叫做引用令牌(reference token)。

简而言之,一个基本的JWT令牌为一段点分3段式结构。生成JWT 令牌的流程为

  1. header json 的 base64 编码为令牌第一部分
  2. payload json 的 base64 编码为令牌第二部分
  3. 拼装第一、第二部分编码后的 json 以及 secret 进行签名的令牌的第三部分
    因此只需要签名的 secret key就能校验 JWT 令牌,如果在消息体中加入用户 ID、过期信息就可以实现验证令牌是否有效、过期了,无需从数据库/缓存中读取信息。因为使用了加密算法,所以第一、二部分即使被修改(包括过期信息)也无法通过验证。JWT 优点是不仅可以作为 token 使用,同时也可以承载一些必要信息,省去多次查询。JWT token在微服务的系统中优势特别突出。多层调用的 API 中可以直接传递 JWT token,利用自包含的能力,可以减少用户信息查询次数;更重要的是,使用非对称的加密方式可以通过在系统中分发密匙的方式验证 JWT token
选择合适的认证方式

客户端到服务器之间认证和服务器到服务器之间认证是不同的。我们把终端用户(Human)参与的通信,叫做 Human-to-machine (H2M),服务器与服务器之间的通信叫做 Machine-to-machine (M2M)H2M的通信需要更高的安全性,M2M 的通信天然比 H2M 安全,因此更多的强调性能,在不同的场合下选择合适的认证技术就显得特别重要。例如 HTTP Basic Authentication 用来作为 H2M 认证显得有些落后,但是在 M2M 中被大量使用。另外值得一提的是,H2M 这种通信方式下,客户端不受控制,由于无法自主分发密匙,认证通信的安全高度依赖 HTTPS。从一个宏观的角度看待他们的关系,对我们技术选型非常有帮助。