在項目中創建新應用:Login,來實現注冊、登錄、認證功能。
一、注冊接口
1、創建注冊路由
首先在工程路由中添加login應用路由:
from django.contrib import admin from django.urls import path, include, re_path from django.views.static import serve from LuffyCity import settings urlpatterns = [ path('admin/', admin.site.urls), path('api/course/', include("Course.urls")), path('api/', include("Login.urls")), # media路徑配置 # path('media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}), re_path('media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}) ]
隨后在login應用中添加路由文件./Login/urls.py:
from django.urls import path from .views import RegisterView urlpatterns = [ path('register', RegisterView.as_view()) ]
2、創建注冊序列化器
添加新文件:./Login/serializers.py,內容如下所示:
from rest_framework import serializers from Course.models import Account # 賬戶表 import hashlib # class RegisterSerializer(serializers.ModelSerializer): # 注冊序列化器 class Meta: model = Account fields = "__all__" def create(self, validated_data): # 重寫pwd,用md5加鹽 pwd = validated_data["pwd"] pwd_salt = "mao_password" + pwd md5_str = hashlib.md5(pwd_salt.encode()).hexdigest() # hexdigest方法拿到md5的str user_obj = Account.objects.create(username=validated_data["username"], pwd=md5_str) return user_obj
3、配置消息響應
在工程根目錄創建utils目錄,添加base_response.py文件:
class BaseResponse(object): def __init__(self): self.code = 1000 # 默認碼1000 self.data = None self.error = None # 錯誤信息 @property # 方法變屬性 def dict(self): print('222', self.__dict__) return self.__dict__
發送請求,可以看到這里dict函數中會打印如下信息:222 {'code': 1000, 'data': {'id': 33, 'username': 'alex', 'pwd': '7ab71bb07cb065c4f5261ea81159c100'}, 'error': None}
4、編寫注冊視圖
在./Login/views.py中編寫注冊視圖:
from rest_framework.views import APIView from rest_framework.response import Response from django.http import JsonResponse, HttpResponse from .serializers import RegisterSerializer # 引入序列化器 from utils.base_response import BaseResponse # Create your views here. class RegisterView(APIView): def post(self, request): res = BaseResponse() # 實例化response # 用序列化器做校驗 ser_obj = RegisterSerializer(data = request.data) if ser_obj.is_valid(): # 檢驗通過 ser_obj.save() res.data = ser_obj.data else: # 檢驗失敗 res.code = 1020 res.error = ser_obj.errors print('1111', res.data, res.dict) return Response(res.dict)
發送請求會打印如下信息:1111 {'id': 33, 'username': 'alex', 'pwd': '7ab71bb07cb065c4f5261ea81159c100'} {'code': 1000, 'data': {'id': 33, 'username': 'alex', 'pwd': '7ab71bb07cb065c4f5261ea81159c100'}, 'error': None}
5、測試注冊請求
二、登錄接口
因為HTTP請求是無狀態的,要區分用戶,需要給用戶發一個會話標識。前后端不分離的項目,是用cookie和session來解決這個問題。對於前后端分離的項目,則通常是給用戶生成一個唯一標識——token令牌。
1、配置路由和redis連接池
在Login/urls.py中添加登錄路由:
from django.urls import path from .views import RegisterView, LoginView, TestView urlpatterns = [ path('register', RegisterView.as_view()), path('login', LoginView.as_view()), ]
創建utils/redis_pool.py,配置redis連接池:
import redis POOL = redis.ConnectionPool(host='127.0.0.1', port=6379, decode_responses=True, max_connections=10) # 最大連接數
2、登錄視圖
from rest_framework.views import APIView from rest_framework.response import Response from .serializers import RegisterSerializer # 引入序列化器 from utils.base_response import BaseResponse from Course.models import Account from utils.redis_pool import POOL import redis import uuid class LoginView(APIView): def post(self, request): res = BaseResponse() username = request.data.get("username", "") pwd = request.data.get("pwd", "") user_obj = Account.objects.filter(username=username, pwd=pwd).first() # 查詢用戶表拿到用戶對象 if not user_obj: res.code = 1030 res.error = "用戶名或密碼錯誤" return Response(res.dict) # 用戶登錄成功生成一個token寫入redis # 寫入redis token(唯一): user_id conn = redis.Redis(connection_pool=POOL) try: token = uuid.uuid4() # 生成隨機字符串,類型是:<class 'uuid.UUID'> conn.set(str(token), user_obj.id, ex=120) # ex:過期時間120秒 res.data = token except Exception as e: print(e) res.code = 1031 res.error = "創建令牌失敗" return Response(res.dict)
注意uuid不能作為redis的key,需要轉化為字符串、數字等數據類型。
另外需要注意到conn.set()方法的參數:
class Redis(object): def set(self, name, value, ex=None, px=None, nx=False, xx=False): """ Set the value at key ``name`` to ``value`` ``ex`` sets an expire flag on key ``name`` for ``ex`` seconds. ``px`` sets an expire flag on key ``name`` for ``px`` milliseconds. ``nx`` if set to True, set the value at key ``name`` to ``value`` only if it does not exist. ``xx`` if set to True, set the value at key ``name`` to ``value`` only if it already exists. """
在Redis中設置值,默認,不存在則創建,存在則修改。各個參數的含義:
- ex:過期時間(秒)
- px:過期時間(毫秒)
- nx:如設置為True,則只有name不存在時,當前set操作才執行,值存在,就修改不了,執行沒有效果。
- xx:如設置為True,則只有name存在時,當前set操作才執行,值存在才能修改,值不存在,不會設置新值。
3、登錄測試
圖中data就是生成的token。
三、認證接口
校驗請求頭中攜帶的token信息。
1、添加認證測試路由
在/Login/urls.py中添加認證測試路由:
from django.urls import path from .views import RegisterView, LoginView, TestView urlpatterns = [ path('register', RegisterView.as_view()), path('login', LoginView.as_view()), path('test_auth', TestView.as_view()), ]
2、添加自定義登錄驗證
在utils中添加my_auth.py文件:
from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed from Course.models import Account from .redis_pool import POOL import redis CONN = redis.Redis(connection_pool=POOL) class LoginAuth(BaseAuthentication): def authenticate(self, request): # 從請求頭中獲取前端帶過來的token token = request.META.get("HTTP_AUTHENTICATION", "") # request.META是一個Python字典,包含本次HTTP請求的Header信息 if not token: raise AuthenticationFailed("沒有攜帶token") # 有token,去redis中比對 user_id = CONN.get(str(token)) # 取不到值會報:None if user_id == None: # token不合法 raise AuthenticationFailed("token已經過期") user_obj = Account.objects.filter(id=user_id).first() return user_obj, token
3、添加測試認證視圖
from utils.my_auth import LoginAuth class TestView(APIView): authentication_classes = [LoginAuth, ] # 局部認證,該接口必須登錄認證 def get(self, request): return Response("認證測試")
4、測試認證
通過POST http://127.0.0.1:8008/api/login,獲取到token信息后,執行測試認證如下所示:
在登錄視圖中曾設置超時時間120秒,120秒后再次測試認證如下所示: