Django測試開發學習筆記(二)

用戶認證

1. cookie&session

  • cookie:因為http請求是無狀態的,第一次和服務器連接后并且登錄成功后,返回一些數據(cookie)給瀏覽器,然后瀏覽器保存在本地,當該用戶發送第二次請求的時候,就會自動的把上次請求存儲的cookie數據自動的攜帶給服務器。
    服務拿到cookie字符串就會進行解析為session={key:value}中的key,對應了找到這個用戶是否登錄is_login、用戶名username等一些信息。
image
  • 設置cookie

    • 新建一個應用來演示cookie相關的操作

    • views.py代碼

      class Login(View):
          def get(self,request):
              # 設置cookie,cookie是在響應中返回的,所以要對響應對象去設置cookie
              response=HttpResponse()
              # set_cookie參數
              # key:這個cookie的key,一般用JSessionID
              # value:這個cookie的value
              # max_age:最長的生命周期
              # expires:過期時間
              response.set_cookie("JSessionID","asdshgfkldsg999",max_age=120)
              response.content="請求成功"
              return response
      
    • 使用Jmeter調用該接口

      image
    • 新建一個HTTP Cookie管理器

  • 獲取cookie

    • views.py

      def post(self,request):
          # 獲取cookie
          print(request.COOKIES)
          return HttpResponse("獲取cookie")
      
    • 使用Jmeter調用該接口

      image
  • 存儲session

    • views.py

      def put(self,request):
          # 存儲cookie
          cookie = request.COOKIES
          # session的key是用戶名,value是cookie的值
          request.session["tim"] = cookie["JSessionID"]
          return HttpResponse("存儲cookie成功")
      
    • 使用Jmeter調用該接口


      image
    • 查看數據庫表Django_session,新增了session信息

      image

2. 修改session的存儲機制

默認情況下,session數據是存儲到數據庫中的。當然也可以將session數據存儲到其他地方。可以通過設置SESSION_ENGINE來更改session的存儲位置,這個可以配置為以下幾種方案:

  • django.contrib.sessions.backends.db:使用數據庫。默認就是這種方案。
  • django.contrib.sessions.backends.file:使用文件來存儲session。
  • django.contrib.sessions.backends.cache:使用緩存來存儲session。想要將數據存儲到緩存中,前提是你必須要在settings.py中配置好CACHES,并且是需要使用Memcached,而不能使用純內存作為緩存。
  • django.contrib.sessions.backends.cached_db:在存儲數據的時候,會將數據先存到緩存中,再存到數據庫中。這樣就可以保證萬一緩存系統出現問題,session數據也不會丟失。在獲取數據的時候,會先從緩存中獲取,如果緩存中沒有,那么就會從數據庫中獲取。
  • django.contrib.sessions.backends.signed_cookies:將session信息加密后存儲到瀏覽器的cookie中。這種方式要注意安全,建議設置SESSION_COOKIE_HTTPONLY=True,那么在瀏覽器中不能通過js來操作session數據,并且還需要對settings.py中的SECRET_KEY進行保密,因為一旦別人知道這個SECRET_KEY,那么就可以進行解密。另外還有就是在cookie中,存儲的數據不能超過4k。

3. 項目應用

使用django自帶的認證、登錄、退出賬號的方法,記得自己配置下urls.py,不再展示

  • 先創建django自帶鑒權相關表

    python manage.py makemigrations
    python manage.py migrate
    

    數據庫會生成對應的用戶權限相關表和session的表

    image
  • 禁掉CSRF中間件

    在django處理請求的過程中,需要經過中間件的過濾,涉及到跨站請求偽造時,django會把請求阻止過濾掉,所以我們要在setting.py中禁用跨站請求偽造的中間件,如果不禁用,好像會報一個403的錯誤。

    image
  • 配置settings.py

    SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 配置session引擎,表示你想把session存到什么地方(session可以存在數據庫、緩存、文件里)
    SESSION_COOKIE_NAME = 'sessionid' # Session的cookie保存在瀏覽器上時的key,即:sessionid=隨機字符串
    SESSION_COOKIE_PATH = "/" # Session的cookie保存的路徑(默認)
    SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名
    SESSION_COOKIE_SECURE = False # 是否Https傳輸cookie
    SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http傳輸
    SESSION_COOKIE_AGE = 60*30 # Session的cookie失效日期(30分鐘),不要寫成1800,這樣不易讀,比如表示8小時,寫為60*60*8
    SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否關閉瀏覽器使得Session過期
    SESSION_SAVE_EVERY_REQUEST = False # 是否每次請求都保存Session,默認修改之后才保存
    
    image
  • 認證、登錄、退出賬號的代碼

    from django.contrib.auth import authenticate, login, logout
    from django.contrib.auth.decorators import login_required
    from django.contrib.auth.models import User
    from django.http import HttpResponse, JsonResponse
    import json
    
    # 使用django自帶的認證、登錄、登出方法
    
    def signup(request):
        '''用戶注冊方法'''
        data = json.loads(request.body)
        # django的創建用戶的方法,request傳入username、password、email是必填的
        user = User.objects.create_user(username=data['username'], password=data['password'],
                                        email=data['email'])  # 使用User模塊的create_user方法創建用戶
        user.save()  # 保存
        # 返回一個響應信息
        info = {
            "code": '0000',
            'msg': "用戶注冊成功",
            'data': {
                "id": user.id,
                "username": user.username,
                "password": user.password
            }
        }
        return JsonResponse(info)  # 使用JsonResponse返回一個json字符串
    
    def user_login(request):
        '''
        用戶登錄方法
        :param request:
        :return:
        '''
        if request.method == 'GET':
            return HttpResponse("腦補一個登錄頁面")
        data = json.loads(request.body)
        user = authenticate(username=data["username"], password=data["password"])  # 返回一個user對象
        info = {
            "code": None,
            'msg': None
        }
        if user is not None:
            if user.is_active:  # is_active字段表示此條用戶名和信息是否有效
                login(request, user)  # 調用django.contrib.auth模塊的login
                info["code"] = "0000"
                info["msg"] = "登錄成功"
            else:
                info["code"] = "9999"
                info["msg"] = "該賬戶不可用"
        else:
        info["code"] = "9999"
        info["msg"] = "用戶名密碼不正確"
    return JsonResponse(info)
    
    def user_logout(request):
        '''退出登錄'''
        logout(request)  # 調用django.contrib.auth模塊的logout方法
        info = {
            "code": "0000",
            'msg': "退出登錄成功"
        }
        return JsonResponse(info)
    
    
    def test(request):
        return HttpResponse("腦補一個登錄頁面")
    
    
    @login_required(login_url='/login_demo/test')  # 此裝飾器會校驗會在訪問該視圖之前,校驗session,若通過則繼續,不通過則重定向到login_url指定的視圖中去
    def test_user(request):
        '''
        測試登錄
        :param request:
        :return:
        '''
        return HttpResponse('ok')
    
    • 使用Jmeter調用注冊接口

      image

      image
    • 數據庫auth_user表新增了一條用戶信息

      image

      可見,用戶密碼是加密的。需要通過django的authenticate方法來驗證用戶名、密碼是否正確。

    • 使用Jmeter調用登錄接口

      image
      image

      接口響應頭返回一個sessionid:

      image

      數據庫表django_session中,新增一條記錄,可以看出返回的sessionid就是這條記錄的session_key

      image
    • 哪些視圖需要用戶認證呢?

      使用裝飾器@login_required(login_url='/login_demo/test'),此裝飾器會校驗會在訪問該視圖之前,校驗session,若通過則繼續,不通過則重定向到login_url指定的視圖中去

      session過期后,調用testuser,重定向到test


      image
      image

      登錄之后,調用testuser


      image

      ps: 在每個視圖上加一個裝飾器其實很麻煩,此處只是了解原理,后面學習drf框架時,會使用新的方式進行用戶認證。

  • 擴展內容
    • 用戶組管理
    • 權限管理

緩存

(暫時不深入講,會在drf部分講我們開發所需要的一點知識)

中間件

1. 簡介

在Django中,中間件(middleware)其實就是一個類,在請求到來和結束后,Django會根據自己的規則在合適的時機執行中間件中相應的方法。

  • 1.執行完所有的request方法到達執行流程;
  • 2.執行中間件的其他方法;
  • 3.經過所有response方法,返回客戶端;

在一個項目中,如果想對全局所有視圖函數或視圖類起作用時,就可以在中間件中實現,比如想實現用戶登錄判斷,基于用戶的權限管理(RBAC)等都可以在Django中間件中來進行操作。

在settings.py文件中,注冊該中間件(Django項目中的settings模塊中,有一個MIDDLEWARE_CLASSES變量,其中每個元素都是一個中間件)

MIDDLEWARE_CLASSES = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

  • 引子

    中間件是 Django用來處理請求和響應的鉤子框架。它是一個輕量級的、底層級的“插件”系統,用于全局性地控制Django 的輸入或輸出,可以理解為內置的app或者小框架。

    在django.core.handlers.base模塊中定義了如何接入中間件,這也是學習Django源碼的入口之一。

    每個中間件組件負責實現一些特定的功能。例如,Django 包含一個中間件組件 AuthenticationMiddleware,它使用會話機制將用戶與請求request關聯起來。

    中間件可以放在你的工程的任何地方,并以Python路徑的方式進行訪問。

    Django 具有一些內置的中間件,并自動開啟了其中的一部分,我們可以根據自己的需要進行調整。

  • 如何啟用中間件

    若要啟用中間件組件,請將其添加到 Django 配置文件settings.py的 MIDDLEWARE 配置項列表中。

    在 MIDDLEWARE 中,中間件由字符串表示。這個字符串以圓點分隔,指向中間件工廠的類或函數名的完整 Python 路徑。下面是使用 django-admin startproject命令創建工程后,默認的中間件配置:

    MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    ]
    

    實際上在Django中可以不使用任何中間件,如果你愿意的話,MIDDLEWARE 配置項可以為空。但是強烈建議至少使用 CommonMiddleware。建議是保持默認的配置,這有助于你提高網站的安全性。

  • 中間件最關鍵的順序問題

    MIDDLEWARE的順序很重要,具有先后關系,因為有些中間件會依賴其他中間件。例如: AuthenticationMiddleware 需要在會話中間件中存儲的經過身份驗證的用戶信息,因此它必須在 SessionMiddleware后面運行 。

    在請求階段,調用視圖之前,Django 按照定義的順序執行中間件 MIDDLEWARE,自頂向下。

    如果某個層的執行過程認為當前的請求應該被拒絕,或者發生了某些錯誤,導致短路,直接返回了一個響應,那么剩下的中間件以及核心的視圖函數都不會被執行。

  • Django內置的中間件

    Django內置了下面這些中間件,滿足了我們一般的需求:

    • Cache

      緩存中間件

      如果啟用了該中間件,Django會以CACHE_MIDDLEWARE_SECONDS 配置的參數進行全站級別的緩存。

    • Common

      通用中間件

2. 中間件方法

傳統方式自定義中間件其實就是在編寫五大鉤子函數:

process_request(self,request)
process_response(self, request, response)
process_view(self, request, view_func, view_args, view_kwargs)
process_exception(self, request, exception)
process_template_response(self,request,response)

可以實現其中的任意一個或多個

image
image
image
image
image

3. 自定義中間件

有時候,為了實現一些特定的需求,我們可能需要編寫自己的中間件。

在編寫方式上,需要注意的是,當前Django版本2.2,存在兩種編寫的方式。一種是Django當前官網上提供的例子,一種是老版本的方式。本質上,兩種方式其實是一樣的。

我們先看一下傳統的,也是技術文章最多,目前使用最多的方式。

Django 提供的 get_response 方法可能是一個實際視圖(如果當前中間件是最后列出的中間件),或者是列表中的下一個中間件。我們不需要知道或關心它到底是什么,它只是代表了下一步要進行的操作。

兩個注意事項:

  1. Django僅使用 get_response 參數初始化中間件,因此不能為 init() 添加其他參數。
  2. 與每次請求都會調用 call() 方法不同,當 Web 服務器啟動后,init() 只被調用一次。
  • 新建一個應用來演示中間件

    python manage.py startapp middle_demo
    

    (自行配置urls.py)

  • 應用下新建一個middleware.py

    class SimpleMiddleware1: # 第一個中間件示例
        # 固定寫法, 必須有
        def __init__(self, get_response):
            self.get_response = get_response
             # 配置和初始化
    
        # call方法中定義一些中間件的操作
        def __call__(self, request):
    
            # 在這里編寫視圖和后面的中間件被調用之前需要執行的代碼
            # 這里其實就是舊的process_request()方法的代碼
    
            print("SimpleMiddleware1的process_request()方法被調用")
            response = self.get_response(request)
    
            # 在這里編寫視圖調用后需要執行的代碼
            # 這里其實就是舊的process_response()方法的代碼
            print("SimpleMiddleware1的process_response()方法被調用")
    
            return response
    
        def process_view(self, request, view_func, view_args, view_kwargs):
            print("SimpleMiddleware1的process_view()方法被調用")
    
        # 出現異常時,會執行這個代碼
        def process_exception(self,request,exception):
            print("SimpleMiddleware1的process_exception()方法被調用")
    
        def process_template_response(self,request,response):
            # 默認不執行這個函數,除非views函數中返回的實例對象(注意這里這個詞)中有render()方法
            print("SimpleMiddleware1的process_template_response()方法被調用")
            return response
    
    class SimpleMiddleware2: # 第二個中間件示例
        # 固定寫法, 必須有
        def __init__(self, get_response):
            self.get_response = get_response
             # 配置和初始化
    
        # call方法中定義一些中間件的操作
        def __call__(self, request):
    
            # 在這里編寫視圖和后面的中間件被調用之前需要執行的代碼
            # 這里其實就是舊的process_request()方法的代碼
    
            print("SimpleMiddleware2的process_request()方法被調用")
            response = self.get_response(request)
    
            # 在這里編寫視圖調用后需要執行的代碼
            # 這里其實就是舊的process_response()方法的代碼
            print("SimpleMiddleware2的process_response()方法被調用")
    
            return response
    
        def process_view(self, request, view_func, view_args, view_kwargs):
            print("SimpleMiddleware2的process_view()方法被調用")
    
        # 出現異常時,會執行這個代碼
        def process_exception(self,request,exception):
            print("SimpleMiddleware2的process_exception()方法被調用")
    
        def process_template_response(self,request,response):
            # 默認不執行這個函數,除非views函數中返回的實例對象(注意這里這個詞)中有render()方法
            print("SimpleMiddleware2的process_template_response()方法被調用")
            return response
    
    
  • 配置settings.py

    MIDDLEWARE = [
        'middle_demo.middleware.SimpleMiddleware1',
        'django.middleware.security.SecurityMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        # 'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
        'middle_demo.middleware.SimpleMiddleware2',
    ]
    

    可以調用我們之前配置好的登錄接口,在控制臺上看到中間件的順序:

    image

4. 中間件應用

  • 應用實例一:IP攔截
    如果我們想限制某些IP對服務器的訪問,可以在settings.py中添加一個BLACKLIST(全大寫)列表,將被限制的IP地址寫入其中。

    然后,我們就可以編寫下面的中間件了:

    from django.http import HttpResponseForbidden
    from django.conf import settings
    
    class BlackListMiddleware():
    
        def __init__(self, get_response):
            self.get_response = get_response
    
        def __call__(self, request):
    
            if request.META['REMOTE_ADDR'] in getattr(settings, "BLACKLIST", []):
                return HttpResponseForbidden('<h1>該IP地址被限制訪問!</h1>')
    
            response = self.get_response(request)
    
            return response
    
  • 應用實例二:DEBUG頁面

    網站上線正式運行后,我們會將DEBUG改為 False,這樣更安全。但是發生服務器5xx系列錯誤時,管理員卻不能看到錯誤詳情,調試很不方便。有沒有辦法比較方便地解決這個問題呢?

    • 普通訪問者看到的是500錯誤頁面
    • 管理員看到的是錯誤詳情Debug頁面

    利用中間件就可以做到!代碼如下:

    import sys
    from django.views.debug import technical_500_response
    from django.conf import settings
    
    class DebugMiddleware():
    
        def __init__(self, get_response):
            self.get_response = get_response
    
        def __call__(self, request):
    
            response = self.get_response(request)
    
            return response
    
        def process_exception(self, request, exception):
            # 如果是管理員,則返回一個特殊的響應對象,也就是Debug頁面
            # 如果是普通用戶,則返回None,交給默認的流程處理
            if request.user.is_superuser or request.META.get('REMOTE_ADDR') in settings.ADMIN_IP:
                return technical_500_response(request, *sys.exc_info())
    

    這里通過if判斷,當前登錄的用戶是否超級管理員,或者當前用戶的IP地址是否在管理員IP地址列表中。符合兩者之一,即判斷當前用戶有權限查看Debug頁面。

    接下來注冊中間件,然后在測試視圖中添加一行raise。再修改settings.py,將Debug設為False,提供ALLOWED_HOSTS = ["*"],設置比如ADMIN_IP = ['192.168.0.100'],然后啟動服務器0.0.0.0:8000,從不同的局域網IP來測試這個中間件。

    正常情況下,管理員應該看到類似下面的Debug頁面:

    RuntimeError at /midtest/
    No active exception to reraise
    Request Method: GET
    Request URL:    http://192.168.0.100:8000/midtest/
    Django Version: 2.0.7
    Exception Type: RuntimeError
    Exception Value:
    No active exception to reraise
    .....
    

    而普通用戶只能看到:

    A server error occurred.  Please contact the administrator.
    
安裝redis

安裝參考:http://www.lxweimin.com/p/bb7c19c5fc47

pycharm集成化插件Iedis

安裝參考:https://blog.csdn.net/babados/article/details/78575145

因在mac上是付費的(不知道win要不要付費),故沒有使用。

自己使用了叫做Redis桌面管理工具的軟件:http://www.pc6.com/mac/486661.html

redis配置和測試
  • 安裝django-redis

    pip install django-redis==4.11.0
    
  • 配置settings.py

    添加:

    CACHES = { # 配置緩存數據為redis
    "default": {
        "BACKEND": "django_redis.cache.RedisCache", 
        "LOCATION": "redis://127.0.0.1:6379",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
            "PASSWORD":"12345" #redis密碼,如果沒設密碼可以不用配置
            }
        }
    }
    
  • 測試redis安裝是否成功

    # 終端輸入
    
    # 啟動redis 服務端
    redis-server
    
    # 啟動redis客戶端
    redis-cli
    
    from django_redis import get_redis_connection
    
    image
  • 獲取緩存庫鏈接

    cache = get_redis_connection("default") # default是settings.py中配置的項
    
    image
  • 添加緩存數據

    cache.set("abc","aaaa1234",30*60)
    
    image

    在redis客戶端可以查看

    image
token
  • token簡介

    在實現登錄功能的時候,正常的B/S應用都會使用cookie+session的方式來做身份驗證,后臺直接向cookie中寫數據,但是由于移動端的存在,移動端是沒有cookie機制的,所以使用token可以實現移動端和客戶端的token通信。

    • 驗證流程

      整個基于Token的驗證流程如下:

      • 客戶端使用用戶名跟密碼請求登錄
      • 服務器收到請求,去驗證用戶名和密碼
      • 驗證成功后,服務端會簽發一個Token,再把這個Token發送到客戶端
        客戶端收到的Token以后可以把它存儲起來,比如放在Cookie或LocalStorage里
      • 客戶端每次向服務器發送其他請求的時候都要帶著服務器簽發的Token
      • 服務器收到請求,去驗證客戶端請求里面帶著的Token,如果驗證成功,就像客戶端返回請求的數據
  • JWT標準

    構造Token的方法挺多的,可以說只要是客戶端和服務器端約定好了格式,是想怎么寫就怎么寫的,然而還有一些標準寫法,例如JWT讀作/jot/,表示:JSON Web Tokens。

    JWT標準的Token有三個部分:

    • header
    • payload
    • signature

    三個部分會用點分割開,并且都會使用Base64編碼,所以真正的Token看起來像這樣:

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc
    
    • Header

      header部分主要是兩部分內容,一個是Token的類型,另一個是使用的算法,比如下面的類型就是JWT,使用的算法是HS256:

      {
        "typ": "JWT",
        "alg": "HS256"
      }
      

      上面的內容要用 Base64 的形式編碼一下,所以就變成這樣:

      eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
      
    • Payload
      Payload 里面是 Token 的具體內容,這些內容里面有一些是標準字段,你也可以添加其它需要的內容。下面是標準字段:

      • iss:Issuer,發行者
      • sub:Subject,主題
      • aud:Audience,觀眾
      • exp:Expiration time,過期時間
      • nbf:Not before
      • iat:Issued at,發行時間
      • jti:JWT ID
    • Signature

      JWT的最后一部分是Signature,這部分相當于前兩段的摘要,用來防止其他人來篡改Token中的信息,在處理時可以首先將前兩段生成的內容使用Base64生成一下再加鹽然后利用MD5等摘要算法在生成一遍。

  • token生成

    • 服務端生成Token

      在服務端生成Token的時候,需要解決兩個問題

      • 使用什么加密算法
      • Token如何存儲
    • 加密算法

      這里的加密算法并不是MD5,SHA1這樣的哈希算法,因為這種算法是無法解密的,只能用來生成摘要,在Django中內置了一個加密前面模塊django.core.signing模塊,可以用來加密和解密任何數據,使用簽名模塊的dumps和load函數來實現。

      from django.core import signing
      value = signing.dumps({"foo":"bar"})
      src = signing.loads(value)
      print(value)
      print(src)
      

      結果是

      eyJmb28iOiJiYXIifQ:1NMg1b:zGcDE4-TCkaeGzLeW9UQwZesciI 
      {‘foo’: ‘bar’}
      
  • token存儲

    項目文件下新建一個utils目錄,token.py

    import time
    from django.core import signing
    import hashlib
    from django_redis import get_redis_connection
    
    cache = get_redis_connection("default")
    HEADER = {'typ': 'JWT', 'alg': 'default'}
    KEY = 'DAYBREAK'
    SALT = 'www.daybreak.com'  # 生成摘要的鹽
    TIME_OUT = 30 * 60  # 30min
    
    
    def encrypt(obj):
        """加密"""
        value = signing.dumps(obj, key=KEY, salt=SALT)
        value = signing.b64_encode(value.encode()).decode()
        return value
    
    
    def decrypt(src):
        """解密"""
        src = signing.b64_decode(src.encode()).decode()
        raw = signing.loads(src, key=KEY, salt=SALT)
        return raw
    
    
    # 重點掌握
    def create_token(username):
        """生成token信息"""
        # 1. 加密頭信息
        header = encrypt(HEADER)
        # 2. 構造Payload
        payload = {"username": username, "iat": time.time()}
        payload = encrypt(payload)
        # 3. 生成簽名
        md5 = hashlib.md5()
        md5.update(("%s.%s" % (header, payload)).encode())
        signature = md5.hexdigest()
        token = "%s.%s.%s" % (header, payload, signature)
        # 存儲到緩存中
        cache.set(username, token, TIME_OUT)
        return token
    
    
    def get_payload(token):
        payload = str(token).split('.')[1]
        payload = decrypt(payload)
        return payload
    
    
    # 通過token獲取用戶名
    def get_username(token):
        payload = get_payload(token)
        return payload['username']
    
    
    # 重點掌握
    def check_token(token):
        if token is None:
            return False
        username = get_username(token)
        last_token = cache.get(username)
        if last_token:
            cache.expire(username, TIME_OUT)
            return True
        return False
    
    
    def delete_token(username):
        last_token = cache.get(username)
        if last_token:
            cache.delete(username)
            return True
        return False
    
    • 重點理解create_token()創建token、check_token()驗證token

      from utils.token import *
      token = create_token("leitx") # 生成token
      check_token(token) # 驗證token,認證成功返回True
      
      image
WSGIHTTP
基于中間件實現用戶登錄驗證
  • utils目錄下,新建login_middleware.py

    請求之前就要做處理,所以不需要實現后面這些方法process_viewprocess_exceptionprocess_template_response

    import json
    import re
    from utils.token import check_token
    
    # 設置白名單,這些請求不需要驗證token,比如注冊、登錄接口
    from django.http import HttpResponse
    
    white_list = ['/middle_demo/login/', '/middle_demo/signup/']
    # 設置黑名單,作為示例
    black_list = ['/middle_demo/black/']
    
    
    class LoginMiddleware:
    
        # 固定寫法, 必須有
        def __init__(self, get_response):
            self.get_response = get_response
            # 配置和初始化
    
        # call方法中定義一些中間件的操作
        def __call__(self, request):
    
            request_url = request.path_info  # 獲取請求的url
    
            # 通過正則判斷url
            # for p in white_list:
            #     r = re.compile(p)
            #     if r.match(request_url):
            #         response = self.get_response(request)
            #         return response
    
            # 不會正則的這樣判斷
            # 如果是白名單
            for p in white_list:
                if request_url in p:
                    response = self.get_response(request)
                    return response
    
            # 如果是黑名單
            for p in black_list:
                if request_url in p:
                    response = HttpResponse()
                    response.content = json.dumps({"code": "9999", "message": "非法請求", "data": None})
                    response["Content-Type"] = "application/json;charset=UTF-8"
                    return response
            # 獲取請求頭token的值
            token = request.META.get("HTTP_TOKEN")
            if check_token(token):
                response = self.get_response(request)
                return response
    
            response = HttpResponse()
            response.content = json.dumps({"code": "9999", "message": "用戶未登錄或token過期", "data": None})
            response["Content-Type"] = "application/json;charset=UTF-8"
            return response
    
    
    • request.META是一個Python字典包含了所有本次HTTP請求的Header信息,他把請求頭的中的key都轉為大寫,并都加上HTTP_ 前綴,所以獲取請求token值時,key使用"HTTP_TOKEN"
  • 配置settings.py中的中間件

    因為中間件代碼中有return,觸發return時,不會再執行后面的中間件request部分,所以一般自定義的中間件都放在最下面。

    MIDDLEWARE = [
    'middle_demo.middleware.SimpleMiddleware1',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'middle_demo.middleware.SimpleMiddleware2',
    'utils.login_middleware.LoginMiddleware',
    ]
    
  • 在middle_demo的app中的view.py

    import json
    from django.contrib.auth import authenticate
    from django.contrib.auth.models import User
    from django.http import JsonResponse, HttpResponse
    from django.views import View
    from utils.token import create_token
    
    # Create your views here.
    def bye(request):
        print("想說拜拜")
        return HttpResponse("成功獲取bye")
        
    class Signup(View):
        def post(self, request):
            data = json.loads(request.body)
            username = data.get("username",None)
            password = data.get("password",None)
            email = data.get("email",None)
            try:
                user = User.objects.create_user(username=username, password=password, email=email)
                token = create_token(user.username)
                return JsonResponse({"code": "0000", "message": "注冊成功", "data": token})
            except:
                return JsonResponse({"code": "9999", "message": "用戶已注冊或信息缺失", "data": None})
                
    class Login(View):
        def post(self, request):
            data = json.loads(request.body)
            username = data['username']
            password = data['password']
            # 校驗用戶名和密碼,成功返回user對象,失敗返回None
            user = authenticate(username=username, password=password)
            if user:
                token = create_token(user.username)
                return JsonResponse({"code": "0000", "message": "登錄成功", "data": token})
            else:
                return JsonResponse({"code": "9999", "message": "用戶名或密碼不正確", "data": None})
    
    
  • Jmeter調用白名單、其他接口對比

    • 踩坑:代碼中寫token是存在redis中的,所以一定要開啟redis服務。

    白名單: '/middle_demo/signup/'


    image

    白名單:'/middle_demo/login/'


    image

    非白名單,沒有加token


    image

    非白名單,加token


    image
格式化響應輸出

Django中請求的生命周期

image

步驟

第一步:瀏覽器發起請求

第二步:WSGI創建socket服務端,接收請求(Httprequest)

第三步:中間件處理請求

第四步:url路由,根據當前請求的URL找到視圖函數

第五步:view視圖,進行業務處理(ORM處理數據,從數據庫取到數據返回給view視圖;view視圖將數據渲染到template模板;將數據返回)

第六步:中間件處理響應

第七步:WSGI返回響應(HttpResponse)

第八步:瀏覽器渲染

FBV模式和CBV模式(了解)

一個url對應一個視圖函數,這個模式叫做FBV(Function Base Views),即函數視圖

一個url對應一個類,這個模式叫做CBV(Class Base views),即類視圖

restframework框架

restful 規范

Restful API是目前比較成熟的一套互聯網應用程序的API設計理念,Rest是一組架構約束條件和原則,如何Rest約束條件和原則的架構,我們就稱為Restful架構,Restful架構具有結構清晰、符合標準、易于理解以及擴展方便等特點,受到越來越多網站的采用!

Restful API接口規范包括以下部分:

  1. 協議

API與用戶的通信協議,總是使用HTTPs協議。

  1. 域名

應該盡量將API部署在專用域名之下,如https://api.專屬域名.com;如果確定API很簡單,不會有進一步擴展,可以考慮放在主域名下,如https://專屬域名.com/api/

  1. 版本

可以將版本號放在HTTP頭信息中,也可以放入URL中,如https://api.專屬域名.com/v1/

  1. 路徑

路徑是一種地址,在互聯網上表現為網址,在RESTful架構中,每個網址代表一種資源(resource),所以網址中不能有動詞,只能有名詞,而且所用的名詞往往與數據庫的表格名對應。一般來說,數據庫中的表都是同種記錄的"集合"(collection),所以API中的名詞也應該使用復數,如https://api.專屬域名.com/v1/students

  1. HTTP****動詞

對于資源的具體操作類型,由HTTP動詞表示,HTTP動詞主要有以下幾種,括號中對應的是SQL命令。

    1. GET(SELECT):從服務器取出資源(一項或多項);
    1. POST(CREATE):在服務器新建一個資源;
    1. PUT(UPDATE):在服務器更新資源(客戶端提供改變后的完整資源);
    1. PATCH(UPDATE):在服務器更新資源(客戶端提供改變的屬性);
    1. DELETE(DELETE):從服務器刪除資源;
    1. HEAD:獲取資源的元數據;
    1. OPTIONS:獲取信息,關于資源的哪些屬性是客戶端可以改變的。
  1. 過濾信息

如果記錄數量很多,服務器不可能都將它們返回給用戶,API會提供參數,過濾返回結果,常見的參數有:

    1. ?limit=20:指定返回記錄的數量為20;
    1. ?offset=8:指定返回記錄的開始位置為8;
    1. ?page=1&per_page=50:指定第1頁,以及每頁的記錄數為50;
    1. ?sortby=name&order=asc:指定返回結果按照name屬性進行升序排序;
    1. ?animal_type_id=2:指定篩選條件。
  1. 狀態碼

服務器會向用戶返回狀態碼和提示信息,以下是常用的一些狀態碼:

    1. 200 OK - [GET]:服務器成功返回用戶請求的數據;
    1. 201 CREATED - [POST/PUT/PATCH]:用戶新建或修改數據成功;
    1. 202 Accepted - [*]:表示一個請求已經進入后臺排隊(異步任務);
    1. 204 NO CONTENT - [DELETE]:用戶刪除數據成功;
    1. 400 INVALID REQUEST - [POST/PUT/PATCH]:用戶發出的請求有錯誤,服務器沒有進行新建或修改數據的操作;
    1. 401 Unauthorized - [*]:表示用戶沒有權限(令牌、用戶名、密碼錯誤);
    1. 403 Forbidden - [*] 表示用戶得到授權(與401錯誤相對),但是訪問是被禁止的;
    1. 404 NOT FOUND - [*]:用戶發出的請求針對的是不存在的記錄,服務器沒有進行操作;
    1. 406 Not Acceptable - [GET]:用戶請求的格式不可得;
    1. 410 Gone -[GET]:用戶請求的資源被永久刪除,且不會再得到的;
    1. 422 Unprocesable entity - [POST/PUT/PATCH] 當創建一個對象時,發生一個驗證錯誤;
    1. 500 INTERNAL SERVER ERROR - [*]:服務器發生錯誤,用戶將無法判斷發出的請求是否成功。
  1. 錯誤處理

如果狀態碼是4xx,就會向用戶返回出錯信息,一般來說,返回的信息中將error作為鍵名,出錯信息作為鍵值。

  1. 返回結果

針對不同操作,服務器向用戶返回的結果應該符合以下規范:

    1. GET /collection:返回資源對象的列表(數組);
    1. GET /collection/resource:返回單個資源對象;
    1. POST /collection:返回新生成的資源對象;
    1. PUT /collection/resource:返回完整的資源對象;
    1. PATCH /collection/resource:返回完整的資源對象;
    1. DELETE /collection/resource:返回一個空文檔。
  1. Hypermedia API

RESTful API最好做到Hypermedia,即返回結果中提供鏈接,連向其他API方法,使得用戶不查文檔,也知道下一步應該做什么。

以上是Restful API設計應遵循的十大規范,除此之外,Restful API還需注意身份認證應該使用OAuth 2.0框架,服務器返回的數據格式,應該盡量使用JSON,避免使用XML。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374