API密钥签名认证详解,包含timestamp+nonce方案BY:Zz Apollo

mac2026-03-14  2

本文举例来说明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"

三、客户端生成签名

1.准备参数

        需要传递的参数有:

{ "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 }
2.对参数进行排序并转为URL键值对格式字符串

        首先需要对传递的参数按照键的字典升序进行排序,并得到URL键值对格式的字符串 SignatureMethod=HmacSHA256&accesskey=ch_improve&city=zhengzhou&method=POST&name=gaofushuai&nonce=7878&timestamp=1572662767

SignatureMethod=HmacSHA256&accesskey=ch_improve&city=zhengzhou&method=POST&name=gaofushuai&nonce=7878&timestamp=1572662767

        python3代码实现                 (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&timestamp=1572662767
3.使用密钥SecretKey对URL格式的字符串args_str进行加密获取签名

        常用加密方法有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签名字符串全部转为大写,转不转大写看接口要求,本文就不再转大写了

4.在原有参数基础上添加signature签名参数,得到最新的参数
{ "accesskey": "ch_improve", "name": "gaofushuai", "city": "zhengzhou", "SignatureMethod": "HmacSHA256", "timestamp": 1572662767, "method": "POST", "nonce": 7878, "signature": "3623d8070662392772d10952ea39c277518546006e6649f4ea358671e51e06f7" }
5.得到最终参数后,可以发送请求了,至于如何携带参数,基本有三种选择(URL字符串、请求头、请求体)

        (1)URL键值对字符串

accesskey=ch_improve&name=gaofushuai&city=zhengzhou& SignatureMethod=HmacSHA256&timestamp=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&timestamp=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)请求体中携带参数                 这个直接放入请求体就行,作为一般参数

四、服务器收到请求后验证

1.接受参数
{ "accesskey": "ch_improve", "name": "gaofushuai", "city": "zhengzhou", "SignatureMethod": "HmacSHA256", "timestamp": 1572662767, "method": "POST", "nonce": 7878, "signature": "3623d8070662392772d10952ea39c277518546006e6649f4ea358671e51e06f7" }
2.检查时间戳timestamp(如果不使用时间戳作为参数,则跳过此步)

        假如约定时间戳误差范围为10分钟,也就是600秒,根据接收到的时间戳1572662767计算出时间戳范围

1572662767-600 ~~ 1572662767+600

        获取服务器本地时间戳

import time timestamp = time.time()

        如果得到结果不在范围内,则拒绝访问

3.检查随机动态码nonce是否有效(如果不使用nonce作为参数,则跳过此步)

        (1)nonce为客户端随机生成的验证码,当服务器接收到请求后,会把nonce存储到数据库中,一般使用redis,并设置一个有效期,一般和时间戳timestamp的失效时间保持一致,设为10分钟有效期。         (2)当服务器接收到请求后,用请求中nonce(本文为7878)和redis中的nonce集合做比较,如果已经存在,则拒绝访问接口,只有当10分钟之内第一次使用7878这个动态码,才判定访问有效。

4.验证签名signature

        对除了signature之外的所有参数进行升序URL格式转换拼接,得到字符串再用密钥secretkey(本文是‘abcd’)加密,得到signature2,和请求中的signature做比较判断请求是否合法。         不一致则拒绝访问。

五、总结下生成签名认证请求参数生成的代码

import json # 导入json import urllib.parse # 导入URL编译库 import hmac # 导入HMAC加密库 accesskey="ch_improve" # 密钥,仅保存在客户端和服务端,不做网络传递,一般都有有效期,15天之类的 secretkey="abcd" # 准备参数 # 时间戳timestamp可以用 timestamp = int(time.time()) 生成 # nonce是随机生成的验证码,例如"7878", "k9i8e7" 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)] # 对排好序的元祖列表进行URL转换 args_str = urllib.parse.urlencode(args_order) # 得到字符串SignatureMethod=HmacSHA256&accesskey=ch_improve&city=zhengzhou&method=POST&name=gaofushuai&nonce=7878&timestamp=1572662767 # 对密钥和字符串进行byte转换 b_secretkey = secretkey.encode('utf-8') b_args_str = args_str.encode('utf-8') # 用密钥abcd进行加密 signature = hmac.new(b_secretkey, b_args_str, digestmod='sha256').hexdigest() # 得到签名3623d8070662392772d10952ea39c277518546006e6649f4ea358671e51e06f7 # 把签名添加到请求参数中作为新的请求参数 { "accesskey": "ch_improve", "name": "gaofushuai", "city": "zhengzhou", "SignatureMethod": "HmacSHA256", "timestamp": 1572662767, "method": "POST", "nonce": 7878"signature": "3623d8070662392772d10952ea39c277518546006e6649f4ea358671e51e06f7" }

最后希望大家给个赞吧

最新回复(0)