笔记-接口验签
笔记-接口验签
1. 接口验签
1.1. 什么是接口验签
通过HTTP post,get方式请求开放的API接口有以下几个安全上的问题需要解决:
- 请求来源(身份)合法性
- 请求参数篡改识别
- 请求的唯一性
为了保证数据在通信时的安全性,一般会使用参数签名的方式来进行验证。
1.2. 接口验签实现方法
假设需要给某 [移动端(app)] 写 [后台接口(api)]:
客户端: 以下简称app
后台接口:以下简称api
- 不进行验证的通信方式
api查询接口:
app访问:http://api.test.com/getproducts?para1=value1........
如上所示,没有身份验证,任何人都可以访问。
- MD5参数签名的通信方式
第一步,给app分配对应的key,secret;
第二步,sign签名,调用api时需要对请求参数进行签名验证,方式如下:
按照请求参数名将所有请求参数排序得到keyvaluekeyvalue….样式字符串将secret加在参数字符串的头部后进行MD5加密,加密后的字符串需大写,即得到签名sign。
新api接口代码:
http://api.test.com/getproducts?key=app_key&sign=BCC7COIWJ982398FIJ2¶1=value......
在这个过程中secretkey是不会在网络中传输的,所以整个过程是安全的。
- 另一个问题是KEY参数复用,如果key是固定的,攻击者可以很简单的复用key
常见的解决办法是timestamp+nonce
nonce是唯一的随机字符串
实现
请求接口:http://api.test.com/test?name=hello&home=world&work=java
客户端
生成当前时间戳timestamp=now和唯一随机字符串nonce=random
按照请求参数名的字母升序排列非空请求参数(包含AccessKey)
stringA="AccessKey=access&home=world&name=hello&work=java×tamp=now&nonce=random";
拼接密钥SecretKey
stringSignTemp="AccessKey=access&home=world&name=hello&work=java×tamp=now&nonce=random&SecretKey=secret";
MD5并转换为大写
sign=MD5(stringSignTemp).toUpperCase();
最终请求
http://api.test.com/test?name=hello&home=world&work=java×tamp=now&nonce=nonce&sign=sign;
app调用:
http://api.test.com/getproducts?key=app_key&sign=BCC7C71CF93F9CDBDB88671B701D8A35×tamp=201603261407&参数1=value1&参数2=value2.......
如上,通过timestamp时间戳用来验证请求是否过期。这样就算被人拿走完整的请求链接也是无效的。
1.2.1. python接口签名实现案例
#!/usr/bin/env python#coding:utf-8
import hashlib
import json
import requests
#测试的域名
domain= "http://******/***/vip2.ldo?"
#get 传递的非sign参数
url_params = {
'type':'orderList',
'userId':'198049148',
'country':86,
'typeGroup':'',
'status':'',
'rows':'20',
'page':'1',
'businessId':'2',
}
sign ="sign=###################"
#删除空值的参数,以用来签名
for key in list(url_params.keys()):
if not url_params.get(key):
del url_params[key]
#按照升序排列,得到的是一个列表,列表的元素为元组
url_params1 = sorted(url_params.items(),key=lambda d:d[0], reverse=False)
values =[]
for li in url_params1:
newsmbol =('=',)
#元组中增加一个新元素
li = li[:1]+newsmbol+li[1:]
#元组转化为字符串,整型不能转化,list包含数字,不能直接转化成字符串
value = "".join('%s' %id for id in li)
values.append(value)
#列表复制不能用= 需要用copy 或者list[:]
values1 = values[:]
values1.append(sign)
sign1 = "&".join(values1)
#md5 调用库函数
sign2 = hashlib.md5(sign1.encode('utf-8')).hexdigest()
sign = 'sign='+sign2
values.append(sign)
para = "&".join(values)
url = domain+ para
print(url)
res = requests.get(url)
print('***---***---***')
print(res.content)
print('***---***---***')
print(res.headers)
print('***---***---***')
print(res.status_code)
if res.status_code == 200:
print('请求成功')
print('***---***---***')
#json 格式打印
print(json.dumps(res.json(),indent=4))
print('***---***---***')
#两种方法
if json.loads(res.text)['msg']=='success':
print('True')
else:
print('error')
if res.json()['msg']=='success':
print('True')
else:
print('error')
1.3. token
在爬取网页时经常遇到token,token实质上就是服务端给出的一个唯一性标识,其使用过程如下:
- 第一次访问时会给出一个标识token;
- 后续的每一次访问需要带上token,
- 服务端验证token,如果通过则可以访问,且每一次访问都会更新值。
token的主要作用是可以防止重复请求,同时有一定身份验证的作用。
当然,token具体放在cookie还是使用ajax则是工程实现的问题了。
1.4. 总结
身份验证的原理都是相似的,向服务器发送一个(多个)唯一性字符串,
为了解决网络传输中的监听和篡改问题,使用约定的加密算法使字符串的信息难以解读,同时防篡改。
理论上可以用用户名+密码,但为了保证安全,用户名及密码应该尽量少在网络上传输,所以实践中多在登陆后由服务端给出一个特定字符串作为标识(或作为标识的一部分)。