在 HTTP 的 API 请求,作为后台服务需要知道请求者是谁,当然对 Django 项目有好多现成的解决方案 DRF token 和 JWT token 等,现在咱们说一种土方法。
class Token(object):
def __init__(self, user, passwd):
self.user = user
self.passwd = passwd
def get_token(self):
# 1. 用随机字符串和时间戳来区分请求和保证安全
self.rdm_str = ''.join(random.sample('abcdefghij123456789', 5))
self.time = int(time.time())
# 2. 加上用户的 passwd 构成一个数组,且按照字母表顺序排序
token_str_list = [self.passwd, self.rdm_str, str(self.time)]
token_str_list.sort()
token_str = ''.join(token_str_list)
# 3. 生成 hash 值,注意 hash 值中隐含 用户密码 的信息,并没有原生的体现密码
m1 = md5.new()
m1.update(token_str)
self.token = m1.hexdigest()
return self.token
发送请求:
# USER_NAME 和 PASSWORD 是由服务端分发的
# 客户端:把 token 和 生产token的原始数据(中的随机字符串 和 时间戳)放到请求的 URL 上 ,用户名放到请求体中
# 服务端:收到请求后,从 URL 中解析出 token 和 随机字符串 和 时间戳 从请求体中获得用户名
# 然后查询用户密码表得到该用户对应的密码
# 最后利用 get_token 方法重新计算下 token,从而和请求 URL 中携带的 token 比较
# 关键在于:服务端和客户端计算token的方法必须一样,且用户名和密码由服务端分发
# 优化点:可以把用户名 和 随机字符串 和 时间戳 和 token 都放到请求头中,这样可能更符合 restful 规范
token_obj = Token(USER_NAME, PASSWROD)
req_url = "http://192.168.0.1/getFlowData/?reqTime=%s&reqToken=%s&reqRandom=%s" % \
(token_obj.time, token_obj.get_token(), token_obj.rdm_str)
post_data = {'username': USERNAME, 'flowNo': self.flow_num}
url_encode = urllib.urlencode(post_data)
req = urllib2.Request(url=req_url, data=url_encode)
res_data = urllib2.urlopen(req)
res = res_data.read()
data_set = json.loads(res)
改进版:替换上面的用户名和密码,可以用 app_name 和 该 app 对应的盐值,同时把生成 token 的原始数据和 token 信息放到请求头里而不是放到请求的 URL 里面。而计算 token 的方法就变成了:
# parms_dict 包括:当前时间,随机字符串,app_name,盐值,注意param_dict是 key 按照字母表顺序排序后的结果,
# 如果不排序的话,客户端和服务端计算出来的值可能就不一样了,因为字典是“键-值对”的无序可变序列
some_param = ["{0}={1}".format(k, v) for k, v in param_dict.iteritems()]
# headers 包括:当前时间,随机字符串,app_name 和根据 parms_dict 计算得到的 token
# 服务端会根据请求头中的 app_name 查库得到盐值,
# 然后按照同样的算法计算 token,然后和请求头里的 token 比对
# 所以要保证 parms_dict 和 headers 中的 当前时间,随机字符串,app_name 的值是一样的
headers['token'] = hashlib.sha1("&".join(some_param).encode()).hexdigest()