在接口请求中会有一个Auth字段, Authorization Header ,用来进行安全校验
def user_auth(request):
get_http_auth = request.META.get('HTTP_AUTHORIZATION', b'')
auth = get_http_auth.split()
try:
auth_parts = base64.b64decode(auth[1]).decode('iso-8859-1').partition(':')
except IndexError:
return "null"
userid, password = auth_parts[0], auth_parts[2]
user = django_auth.authenticate(username=userid, password=password)
if user is not None and user.is_active:
django_auth.login(request, user)
return "success"
else:
return "fail"
from django.contrib import auth as django_auth
import hashlib
import base64
# 用户认证
def user_auth(request):
#request.META是python的一个字典,包含传入request的所有HTTP请求信息
#HTTP_AUTHORIZATION 用于获取HTTP authorization的参数
# 得到的数据是这样的:Basic YWRtaW46YWRtaW4xMjM0NTY=
get_http_auth = request.META.get('HTTP_AUTHORIZATION', b'')
#通过split拆分成对应的list
#拆分后的数据是这样的:['Basic', 'YWRtaW46YWRtaW4xMjM0NTY=']
auth = get_http_auth.split()
try:
#通过base64对加密串进行解码,通过partion增加:区分admin和密码
# 得到的数据是:('admin', ':', 'admin123456')
auth_parts = base64.b64decode(auth[1]).decode('iso-8859-1').partition(':')
except IndexError:
return "null"
#获取用户名密码
userid, password = auth_parts[0], auth_parts[2]
#Django的账户认证
user = django_auth.authenticate(username=userid, password=password)
if user is not None and user.is_active:
django_auth.login(request, user)
return "success"
else:
return "fail"
# 在发布会查询接口-中增加用户认证
def get_event_list(request):
# 调用认证函数
auth_result = user_auth(request)
if auth_result == "null":
return JsonResponse({'status':10011,'message':'user auth null'})
if auth_result == "fail":
return JsonResponse({'status':10012,'message':'user auth fail'})
#原先查询逻辑
eid = request.GET.get("eid", "") # 发布会id
name = request.GET.get("name", "") # 发布会名称
# 增加用户认证后
path('sec_get_event_list/', views_if_sec.get_event_list, name='get_event_list'),
这种认证方式是一种相对较弱的认证方式,安全性较低。
更改类用户认证时候,需要修改接口文档。
#python manage.py test blog.tests.GetEventListTest
class GetEventListTest(unittest.TestCase):
''' 查询发布会信息(带用户认证)'''
def setUp(self):
self.base_url = "http://127.0.0.1:8000/api/sec_get_event_list/"
self.auth_user = ('admin', 'admin123456')
def test_get_event_list_auth_null(self):
''' auth 为空'''
r = requests.get(self.base_url, params={'eid': ''})
result = r.json()
self.assertEqual(result['status'], 10011)
self.assertEqual(result['message'], 'user auth null')
def test_get_event_list_auth_error(self):
''' auth 错误'''
r = requests.get(self.base_url, auth=('abc', '123'), params={'eid': ''})
result = r.json()
self.assertEqual(result['status'], 10012)
self.assertEqual(result['message'], 'user auth fail')
def test_get_event_list_eid_null(self):
''' eid 参数为空'''
r = requests.get(self.base_url, auth=self.auth_user, params={'eid': ''})
result = r.json()
self.assertEqual(result['status'], 10021)
self.assertEqual(result['message'], 'parameter error')
def test_get_event_list_eid_success(self):
''' 根据eid 查询结果成功'''
r = requests.get(self.base_url, auth=self.auth_user, params={'eid': 1})
result = r.json()
self.assertEqual(result['status'], 200)
self.assertEqual(result['message'], 'success')
self.assertEqual(result['data']['name'], u'mx6 发布会')
self.assertEqual(result['data']['address'], u'北京国家会议中心')
数字签名是HTTP/SOAP协议传输数据的时候,用来鉴权的一种方式。鉴权,也就是客户的密钥,需要和服务端的密钥匹配。
import hashlib
md5 = hashlib.md5()
sign_str = "@admin123"
sign_bytes_utf8 = sign_str.encode(encoding="utf-8")
md5.update(sign_bytes_utf8)
sign_md5 = md5.hexdigest()
print(sign_md5)
# 4b9db269c5f978e1264480b0a7619eea
#加密前
http://127.0.0.1:8000/sign/?a=1&b=2&sign=@admin123
#加密后
http://127.0.0.1:8000/sign/?a=1&b=2&sign=4b9db269c5f978e1264480b0a7619eea
因为MD5加密是不可逆的算法,与传统的加密解密相比,这个只需要核对数据的一致性。
所以服务器收到请求后,同样需要把数据库中已有的密码进行md5加密,然后比较加密后的结果和请求传递来的加密串"a=1&b=2&api_key=@admin123",做个方式
另一种是用md5加密的方式,是扩大加密的密钥,例如并不只是把"@admin123"进行加密,更多是所有参数都加密。
import time
import hashlib
# 使用用户签名+时间戳的方式来做鉴权
def user_sign(request):
#获取time和sign参数
client_time = request.POST.get('time', '')
client_sign = request.POST.get('sign', '')
if client_time == '' or client_sign == '':
return "sign null"
'''判断时间戳'''
# 获取服务器的当前时间
now_time = time.time()
#去除精度限制
server_time = str(now_time).split('.')[0]
# 获取时间差,时间差太多抛出延时,这就要求客户端需要获取最新的时间来访问服务端
time_difference = int(server_time) - int(client_time)
if time_difference >= 60:
return "timeout"
'''签名检查'''
# 密钥:api_key;明文:&Guest-Bugmaster
md5 = hashlib.md5()
# 将密钥和client_time时间进行整合么然后md5加密,获取加密结果sever_sign
sign_str = client_time + "&Guest-Bugmaster"
sign_bytes_utf8 = sign_str.encode(encoding="utf-8")
md5.update(sign_bytes_utf8)
sever_sign = md5.hexdigest()
#将服务端加密之后的结果sever_sign和客户端传的lient_sign比较
if sever_sign != client_sign:
return "sign error"
else:
return "sign right"
def add_event(request):
'''签名校验'''
# 获取签名校验结果
sign_result = user_sign(request)
#签名校验处理结果
if sign_result == "sign null":
return JsonResponse({'status':10011,'message':'user sign null'})
elif sign_result == "timeout":
return JsonResponse({'status':10012,'message':'user sign timeout'})
elif sign_result == "sign error":
return JsonResponse({'status':10013,'message':'user sign error'})
#增加 MD5签名校验
path(r'^sec_add_event/', views_if_security.add_event, name='add_event'),
针对我们刚刚写的这个时间戳+MD5加密方法
#python manage.py test blog.tests.AddEventTest
class AddEventTest(unittest.TestCase):
'''添加发布会(时间戳MD5校验)
通过修改payload参数'''
def setUp(self):
self.base_url = "http://127.0.0.1:8000/api/sec_add_event/"
self.api_key = "&Guest-Bugmaster"
# 获取当前时间
now_time = time.time()
self.client_time = str(now_time).split('.')[0]
# 获取服务端的sign
md5 = hashlib.md5()
sign_str = self.client_time + self.api_key
#把sign进行md5转换
sign_bytes_utf8 = sign_str.encode(encoding="utf-8")
md5.update(sign_bytes_utf8)
self.sign_md5 = md5.hexdigest()
def test_add_event_sign_null(self):
''' 签名参数为空'''
payload = {'eid': 1, '': '', 'limit': '', 'address': '', 'start_time': '', 'time': '', 'sign': ''}
r = requests.post(self.base_url, data=payload)
result = r.json()
self.assertEqual(result['status'], 10011)
self.assertEqual(result['message'], 'user sign null')
def test_add_event_time_out(self):
''' 请求超时'''
now_time = str(int(self.client_time) - 61)
payload = {'eid': 1, '': '', 'limit': '', 'address': '', 'start_time': '', 'time': now_time, 'sign': 'abc'}
r = requests.post(self.base_url, data=payload)
result = r.json()
self.assertEqual(result['status'], 10012)
self.assertEqual(result['message'], 'user sign timeout')
def test_add_event_sign_error(self):
''' 签名错误'''
payload = {'eid': 1, '': '', 'limit': '', 'address': '', 'start_time': '', 'time': self.client_time, 'sign': 'abc'}
r = requests.post(self.base_url, data=payload)
result = r.json()
self.assertEqual(result['status'], 10013)
self.assertEqual(result['message'], 'user sign error')
def test_add_event_success(self):
''' 添加成功'''
payload = {'eid': 11, 'name': '一加4 手机发布会', 'limit': 2000, 'address': "深圳宝体", 'start_time': '2017-05-10 12:00:00','time': self.client_time, 'sign': self.sign_md5}
r = requests.post(self.base_url, data=payload)
result = r.json()
self.assertEqual(result['status'], 200)
self.assertEqual(result['message'], 'add event success')
这里也就是针对MD5加密原则,在测试用例里面增加对时间戳的校验
Django 全程是跟着虫师的web接口开发书学习的,不得不说书不错,感觉可以学到东西的。