全文转载:《细说API – 认证、授权和凭证》 - THOUGHTWORKS

互联网基于HTTP协议构建,而HTTP协议因为简单而流行开来,但是HTTP协议是无状态的,为此开发者为了追踪用户想出了各种办法,包括Cookie/Session机制、Token、Flash跨浏览器Cookie,甚至浏览器指纹等。

更有甚者,把userID存储到LocalStorage中当做Token使用;移动端项目,伺服器给出的API中需要客户端模拟Cookie,从而像浏览器中Ajax方式调用API。

一、认证、授权、凭证

首先,认证和授权是两个不同的概念,为了让API更加安全和具有清晰的设计,理解认证和授权的不同就非常有必要了,它们在英文中也是不同的单词。

A、认证 - Authentication

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

B、授权 - Authorization

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

实现认证和授权的基础是需要一种媒介(credentials)来标记访问者的身份或权利,在现实生活中每个人都需要一张身份证才能访问自己的银行账户、结婚和办理养老保险等,这就是认证的凭证;在古代军事活动中,皇帝会给出战的将军颁发兵符,下级将领不关心持有兵符的人,只需要执行兵符对应的命令即可。在互联网世界中,伺服器为每一个访问者颁发Session ID存放到Cookie,这就是一种凭证技术。数字凭证还表现在方方面面,SSH登录的密匙、JWT令牌、一次性密码等。

C、访问控制策略 - AC

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

在流行的技术和框架中,这些概念都无法孤立的被实现,API开发中常常使用的认证和授权技术:HTTP Basic AUthentication、HAMC、OAuth2,以及凭证技术JWT Token。

二、HTTP Basic Authentication

传统的login表单,当用户输入完用户名密码后,浏览器做了非常简单的操作:

  1. 组合用户名和密码然后Base64编码
  2. 给编码后的字元串添加Basic前缀,然后设置名称为Authorization的header头部

API也可以提供非常简单的HTTP Basic Authentication认证方式:

  1. 将用户名和密码使用冒号连接,例如username:abc123456
  2. 为了防止用户名或者密码中存在超出ASCII码范围的字元,推荐使用UTF-8编码
  3. 将上面的字元串使用Base64编码,例如dXNlcm5hbWU6YWJjMTIzNDU2
  4. 在HTTP请求头中加入Basic + 编码后的字元串,即:Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l

这种方式实现起来非常简单,在大量场景下被采用。

缺点也很明显,Base64只能称为编码,而不是加密。这种方式的致命弱点是编码后的密码如果明文传输则容易在网路传输中泄露,在密码不会过期的情况下,密码一旦泄露,只能修改密码。

三、HMAC(AK/SK)认证

在对接一些PASS平台和支付平台时,会要求预先生成一个Access Key和Secure Key,然后通过签名的方式完成认证请求,这种方式可以避免传输Secure Key,且大多数情况下签名只允许使用一次,避免了重放攻击。

基于AK/SK的认证方式主要是利用散列的消息认证码(Hash-based MessageAuthentication Code)来实现,因此有很多地方叫HMAC认证,实际上不是非常准确。HMAC只是利用带有Key值的哈希演算法生成消息摘要,在设计API时有不同的具体实现。

HMAC在作为网路通信的认证设计中作为凭证生成演算法使用,避免了口令等敏感信息在网路中传输,基本过程如下:

  1. 客户端需要在认证伺服器中预先设置Access Key(AK或App ID)和Secure Key(SK)
  2. 在调用API时,客户端需要对参数和Access Key进行自然排序后并使用Secure Key进行签名,生成一个额外的参数Digest
  3. 伺服器根据预先设置的Secure Key进行同样的摘要计算,并要求结果完全一致
  4. 注意Secure Key不能在网路中传输,以及在不受信任的位置存放(浏览器等)

为了让每一次请求的签名变得独一无二,从而避免重放攻击,需要在签名时放入一些干扰信息。

业界标准中有两种典型的做法,质疑/应答演算法、基于时间的一次性密码演算法。

A、质疑/应答演算法

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

B、基于时间的一次性密码认证

为了避免额外的请求来获取nonce,还有一种演算法是使用时间戳,并且通过同步时间的方式协商到一致,在一定的时间窗口内有效。

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

四、OAuth2和Open ID

OAuth(开放授权)是一个开放标准,允许用户授权第三方网站访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方网站或分享他们数据的所有内容。

OAuth是一个授权标准,而不是认证标准。提供资源的伺服器不需要知道确切的用户身份(Session),只需要验证授权伺服器授予的许可权(Token)即可。

上图是OAuth的一个简化流程,OAuth的基本思路就是通过授权伺服器获取Access Token和Refresh Token(Refresh Token用于重新刷新Access Token,然后通过Access Token从资源伺服器获取数据。

在特定的场景下还有下面几种模式:

  1. 授权码模式(authorization code)
  2. 简化模式(implicit)
  3. 密码模式(resource owner password credentials)
  4. 客户端模式(client credentials)

如果需要获取用户的认证信息,OAuth本身没有定义这部分内容,如果需要识别用户信息,则需要借助另外的认证层,例如OpenID Connect。

A、验证Access Token

OAuth中资源伺服器如何验证Access Token,OAuth Core标准并没有定义这部分,不过在OAuth其他标准文件中提到两种验证Access Token的方式。

  1. 在完成授权流程后,资源伺服器可以使用OAuth伺服器提供的Introspection介面来验证Access Token,OAuth伺服器会返回Access Token的状态以及过期时间。在OAuth标准中验证Token的术语是Introspection。同时也需要注意Access Token是用户和资源伺服器之间的凭证,不是资源伺服器和授权伺服器之间的凭证。资源伺服器和授权伺服器之间应该使用额外的认证。
  2. 使用JWT验证。授权伺服器使用私钥签发JWT形式的Access Token,资源伺服器需要使用预先配置的公钥校验JWT Token,并得到Token状态和一些被包含在Access Token中信息。因此在JWT的方案下,资源伺服器和授权伺服器不再需要通信,在一些场景下带来巨大的优势。

B、Refresh Token和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,客户端和授权伺服器之前始终存在安全的认证。

C、OAuth、Open ID、OpenID Connect

OAuth负责解决分散式系统之间的授权问题,即使有时候客户端和资源伺服器或者认证伺服器存在同一台机器上。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段式结构。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

生成JWT令牌的流程为:

  1. header Json的Base64编码为令牌第一部分
  2. payload Json的Base64编码为令牌第二部分
  3. 拼装第一、第二部分编码后的Json以及Secret进行签名的令牌的第三部分

因此只需要签名的secret key就能校验JWT令牌,如果在消息体中加入用户ID、过期信息就可以实现验证令牌是否有效、过期了,无需从资料库/缓存中读取信息。因为使用了加密演算法,所以第一、二部分即使被修改(包括过期信息)也无法通过验证。JWT优点是不仅可以作为Token使用,同时也可以承载一些必要信息,省去多次查询。

PS:

  1. JWT Token的第一、二部分只是Base64编码,肉眼不可读,不应当存放敏感信息
  2. JWT Token的自包含特性,导致了无法被撤回
  3. JWT的签名演算法可以自己拟定,为了便于调试,本地环境可以使用对称加密演算法,生产环境建议使用非对称加密演算法

JWT Token在微服务的系统中优势特别突出。多层调用的API中可以直接传递JWT Token,利用自包含的能力,可以减少用户信息查询次数;更重要的是,使用非对称的加密方式可以通过在系统中分发密匙的方式验证JWT token。

OAuth对Access Token等凭证所选用的技术并没有做出限制,OAuth并不强制使用JWT,在使用JWT自包含特性的优势时,必须考虑到JWT撤回困难的问题。在一些对撤回Token要求很高的项目中不适合使用JWT,即使采用了一些方案实现(WhiteList 和 BlackList)也违背了设计JWT的初衷。

六、Cookie、Token in Cookie、Session Token

在构建API时,认证方式和网页应用有一些不同,除了像Ajax这种web技术外,如果希望API是无状态的,则不推荐使用Cookie。

使用Cookie的本质是用户第一次访问时伺服器会分配一个Session ID,后面的请求中客户端都会带上这个ID作为当前用户的标志,因为HTTP本身是无状态的,Cookie属于一种内建于浏览器中实现状态的方式。如果API是用来给客户端使用的,强行要求API的调用者管理Cookie也可以完成任务。

在一些遗留或者不是标准的认证实现的项目中,依然可以看到这些做法,快速地实现认证。

  1. 使用Cookie,例如web项目中Ajax的方式
  2. 使用Session ID或Hash作为Token,但将Token放入header中传递
  3. 将生成的Token(可能是JWT)放入Cookie传递,利用HTTPonly和Secure标签保护Token

七、选择合适的认证方式

随著微服务的发展,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。

八、安全API设计规范

A、身份认证

  1. 不使用basic auth使用标准的认证协议(如JWT、OAuth)
  2. 不再造authentication、token generating、password storing轮子,使用标准方案
  3. 在登录中使用max retry和自动封禁功能
  4. 加密所有的敏感数据

B、JWT(JSON Web Token)

  1. 使用随机复杂的密钥(JWT Secret)以增加暴力破解的难度
  2. 不在请求体中直接提取数据,要对数据进行加密(HS256或RS256)
  3. 使token的过期时间尽量的短(TTL、RTTL)
  4. 不在JWT的请求体中存放敏感数据,它是可破解的

C、OAuth授权或认证协议

  1. 始终在后台验证redirect_uri,只允许白名单的URL
  2. 每次交换令牌的时候不加token(不允许response_type=token)
  3. 使用state参数并填充随机的哈希数来防止跨站请求伪造(CSRF)
  4. 对不同的应用分别定义默认的作用域和各自有效的作用域参数

D、访问

  1. 限制流量来防止DDoS攻击和暴力攻击
  2. 在服务端使用HTTPS协议来防止MITM攻击
  3. 使用HSTS协议防止SSLStrip攻击

E、输入

  1. 使用与操作相符的HTTP操作函数,GET(读取)、POST(创建)、PUT(替换/更新)以及DELETE(删除记录),如果请求的方法不适用于请求的资源则返回405 Method Not Allowed
  2. 在请求头中的content-type栏位使用内容验证来只允许支持的格式(如application/xml、application/json等等)并在不满足条件的时候返回406 Not Acceptable
  3. 验证content-type的发布数据和收到的一样(如application/x-www-form-urlencoded、multipart/form-data、application/json等等)
  4. 验证用户输入来避免一些普通的易受攻击缺陷(如XSS、SQL注入、远程代码执行等等)
  5. 不在URL中使用任何敏感的数据(credentials、Passwords、security tokens、API keys)而是使用标准的认证请求头
  6. 使用一个API Gateway服务来启用缓存、访问速率限制(如Quota、Spike Arrest、Concurrent Rate Limit)以及动态地部署APIs resources

F、处理

  1. 检查是否所有的终端都在身份认证之后,以避免被破坏了的认证体系
  2. 避免使用特有的资源id,使用/me/orders替代/user/654321/orders
  3. 使用UUID代替自增长的id
  4. 如果需要解析XML文件,确保实体解析(entity parsing)是关闭的以避免XXE攻击
  5. 如果需要解析XML文件,确保实体扩展(entity expansion)是关闭的以避免通过指数实体扩展攻击实现的Billion Laughs/XML bomb
  6. 在文件上传中使用CDN
  7. 如果需要处理大量的数据,使用Workers和Queues来快速响应,从而避免HTTP阻塞
  8. 关闭DEBUG模式

G、输出

  1. 发送X-Content-Type-Options: nosniff头
  2. 发送X-Frame-Options: deny头
  3. 发送Content-Security-Policy: default-src none头
  4. 删除指纹头X-Powered-By、Server、X-AspNet-Version等等
  5. 在响应中强制使用content-type
  6. 不返回敏感的数据,如credentials、Passwords、security tokens
  7. 在操作结束时返回恰当的状态码(如200 OK、400 Bad Request、401 Unauthorized、405 Method Not Allowed等等)

H、持续集成和持续部署

  1. 使用单元测试和集成测试来审计设计和实现
  2. 引入代码审查流程,不自行批准更改
  3. 在推送到生产环境之前确保服务的所有组件都用杀毒软体静态地扫描过,包括第三方库和其它依赖
  4. 为部署设计一个回滚方案

I、其他

  1. 构建RESTful API的有用资源集合 - 参考yosriady/api-development-tools

九、术语表

  1. Browser fingerprinting,通过查询浏览器的代理字元串,屏幕色深,语言等,然后这些值通过散列函数传递产生指纹,不需要通过Cookie就可以识别浏览器。
  2. MAC(Message authentication code)在密码学中,讯息鉴别码,是经过特定演算法后产生的一小段资讯,检查某段讯息的完整性。
  3. HOTP(HMAC-based One-time Password algorithm)基于散列消息验证码的一次性密码演算法。
  4. Two-step verification是一种认证方法,使用两种不同的元素,合并在一起,来确认使用者的身份,是多因素验证中的一个特例。
  5. OTP(One time password)一次性密码,例如注册邮件和简讯中的认证码。

参考:

shieldfy/API-Security-Checklist

swagger.io/docs/specifi

推荐阅读:

相关文章