以腾讯上传用户在应用中的等级相关信息为例。接口详情在此【"http://wiki.open.qq.com/wiki/v3/user/get_info"】
在set_achievement这个接口中,接口参数包括openid, openkey, appid, pf, sig, user_attr, format
此接口最后的请求示例为
http://openapi.tencentyun.com/v3/user/set_achievement? openid=B624064BA065E01CB73F835017FE96FA& openkey=5F154D7D2751AEDC8527269006F290F70297B7E54667536C& appid=2& pf=qzone& format=json& user_attr=%7B%22level%22%3A10%7D& sig=9999b41ad0b688530bb1b21c5957391c
这里除了sig其他参数都只是经过了URL编码而已,所以本文的重点也在如何生成sig。
在写代码的过程中我们也可以思考下为什么需要生成签名~
Step 1. 构造源串
第1步:将请求的URI路径进行URL编码(URI不含host,URI示例:/v3/user/get_info)。请开发者关注: URL编码注意事项 ,否则容易导致后面签名不能通过验证。
第2步:将除“sig”外的所有参数按key进行字典升序排列。 注:除非OpenAPI文档中特别标注了某参数不参与签名,否则除sig外的所有参数都要参与签名。
第3步:将第2步中排序后的参数(key=value)用&拼接起来,并进行URL编码。请开发者关注: URL编码注意事项 ,否则容易导致后面签名不能通过验证。 第4步:将HTTP请求方式(GET或者POST)以及第1步和第3步中的字符串用&拼接起来。
Step 2. 构造密钥
得到密钥的方式:在应用的appkey末尾加上一个字节的“&”,即appkey&
Step 3. 生成签名值
1. 使用HMAC-SHA1加密算法,使用Step2中得到的密钥对Step1中得到的源串加密。 (注:一般程序语言中会内置HMAC-SHA1加密算法的函数,例如PHP5.1.2之后的版本可直接调用hash_hmac函数。)
2. 然后将加密后的字符串经过Base64编码。 (注:一般程序语言中会内置Base64编码函数,例如PHP中可直接调用 base64_encode() 函数。)
3. 得到的签名值结果如下:
FdJkiDYwMj5Aj1UG2RUPc83iokk=
以下是生成sig的解决方案:
def getSig(cfg): strFilter = ".-_" codeUrlAddr = urllib2.quote(cfg[‘urlAddr‘],strFilter) urlData2 = sorted(cfg[‘urlData‘].iteritems(), key=lambda d:d[0]) codeStr0 = "" for (value01,value02) in urlData2: if codeStr0: codeStr0 += "&" + str(value01) + ‘=‘ + str(value02) else: codeStr0 += str(value01) + ‘=‘ + str(value02) codeStr1 = urllib2.quote(codeStr0) codeConn = cfg[‘urlMethod‘] + ‘&‘ + codeUrlAddr + ‘&‘ + codeStr1 sig = hmac.new(cfg[‘appkey‘] + ‘&‘, codeConn, hashlib.sha1).digest().encode(‘base64‘).rstrip() return sig def test(): urlData = { ‘openid‘ : ‘12345‘, ‘openkey‘ : ‘12345‘, ‘pf‘ : ‘wanba_ts‘, ‘appid‘ : 12345, ‘format‘ : ‘json‘, ‘user_attr‘ : ‘{"level":%d}‘ % 1234 } urlcfg = { ‘urlAddr‘ : ‘/v3/user/set_achievement‘, ‘urlMethod‘ : ‘GET‘, ‘appkey‘ : ‘ABCDWFSFFG‘, ‘urlData‘ : urlData } urlData[‘sig‘] = getSig(urlcfg)
第一步:将请求的URI路径进行URL编码(URI不含host,URI示例:/v3/user/get_info)。
urlCfg中urlAddr是第一步中的请求的URI路径。
URL编码规则:
签名验证时,要求对字符串中除了“-”、“_”、“.”之外的所有非字母数字字符都替换成百分号(%)后跟两位十六进制数。
十六进制数中字母必须为大写。
strFilter = ".-_" #不需要替换的字符 codeUrlAddr = urllib2.quote(cfg[‘urlAddr‘],strFilter)# url编码
第二步:将除“sig”外的所有参数按key进行字典升序排列。
urlData中就是第二步要求的sig参数外的所有参数,采用sorted方式进行排序。
urlData2 = sorted(cfg[‘urlData‘].iteritems(), key=lambda d:d[0])
第三步:将第2步中排序后的参数(key=value)用&拼接起来,并进行URL编码。
codeStr0 = "" for (value01,value02) in urlData2: if codeStr0: codeStr0 += "&" + str(value01) + ‘=‘ + str(value02) else: codeStr0 += str(value01) + ‘=‘ + str(value02) codeStr1 = urllib2.quote(codeStr0)
当我看到urllib.urlencode可以用来直接生成&进行拼接的时候非常开心,但是后来发现它会对value进行url编码。这与我们的要求是不符合的。所以采用了上面这种诡异的方式。
第四步:将HTTP请求方式(GET或者POST)以及第1步和第3步中的字符串用&拼接起来。
codeConn = cfg[‘urlMethod‘] + ‘&‘ + codeUrlAddr + ‘&‘ + codeStr1
Step2和Step3
sig = hmac.new(cfg[‘appkey‘] + ‘&‘, codeConn, hashlib.sha1).digest().encode(‘base64‘).rstrip()
这样就得到了sig的值,文档看起来有些复杂,但是一步步做下来其实难度也不大。
接下来就是按照文档要求进行数据请求啦~
urlStr1 = urllib.urlencode(urlData) url = urlHost + qqUserInfos[‘getPlayzoneUserInfoUrl‘] + ‘?‘ + urlStr1 request = urllib2.Request(url) result = json.loads(urllib2.urlopen(request).read())
在这个过程中不知道有没有小伙伴好奇为什么我的urlData里面的user_attr要像下面这样写
‘user_attr‘ : ‘{"level":%d}‘ % 1234
其实刚开始我是直接把{"level:1234}这个字典赋值给user_attr的。但是在操作的过程中发现由于python会把双引号自动变成单引号,导致生成的签名一直有问题。所以采用json格式来解决这个问题。
你以为本文这就完了?当!然!不!是!还有刚开始提出的问题没回答捏~
sooooo...为什么要生成签名?
The answer is "为了确定数据来源正确合法"
双方如何根据签名确定数据来源正确,以腾讯为例:
1.腾讯给我们一个appkey用作密钥,我们按照腾讯约定的签名方式对sig以外的参数进行签名。
2.生成签名后,将用作签名的参数和签名一起传给腾讯服务器。
3.服务器将签名与我们的appid对应的appkey进行解密。
4.将传递过去的参数排序,然后与生成的参数进行对比。若数据一致则能保证数据真实。