服务端鉴权最佳实践

上文中分析了常见的 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
2
3
4
5
6
7
8
9
10
https://vpc.aliyuncs.com/?Action=DescribeVpcs
&Format=xml
&Version=2014-05-15
&Signature=xxxx%xxxx%3D
&SignatureMethod=HMAC-SHA1
&SignatureNonce=15215528852396
&SignatureVersion=1.0
&AccessKeyId=key-test
&Timestamp=2012-06-01T12:00:00Z


> JWT

再看下 JWT 的形式,其实上面 KeySecret 中的 签名方式,已经做完了所有的事情,但会发现,参数太凌乱了。如果把一些公用的参数收集在一起,然后放在一起进行传递。进行标准化,会更省心,好用。

这就是 JWT 要做的。它定义了一种结构,用 JSON 序列化,放在 HTTP Header 的 Authorization 中。通过 Payload 来表示要传递的数据部分。

JWT 是自签名的,可以安全的传递数据
签名方式上,有多种选择,像 HAMC-SHA,RSA-SHA,EcDSA-SHA 等对称和非对称签名。

- 请求格式

1
Authorization Bearer xxxxx
Payload
1
2
3
4
5
6
7
8
9
{
"iss": iss,
"iat": time.Now().UTC().Unix(),
"exp": expire.Unix(),
"jti": uuid.NewV4().String(),
"sid": access_key,
"sig": query_signature,
"sal": "sha1/sha256",
}
  • 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::