【Django】Day07-接口安全機制

接口安全機制

一. 用戶認證

在接口請求中會有一個Auth字段, Authorization Header ,用來進行安全校驗

1.用戶認證接口

  • blog/view_if_sec.py
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"
  • blog/view_if_sec.py 增加註釋
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"

2.新增發佈會查詢接口

  • blog/view_if_sec.py
# 在發佈會查詢接口-中增加用戶認證
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", "") # 發佈會名稱

3.配置安全接口指向

  • blog/urls.py
    # 增加用戶認證後
    path('sec_get_event_list/', views_if_sec.get_event_list, name='get_event_list'),

這種認證方式是一種相對較弱的認證方式,安全性較低。

4.接口文檔+測試用例

更改類用戶認證時候,需要修改接口文檔。

  • 測試用例
#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'北京國家會議中心')

二.數字簽名

1.鑑權

數字簽名是HTTP/SOAP協議傳輸數據的時候,用來鑑權的一種方式。鑑權,也就是客戶的密鑰,需要和服務端的密鑰匹配。

  • md5 加密
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",做個方式

2.數據防篡改

另一種是用md5加密的方式,是擴大加密的密鑰,例如並不只是把"@admin123"進行加密,更多是所有參數都加密。

  • 密鑰:a=1&b=2&api_key=@admin123

3.具體實現

  • blog/views_if_security.py
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'),

4.MD5測試用例

針對我們剛剛寫的這個時間戳+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接口開發書學習的,不得不說書不錯,感覺可以學到東西的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章