鉴权作用
在实际的业务中,必然会存在和其他平台系统进行数据传输。这个时候出于对数据的保密要求,都会对接口(API)添加鉴权机制,识别调用方的真实身份,对未通过鉴权的请求不做任何业务处理,以帮助接口更好的识别用户及其调用行为的合法性。
API鉴权的作用:识别调用方身份,控制API的访问权限,进而保护平台数据的安全。
鉴权方案设计
目前鉴权主要分为两种:Token方案和API签名方案。
1、两者最大的不同在于校验信息的适用范围不同,而由此带来的安全方面的差距就比较明显。
Token方案:多个请求(即一段时间内的请求)对应一个Token。若Token信息被窃取后,其他平台是可以据此伪造一系列请求进行攻击的。
API签名方案:一个请求对应一个签名信息;若API签名信息被窃取后,其他平台是无法据此发起更多有效攻击的
2、两者的第二大不同在于,密钥信息的使用不同,由此带来使用场景方面的不同。
Token方案:存在登录交互的过程,用户输入一次密钥,换回票据;后续请求无需密钥参与,使用票据即可。在另一篇博文中有介绍,OAuth 2.0设计(以微信登录为例)
API签名方案:每次请求都需要密钥参与,绝不可能每次请求都让用户输入密钥,那么就要求发起方存储密钥;若是js、app等纯前端场景存储密钥一定安全问题。
总结:API签名方案适用于安全性较高的后端交互场景,其余则考虑Token方案。
API签名方案
数据准备
对一个已存在的API添加签名鉴权,一般需要在接口请求参数处额外增加以下五个签名特有参数。
ServiceID:第三方平台调用API前,必须向API所在系统申请一个唯一的标识。调用者调用API时在请求中带上该ServiceID,API则。通过SecretID确认调用者身份。(API系统为每个调用者分配一个唯一的ServiceID)
ServiceKey:由于ServiceID是明文,极容易被窃取和伪造;但ServiceID不能隐藏或加密,因为SecretID需要明确告诉API请求方是谁。故,在ServiceID之外,设计了一个和ServiceID绑定的信息ServiceKey。调用者必须保护好ServiceKey,不能在任何地方明文显示,也不要请求过程中传输。它主要是用来生成Sign值。
Nonce:随机字符串,是调用者随机生成的值,通过请求传递给API。其组成和长度没有固定规则,主要是为了增加sign签名的多变性和防止重复请求。
TimeStamp:时间戳,调用请求时间,用来生成Sign和验证请求的时效。
Sign:参数签名,防止参数在传输过程中被非法篡改。一般由 (请求中所有非空参数 + ServiceID + ServiceKey + Nonce + TimeStamp)通过加密算法生成。sign通过请求传递给API确认请求是否合法。
调用方流程
(1)生成请求正常参数。
(2)生成随机数Nonce,TimeStamp;获取ServiceID和ServiceKey;拼接签名内容,加密生成Sign信息。
(3)调用API时,带上ServiceID,Nonce,TimeStamp和Sign等信息。
服务API方校验流程
(1)获取请求,校验必须参数是否为空。
(2)校验时间有效性Timestamp;校验Nonce唯一性;根据SecretId获取SecretKey,和其他参数生成Sign;校验请求签名和服务端生产签名是否一致。
(3)校验通过,正常处理请求。
具体代码:
// VerifySign 校验权限
func VerifySign(auth Auth, ctx context.Context) (bool, error) {
// 1. 验证时间戳
now := time.Now().Unix()
diff := uint64(now) - auth.Timestamp
if diff > 10 {
return false, nil
}
// 2. 验证请求是否重复
m := redis.NewModel(ctx)
flag, err := m.ExistsKey(auth.Sign)
if err != nil {
return false, err
}
if flag {
return false, err
} else {
err = m.SetKey(auth.Sign, 10)
if err != nil {
return false, err
}
}
// 3. 加密签名
s := tool.SignElem{
ServiceID: auth.ServiceID,
ServiceKey: auth.ServiceKey,
Random: auth.Nonce,
TimeStamp: auth.Timestamp,
}
sign := tool.HmacSha512Sign(s)
if sign != auth.Sign {
return false, nil
}
return true, nil
}
注意,上述代码都没有携带参数,真实使用时需修改入参携带具体API参数。
签名生产流程
在密码学中,有对称加密算法、非对称加密算法、 希运算消息认证码等等几种方案可以很好保护用户密钥的同时,验证用户的身份。
(1)排除非对称加密算法,理由是耗时长,性能差。通过实测,非对接加密算法(RSA)相对加密算法(AES)和 希运算消息认证码算法(HmacSHA256)的加解密耗时要高2~3个数量级,对于一个服务端来说,性能也是很重要的考虑标准,故一般不选择非对称加密算法。
(2)Hmac和AES似乎都不错,而且AES更优。但考虑到签名的目的,除了明确用户身份外,还要明确调用者的调用行为;也就是说,为了需要保证整个请求的完整性,需要加密整个请求的所有关键内容,这时,Hmac算法的防伪造性(即修改一个字节,签名信息就完全不一样)的优势就突显出来了,在性能差不多的情况下,当然,选择Hmac算法了。
(3)Hmac支持的hash算法非常多,但一般不建议使用MD5和SHA1,因其有哈希长度扩展攻击(Hash Length Extension Attacks)的风险,故一般推荐使用HmacSHA256或HmacSHA512。
若服务端支持多种算法,则请求时,需明确带上使用的签名方法:SignatureMethod。
注意,上述代码都没有携带参数,真实使用时需修改入参携带具体API参数。
保证请求时效性
客户端调用接口时对应的当前请求的Timestamp, 即某个请求,其请求时间戳Timestamp,和服务端的当前时间在规定时间内(如1分钟内)则为合法请求,反之,则视为无效请求。
防重复提交
对于一些需要防止客户端重复提交的(如非幂等性重要操作),具体是获取第一次请求时将sign作为key保存到redis,并设置超时时间,超时时间和Timestamp中设置的差值相同。当同一个请求第二次访问时会先检测redis是否存在该sign,如果存在则证明重复提交了,接口就不再继续调用了。(也可以存储Nonce)