上文中分析了常见的 API Auth 鉴权方案,本文再单独分析下服务端鉴权常用的一些实践。
Server Side 指的是 API 的使用方是一个服务器,而不是面向用户(浏览器、客户端 APP)
▎常见的方案中,都有哪些呢?
- KeySecert (Basic Auth)
- JWT
这几种方案差别上在:
- Token 的生成
- Token 的表达能力
- 签名方式
下面几种方案的使用,进行详细的描述。
> KeySecert
一般在开放 API 的服务端那里申请访问,都会给一组下面的值:
UserID | opsman@lanvige.com |
AccessKey ID | LTAISERp |
AccessKeySecret | xCkspAakY |
有时也只会给 Secret,关于这一些,下面会单独讲。
在使用上,有以下三组常见情况,按 Token 传递方式,是否签名来分
Token 位置 | 明文 | 签名 | |
---|---|---|---|
HTTP Auth | Header | 是 | 否 |
HTTP Query | Query | 是 | 否 |
签名形式 | Query | 否 | 是 |
- 使用 HTTP Basic Auth
在使用上,可以有两种方式,其中一种就是,使用 HTTP Authorization 标准,
可以通过标准的 HTTP Basic Auth Scheme 来传递,把 key/secret 当作用户名密码来使用:
1 | Authorization Basic b64(key:secrect) |
- 签名形式 HTTP Digest Auth
还有另一种用法,不直接传递 Secret,传递会导致泄露,而是通过签名的形式,对每次的请求,按一定的规则进行签名。
签名的好处是,Secret 不会被泄露,而通过签名传递的数据,可以防止篡改,提升安全的同时,也让参数变的更有用,之前一些必要非敏感不能通过参数传递,只能服务端多几次查询的情况,现在可以通过签名的形式提供,更加安全。
如何对它进行签名呢?
Digest
参加 Digest 的方式,加上一些参数,然后再进行签名。
阿里云的实践:
把所有的参数排序,然后使用 Secret 对其进行签名
1 | https://vpc.aliyuncs.com/?Action=DescribeVpcs |
> JWT
再看下 JWT 的形式,其实上面 KeySecret 中的 签名方式,已经做完了所有的事情,但会发现,参数太凌乱了。如果把一些公用的参数收集在一起,然后放在一起进行传递。进行标准化,会更省心,好用。
这就是 JWT 要做的。它定义了一种结构,用 JSON 序列化,放在 HTTP Header 的 Authorization 中。通过 Payload 来表示要传递的数据部分。
JWT 是自签名的,可以安全的传递数据
签名方式上,有多种选择,像 HAMC-SHA,RSA-SHA,EcDSA-SHA 等对称和非对称签名。
- 请求格式
1 | Authorization Bearer xxxxx |
Payload
1 | { |
- iss: 标明客户端身份
- iat/exp: 时间属性
- jti: Nonce 用法,防重放
- sid: SessionID,用于表明本次的 Key
技术上可取代 iss,但 JWT 的优势就是多放一些数据,少一些查询。 - sig: 对整个请求进行的签名
- sal: 签名算法,MD5/SHA1/SHA256
- 对称签名
对称签名是指 Key/Secret 的方式,密钥只是一个随机字符串,服务端和客户端都保存同样的值。
每次请求,通过签名进行请求。
算法是唯一的: HMAC-SHA
- 非对称签名
非对称目前有很多主流算法,像 RSA、EcDSA,Ed 等,主流 JWT 库也都实现了相应的算法,可以在 JWT Header 中指定。
客户端保留私钥,服务端保留公钥。
每次请求,客户端用私钥进行签名,服务端用公钥进行验签。
非对称签名的性能消耗要大一些。但安全性也会高一些。
> 对比
JWT 有标准库,有格式定义,比较优雅
KeySecret 没有,都是自己实现的
JWT 默认实现自签名,而 KeySecret 没有,可以自定义实现
实现签名后,可以通过 URL/Header 安全的传递一些数据,减少服务端查询,增加性能。
更推荐 JWT 的用法。
▎设计
> 权限
每个 AccessKey,就像创建了一个子用户一样,要对其进权限的管理。
OAuth 2 中对授权范围做了一些定义,Scope,可以参考下。
但在技术层面上,可以当这个 Key 为一个用户,做 RBAC。
> 是否需要多个不同的 Key
有的是给个 KeyID,有的则是只给一个 Secret,
有 ID 的好处则是一个用户同时可以拥有多个不同访问账号,有效解决不同账号的过期时限问题,如果一个账号密钥泄露,可以不影响业务的情况下进行更换。
同时,最新的实践中,也会为不同的 Key 分配不同的权限。可以把不同权限的 Key/Secert 用在不同的场景中。
> 表设计
id | type | scheme | user_id | scope | publick_key | secret | expried_at | created_at |
---|---|---|---|---|---|---|---|---|
005f21 | jwtrs | bearer | 30075 | MFwwDQwEAAQ== | 2020-09-20 | 2020-04-01 | ||
005f23 | jwtes | bearer | 30075 | MFwwDQwEAAQ== | 2020-09-20 | 2020-04-01 | ||
005f27 | jwtps | bearer | 30075 | MFwwDQwEAAQ== | 2020-09-20 | 2020-04-01 | ||
69274f | jwths | bearer | 30075 | 69ed732 | 2020-09-20 | 2020-04-01 | ||
203782 | secret | token | 30075 | 90ed7a1 | 2020-09-20 | 2020-04-01 | ||
203782 | secret | / | 30075 | 90ed7a1 | 2020-09-20 | 2020-04-01 | ||
203112 | sign | / | 30075 | MFwwDQwEAAQ== | 2020-09-20 | 2020-04-01 |
jwths 表示是对称加密
jwtrs 非对称加密
▎其它
> 时效性问题
一般 token 时效性如何设计,在 JWT 中可以通过 EXP 来进行限制,但这是一种客户端行为。
服务端可以在拿到 JWT 时,验证下 exp - iat <= limit
> 长期 Token
有时为了接入简单,会需要一个长期可以用的 Token,当前的省力办法就是用 JWT 签发一个长期的 Token。
但这样不安全,也不符合上一个时效性问题。
如果不需要签名的话,可以用 Key/Secret 中的 HTTP Auth 方案。
▎REF::
- Coding 令牌 - https://help.coding.net/docs/member/tokens.html
- Aliyun Token 实践 - https://help.aliyun.com/document_detail/35733.html
- 微信支付签名算法 - https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3