全文转载:《细说API – 认证、授权和凭证》 - THOUGHTWORKS
互联网基于HTTP协议构建,而HTTP协议因为简单而流行开来,但是HTTP协议是无状态的,为此开发者为了追踪用户想出了各种办法,包括Cookie/Session机制、Token、Flash跨浏览器Cookie,甚至浏览器指纹等。
更有甚者,把userID存储到LocalStorage中当做Token使用;移动端项目,伺服器给出的API中需要客户端模拟Cookie,从而像浏览器中Ajax方式调用API。
首先,认证和授权是两个不同的概念,为了让API更加安全和具有清晰的设计,理解认证和授权的不同就非常有必要了,它们在英文中也是不同的单词。
认证指的是当前用户的身份,当用户登陆过后系统便能追踪到他的身份做出符合相应业务逻辑的操作。即使用户没有登录,大多数系统也会追踪他的身份,只是当做来宾或者匿名用户来处理。认证技术解决的是「我是谁」的问题。
授权指的是什么样的身份被允许访问某些资源,在获取到用户身份后继续检查用户的许可权。单一的系统授权往往是伴随认证来完成的,但是在开放API的多系统结构下,授权可以由不同的系统来完成,例如OAuth。授权技术是解决「我能做什么」的问题。
实现认证和授权的基础是需要一种媒介(credentials)来标记访问者的身份或权利,在现实生活中每个人都需要一张身份证才能访问自己的银行账户、结婚和办理养老保险等,这就是认证的凭证;在古代军事活动中,皇帝会给出战的将军颁发兵符,下级将领不关心持有兵符的人,只需要执行兵符对应的命令即可。在互联网世界中,伺服器为每一个访问者颁发Session ID存放到Cookie,这就是一种凭证技术。数字凭证还表现在方方面面,SSH登录的密匙、JWT令牌、一次性密码等。
如果需要把资源的许可权划分到一个很细的粒度,就不得不考虑用户以何种身份来访问受限的资源,选择基于访问控制列表(ACL)还是基于用户角色的访问控制(RBAC)或者其他访问控制策略。
在流行的技术和框架中,这些概念都无法孤立的被实现,API开发中常常使用的认证和授权技术:HTTP Basic AUthentication、HAMC、OAuth2,以及凭证技术JWT Token。
传统的login表单,当用户输入完用户名密码后,浏览器做了非常简单的操作:
API也可以提供非常简单的HTTP Basic Authentication认证方式:
这种方式实现起来非常简单,在大量场景下被采用。
缺点也很明显,Base64只能称为编码,而不是加密。这种方式的致命弱点是编码后的密码如果明文传输则容易在网路传输中泄露,在密码不会过期的情况下,密码一旦泄露,只能修改密码。
在对接一些PASS平台和支付平台时,会要求预先生成一个Access Key和Secure Key,然后通过签名的方式完成认证请求,这种方式可以避免传输Secure Key,且大多数情况下签名只允许使用一次,避免了重放攻击。
基于AK/SK的认证方式主要是利用散列的消息认证码(Hash-based MessageAuthentication Code)来实现,因此有很多地方叫HMAC认证,实际上不是非常准确。HMAC只是利用带有Key值的哈希演算法生成消息摘要,在设计API时有不同的具体实现。
HMAC在作为网路通信的认证设计中作为凭证生成演算法使用,避免了口令等敏感信息在网路中传输,基本过程如下:
为了让每一次请求的签名变得独一无二,从而避免重放攻击,需要在签名时放入一些干扰信息。
业界标准中有两种典型的做法,质疑/应答演算法、基于时间的一次性密码演算法。
质疑/应答演算法需要客户端先请求一次伺服器,获得一个401未认证的返回,并得到一个随机字元串(nonce)。将nonce附加到按照上面说到的方法进行HMAC签名,伺服器使用预先分配的nonce同样进行签名校验,这个nonce在伺服器只会被使用一次,因此可以提供唯一的摘要。
为了避免额外的请求来获取nonce,还有一种演算法是使用时间戳,并且通过同步时间的方式协商到一致,在一定的时间窗口内有效。
利用时间戳作为验证的时间窗口,并不能严格的算作基于时间的一次性密码演算法。标准的基于时间的一次性密码演算法在两步验证中被大量使用,例如Google身份验证器不需要网路通信也能实现验证(但依赖准确的授时服务)。原理是客户端伺服器共享密钥然后根据时间窗口能通过HMAC演算法计算出一个相同的验证码。
OAuth(开放授权)是一个开放标准,允许用户授权第三方网站访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方网站或分享他们数据的所有内容。
OAuth是一个授权标准,而不是认证标准。提供资源的伺服器不需要知道确切的用户身份(Session),只需要验证授权伺服器授予的许可权(Token)即可。
上图是OAuth的一个简化流程,OAuth的基本思路就是通过授权伺服器获取Access Token和Refresh Token(Refresh Token用于重新刷新Access Token,然后通过Access Token从资源伺服器获取数据。
在特定的场景下还有下面几种模式:
如果需要获取用户的认证信息,OAuth本身没有定义这部分内容,如果需要识别用户信息,则需要借助另外的认证层,例如OpenID Connect。
OAuth中资源伺服器如何验证Access Token,OAuth Core标准并没有定义这部分,不过在OAuth其他标准文件中提到两种验证Access Token的方式。
授权伺服器会在第一次授权请求时一起返回Access Token和Refresh Token,在后面刷新Access Token时只需要RefreshToken。Access Token和Refresh Token的设计意图是不一样的,Access Token被设计用来客户端和资源伺服器之间交互,而Refresh Token是被设计用来客户端和授权伺服器之间交互。
某些授权模式下Access Token需要暴露给浏览器,充当一个资源伺服器和浏览器之间的临时会话,浏览器和资源伺服器之间不存在签名机制,Access Token成为唯一凭证,因此Access Token的过期时间(TTL)应该尽量短,从而避免用户的Access Token被嗅探攻击。
由于要求Access Token时间很短,Refresh Token可以帮助用户维护一个较长时间的状态,避免频繁重新授权。实际上Refresh Token和Access Token的不同之处在于即使Access Token被截获,系统依然是安全的,客户端拿著Refresh Token去获取Access Token时同时需要预先配置的Secure Key,客户端和授权伺服器之前始终存在安全的认证。
OAuth负责解决分散式系统之间的授权问题,即使有时候客户端和资源伺服器或者认证伺服器存在同一台机器上。OAuth没有解决认证的问题,但提供了良好的设计利于和现有的认证系统对接。
Open ID解决的问题是分散式系统之间身份认证问题,使用Open ID Token能在多个系统之间验证用户,以及返回用户信息,可以独立使用,与OAuth没有关联。
OpenID Connect解决的是在OAuth这套体系下的用户认证问题,实现的基本原理是将用户的认证信息(ID token)当做资源处理。在OAuth框架下完成授权后,再通过Access Token获取用户的身份。
这三个概念之间的关系有点难以理解,用现实场景来说,如果系统中需要一套独立的认证系统,并不需要多系统之间的授权可以直接采用Open ID。如果使用了OAuth作为授权标准,可以再通过OpenID Connect来完成用户的认证。
在OAuth等分散式的认证、授权体系下,对凭证技术有了更多的要求,比如包含用户ID、过期等信息,不需要再外部存储中关联。因此业界对Token做了进一步优化,设计了一种自包含令牌,令牌签发后无需从伺服器存储中检查是否合法,通过解析令牌就能获取令牌的过期、有效等信息,这就是JWT(JSON Web Token)。
JWT是一种包含令牌(self-contained token),或者叫值令牌(value token),关联到Session上的Hash值被叫做引用令牌(reference token)。
简而言之,一个基本的JWT令牌为一段点分3段式结构。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
生成JWT令牌的流程为:
因此只需要签名的secret key就能校验JWT令牌,如果在消息体中加入用户ID、过期信息就可以实现验证令牌是否有效、过期了,无需从资料库/缓存中读取信息。因为使用了加密演算法,所以第一、二部分即使被修改(包括过期信息)也无法通过验证。JWT优点是不仅可以作为Token使用,同时也可以承载一些必要信息,省去多次查询。
PS:
JWT Token在微服务的系统中优势特别突出。多层调用的API中可以直接传递JWT Token,利用自包含的能力,可以减少用户信息查询次数;更重要的是,使用非对称的加密方式可以通过在系统中分发密匙的方式验证JWT token。
OAuth对Access Token等凭证所选用的技术并没有做出限制,OAuth并不强制使用JWT,在使用JWT自包含特性的优势时,必须考虑到JWT撤回困难的问题。在一些对撤回Token要求很高的项目中不适合使用JWT,即使采用了一些方案实现(WhiteList 和 BlackList)也违背了设计JWT的初衷。
在构建API时,认证方式和网页应用有一些不同,除了像Ajax这种web技术外,如果希望API是无状态的,则不推荐使用Cookie。
使用Cookie的本质是用户第一次访问时伺服器会分配一个Session ID,后面的请求中客户端都会带上这个ID作为当前用户的标志,因为HTTP本身是无状态的,Cookie属于一种内建于浏览器中实现状态的方式。如果API是用来给客户端使用的,强行要求API的调用者管理Cookie也可以完成任务。
在一些遗留或者不是标准的认证实现的项目中,依然可以看到这些做法,快速地实现认证。
随著微服务的发展,API的设计不仅仅是面向web或者Mobile App,还有BFF(Backend for Frontend)和Domain API的认证,以及第三方服务的集成。
客户端到伺服器之间认证和伺服器到伺服器之间认证是不同的。
把终端用户参与的通信,叫做Human-to-machine(H2M),伺服器与伺服器之间的通信叫做Machine-to-machine(M2M)。
H2M的通信需要更高的安全性,M2M的通信天然比H2M安全,因此更多的强调性能,在不同的场合下选择合适的认证技术就显得特别重要。例如HTTP Basic Authentication用来作为H2M认证显得有些落后,但是在M2M中被大量使用。
另外值得一提的是,H2M这种通信方式下,客户端不受控制,由于无法自主分发密匙,认证通信的安全高度依赖HTTPS。
参考:
shieldfy/API-Security-Checklist
https://swagger.io/docs/specification/authentication/basic-authentication/
推荐阅读: