用户注册登录认证模块

  在项目中创建新应用: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秒后再次测试认证如下所示:

  用户注册登录认证模块