本文举例来说明API签名,并有具体实现流程,规则弄会,一通百通。 本文先用一个故事举例,方便理解,然后对整个流程做了逐步分析和局部代码实现,最后把代码整合起来,想直接看整合后代码的可以直接去最底。
有家饭店,只对会员开放,小明在饭店注册会员。 饭店给小明一个会员号"xiaoming"(签名中的ApiKey)并且永久有效。 又给小明一个令牌(签名中的SecretKey),令牌内容是"abcd",并告诉小明令牌只能自己知道,不可以告诉别人,又告知令牌30天后失效,失效后可以再来申请一个新的令牌(依然30天有效期)。 (饭店是知道小明的会员号和令牌的) 这天,小明要在饭店点餐,主食:米饭,炒菜:土豆丝, 于是给饭店发送了点菜短信,短信内容可以有多种方案,具体内容如下(这里只写两个短信方案): 方案一:
会员号:xiaoming 主食:米饭 炒菜:土豆丝 口令(signature):abcd(实际开发中,令牌abcd不会用于网络通讯中, 而是把会员号,主食,炒菜三个键值对加密拼接, 并用‘abcd’进行加密, 具体实现下面会说明,这里先了解流程)方案二:
会员号:小明 主食:米饭 炒菜:土豆丝 时间(timestamp):12点(假设偏差10分钟,如果饭店收到消息比11点50早 或比12点10分晚,就认为是骗子) 随机码(nonce):7878(饭店每次都记录随机码,收到信息后看看随机码之前用过没, 如果用过,就认为是骗子) 口令(signature):abcd(实际开发中,令牌abcd不会用于网络通讯中, 而是会把上边5个键值对先加密, 再拼接,再用‘abcd’加密作为口令, 具体实现下面会说明,这里先了解流程)饭店收到信息后核对无误,开始做饭。 有人会有疑问,如果骗子随便写个随机码,刚好之前没用过,岂不是就蒙混过关了??注意一点,口令的生成和随机码有关系,所以只要骗子拿不到令牌(secretkey),就无法生成正确的口令。
服务器会返回两个key
AccessKey: 身份标识,唯一,类似ID,用户名什么的 SecretKey:密钥,只保存在客户端和服务器,不做网络传递,一般都有有效期本文中
accesskey="ch_improve" secretkey="abcd"需要传递的参数有:
{ "accesskey": "ch_improve", "name": "gaofushuai", "city": "zhengzhou", }实际运用中,可以添加参数,例如
SignatureMethod="HmacSHA256" timestamp=1572662767 method="POST" nonce=7878我以如下参数为例
{ "accesskey": "ch_improve", "name": "gaofushuai", "city": "zhengzhou", "SignatureMethod": "HmacSHA256", "timestamp": 1572662767, "method": "POST", "nonce": 7878 }首先需要对传递的参数按照键的字典升序进行排序,并得到URL键值对格式的字符串 SignatureMethod=HmacSHA256&accesskey=ch_improve&city=zhengzhou&method=POST&name=gaofushuai&nonce=7878×tamp=1572662767
SignatureMethod=HmacSHA256&accesskey=ch_improve&city=zhengzhou&method=POST&name=gaofushuai&nonce=7878×tamp=1572662767python3代码实现 (1)字典升序排序
args = { "accesskey": "ch_improve", "name": "gaofushuai", "city": "zhengzhou", "SignatureMethod": "HmacSHA256", "timestamp": 1572662767, "method": "POST", "nonce": 7878 } args_order = sorted(args.items(), key=lambda x: x[0])args_order的值如下
[('SignatureMethod', 'HmacSHA256'), ('accesskey', 'ch_improve'), ('city', 'zhengzhou'), ('method', 'POST'), ('name', 'gaofushuai'), ('nonce', 7878), ('timestamp', 1572662767)](2)转为URL字符串
import urllib.parse args_str = urllib.parse.urlencode(args_order)args_str的值如下,是一个长字符串
SignatureMethod=HmacSHA256&accesskey=ch_improve&city=zhengzhou&method=POST&name=gaofushuai&nonce=7878×tamp=1572662767常用加密方法有MD5、SHA256等等,本文采用HmacSha256加密
import hmac secretkey="abcd" # 先把secretkey和args_str转为byte类型 b_secretkey = secretkey.encode('utf-8') b_args_str = args_str.encode('utf-8') # 用密钥对URL格式的参数进行hmacsha256加密 signature = hmac.new(b_secretkey, b_args_str, digestmod='sha256').hexdigest()signature签名的值如下
3623d8070662392772d10952ea39c277518546006e6649f4ea358671e51e06f7有些服务器要求把signature签名字符串全部转为大写,转不转大写看接口要求,本文就不再转大写了
(1)URL键值对字符串
accesskey=ch_improve&name=gaofushuai&city=zhengzhou& SignatureMethod=HmacSHA256×tamp=1572662767&meth od=POST&nonce=7878&signature=3623d8070662392772d10952 ea39c277518546006e6649f4ea358671e51e06f7比如访问http://127.0.0.1:8888,那么url就是
http://127.0.0.1:8888/?accesskey=ch_improve&name=gaofushuai &city=zhengzhou&SignatureMethod=HmacSHA256×tamp=1572662767 &method=POST&nonce=7878&signature=3623d8070662392772d10952e a39c277518546006e6649f4ea358671e51e06f7(2)请求头中携带参数 可以自定义一个字段,比如"MyArgs",值就使用字典参数的json格式字符串,如下
Myargs: {"accesskey": "ch_improve", "name": "gaofushuai", "city": "zhengzhou", "SignatureMethod": "HmacSHA256", "timestamp": 1572662767, "method": "POST", "nonce": 7878, "signature": "3623d8070662392772d10952ea39c277518546006e6649f4ea358671e51e06f7"}(3)请求体中携带参数 这个直接放入请求体就行,作为一般参数
假如约定时间戳误差范围为10分钟,也就是600秒,根据接收到的时间戳1572662767计算出时间戳范围
1572662767-600 ~~ 1572662767+600获取服务器本地时间戳
import time timestamp = time.time()如果得到结果不在范围内,则拒绝访问
(1)nonce为客户端随机生成的验证码,当服务器接收到请求后,会把nonce存储到数据库中,一般使用redis,并设置一个有效期,一般和时间戳timestamp的失效时间保持一致,设为10分钟有效期。 (2)当服务器接收到请求后,用请求中nonce(本文为7878)和redis中的nonce集合做比较,如果已经存在,则拒绝访问接口,只有当10分钟之内第一次使用7878这个动态码,才判定访问有效。
对除了signature之外的所有参数进行升序URL格式转换拼接,得到字符串再用密钥secretkey(本文是‘abcd’)加密,得到signature2,和请求中的signature做比较判断请求是否合法。 不一致则拒绝访问。
