前言

django的生命周期

  1. 当用户在浏览器中输入url时,浏览器会生成请求头和请求体发给服务端请求头和请求体中会包含浏览器的动作(action),这个动作通常为get或者post,体现在url之中.
  2. url经过Django中的wsgi,再经过Django的中间件,最后url到过路由映射表,在路由中一条一条进行匹配,一旦其中一条匹配成功就执行对应的视图函数,后面的路由就不再继续匹配了.
  3. 视图函数根据客户端的请求查询相应的数据.返回给Django,然后Django把客户端想要的数据做为一个字符串返回给客户端.
  4. 客户端浏览器接收到返回的数据,经过渲染后显示给用户.

主要是通过dispath触发的一系列操作

源码(登录)

  • models.py
class UserInfo(models.Model):
    user_type_choices = (
        (1,'普通用户'),
        (2,'VIP'),
        (3,'SVIP')
    )
    username = models.CharField(max_length=32, unique=True)
    password = models.CharField(max_length=64)
    user_type = models.IntegerField(choices=user_type_choices)

class UserToken(models.Model):
    user = models.OneToOneField(to='UserInfo')
    token = models.CharField(max_length=64)
  • views.py
from django.http import JsonResponse
from rest_framework.views import APIView
from api import models

import hashlib,time
def md5(user):
    ctime = str(time.time())
    m = hashlib.md5(bytes(user,encoding='utf-8'))
    m.update(bytes(ctime,encoding='utf-8'))

    return m.hexdigest()


class AuthView(APIView):
    def post(self, request, *args, **kwargs):
        ret = {
            'code':1000,
            'msg':None,
        }
        try:
            user = request._request.POST.get('username')
            password = request._request.POST.get('password')
            obj = models.UserInfo.objects.filter(username=user, password=password).first()
            if not obj:
                ret['code'] = 1001
                ret['msg'] = '用户名或密码错误'
            token = md5(user)
            models.UserToken.objects.update_or_create(user=obj,defaults={'token':token})
            ret['token'] = token
        except Exception as e:
            ret['code'] = 1002
            ret['mesage'] = '请求异常'

        return JsonResponse(ret)

源码(认证)

需求:对于订单,用户信息类的信息获取,必须用户登录后才能获得接口数据

from django.shortcuts import render, HttpResponse
from django.http import JsonResponse
from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework import exceptions
from rest_framework.authentication import BasicAuthentication
from api import models

import hashlib
import time
# Create your views here.

ORDER_DICT = {
    1: {
        'name': '媳妇',
        'age': 18,
        'gender': '女',
        'content': '...',
    },
    2: {
        'name': '狗',
        'age': 8,
        'gender': '男',
        'content': '...',
    },
}


def md5(user):
    ctime = str(time.time())
    m = hashlib.md5(bytes(user, encoding='utf-8'))
    m.update(bytes(ctime, encoding='utf-8'))

    return m.hexdigest()


class AuthView(APIView):
    """
    用于用户登录认证
    """

    def post(self, request, *args, **kwargs):
        ret = {
            'code': 1000,
            'msg': None,
        }
        try:
            user = request._request.POST.get('username')
            password = request._request.POST.get('password')
            obj = models.UserInfo.objects.filter(
                username=user, password=password).first()
            if not obj:
                ret['code'] = 1001
                ret['msg'] = '用户名或密码错误'
            token = md5(user)
            models.UserToken.objects.update_or_create(
                user=obj, defaults={'token': token})
            ret['token'] = token
        except Exception as e:
            ret['code'] = 1002
            ret['mesage'] = '请求异常'

        return JsonResponse(ret)


class Authtication(object):
    """
    用户验证
    """

    def authenticate(self, request):
        token = request._request.GET.get('token')  # 获取原始request中的token值
        token_obj = models.UserToken.objects.filter(token=token).first()
        if not token_obj:
            raise exceptions.AuthenticationFailed('用户认证失败')
        # 在rest framework 内部会将这两个字段赋值给request以供后续操作使用
        return (token_obj.user, token_obj)

    def authenticate_header(self, request):
        pass


class OrderView(APIView):
    """
    订单相关业务
    """
    authentication_classes = [Authtication]

    def get(self, request, *args, **kwargs):
        ret = {
            'code': 1000,
            'message': None,
            'data': None,
        }
        try:
            ret['data'] = ORDER_DICT
        except Exception as e:
            pass
        return JsonResponse(ret)


class UserInfoView(APIView):
    authentication_classes = [Authtication]

    def get(self, request, *args, **kwargs):
        return HttpResponse('用户信息')

认证流程原理

  1. 进入dispatch方法
  2. 对原始request进行封装,其中把authentication_classed中的每个类的对象和原生request进行封装
  3. 执行initial方法,如果抛出异常会被捕捉到
  4. 执行perform_authentication方法,其中perform_authentication调用了request.user
  5. 执行request.user
  6. 调用了_authenticate()方法,对认证对象进行循环,执行authenticate方法

user_auth_tuple = authenticator.authenticate(self)
如果在authenticate返回了值,那么user_auth_tuple就是返回的值,否则就是None

 if user_auth_tuple is not None:
        self._authenticator = authenticator
        self.user, self.auth = user_auth_tuple
        return

可以看到返回的元祖,第一个值赋值给了user,第二个值赋值给了auth
如果不返回None,则调到下一个authentication_classed继续做认证,如果都不处理,则执行self._not_authenticated()

def _not_authenticated(self):
    """    Set authenticator, user & authtoken representing an unauthenticated request.
    Defaults are None, AnonymousUser & None.    """    self._authenticator = None
    if api_settings.UNAUTHENTICATED_USER:
        self.user = api_settings.UNAUTHENTICATED_USER()  # AnonymousUser
    else:
        self.user = None
    if api_settings.UNAUTHENTICATED_TOKEN:
        self.auth = api_settings.UNAUTHENTICATED_TOKEN()  # None
    else:
        self.auth = None
  1. 执行反射函数

原理流程图


配置项

认证的配置可以在局部视图使用,同时也可以全局配置

  • 局部配置

在每个类里面配置authentication_classes

  • 全局配置

在settings里面配置,其中列表里面的值是自己写的验证类的地址

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES':['api.utils.auth.FirstAuthtication','api.utils.auth.Authtication']
}

配置匿名用户

因为前面提到如果自己写的验证类返回了None,最后会进入self._not_authenticated()这个方法里,如果没有配置匿名配置,那么会返回Anonymous User

但是如果配置了UNAUTHENTICATED_USER这个参数,就会执行下一步
self.user = api_settings.UNAUTHENTICATED_USER()
所以我们需要配置一个函数,这里直接用匿名函数

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES':['api.utils.auth.FirstAuthtication','api.utils.auth.Authtication'],
    'UNAUTHENTICATED_USER': lambda : "匿名用户",  # 如果是匿名或未登录,request.user = "匿名用户"
    'UNAUTHENTICATED_TOKEN': None,  # 如果是匿名或未登录,request.auth = None
}


内置认证类

from rest_framework.authentication import BaseAuthentication

其中BaseAuthentication是验证的基类,如果自己写验证的类,可以直接继承于它。

class BaseAuthentication:
    """
    All authentication classes should extend BaseAuthentication.
    """

    def authenticate(self, request):
        """
        Authenticate the request and return a two-tuple of (user, token).
        """
        raise NotImplementedError(".authenticate() must be overridden.")

    def authenticate_header(self, request):
        """
        Return a string to be used as the value of the `WWW-Authenticate`
        header in a `401 Unauthenticated` response, or `None` if the
        authentication scheme should return `403 Permission Denied` responses.
        """
        pass

若继承自BasicAuthentication,若带上了表明自己身份的信息,则能正常请求到数据
http://127.0.0.1:8000/api/v1/info/?token=e7f53ed7ce093a5db471a001ca4b8e49
若没有带上token或者其他表明身份的信息,则会跳出如下界面:

首先执行
auth = get_authorization_header(request).split()

HTTP_AUTHORIZATION':' basic(用户名:密码)Base64加密'

操作代码:

    def authenticate(self, request):
        """
        Returns a `User` if a correct username and password have been supplied
        using HTTP Basic authentication.  Otherwise returns `None`.
        """
        auth = get_authorization_header(request).split()  # 分割

        if not auth or auth[0].lower() != b'basic':  # 如果auth为空或者分割出来的第一项不等于basic
            return None

        if len(auth) == 1:
            msg = _('Invalid basic header. No credentials provided.')
            raise exceptions.AuthenticationFailed(msg)
        elif len(auth) > 2:
            msg = _('Invalid basic header. Credentials string should not contain spaces.')
            raise exceptions.AuthenticationFailed(msg)

        try:
            auth_parts = base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(':')  # 对base64进行解密
        except (TypeError, UnicodeDecodeError, binascii.Error):
            msg = _('Invalid basic header. Credentials not correctly base64 encoded.')
            raise exceptions.AuthenticationFailed(msg)

        userid, password = auth_parts[0], auth_parts[2]
        return self.authenticate_credentials(userid, password, request)

总结

对接口数据进行认证的本质就是去写一个类,类里面需要包含两个最基本的方法
def authenticate(self, request): 和def authenticate_header(self, request):
这两个缺一不可。

    def authenticate(self, request):
        """
        Authenticate the request and return a two-tuple of (user, token).
        """
        raise NotImplementedError(".authenticate() must be overridden.")

    def authenticate_header(self, request):
        """
        Return a string to be used as the value of the `WWW-Authenticate`
        header in a `401 Unauthenticated` response, or `None` if the
        authentication scheme should return `403 Permission Denied` responses.
        """
        pass

但是如果继承了认证的基类(BaseAuthentication),就只需要自定义authenticate方法完成认证逻辑即可。

这个方法有三种返回值:

  • None:下一个认证继续来执行认证,如果一直是None,则是匿名用户
  • raise exceptions.AuthenticationFailed:认证失败
  • 元祖:元祖[0]赋值给request.user,元祖[1]赋值给request.auth

对于全局使用:

需要在settings.py里面设置
这里面写的是具体的路径

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES':['api.utils.auth.FirstAuthtication','api.utils.auth.Authtication'],
    'UNAUTHENTICATED_USER': lambda : "匿名用户",  # 如果是匿名或未登录,request.user = "匿名用户"
    'UNAUTHENTICATED_TOKEN': None,  # 如果是匿名或未登录,request.auth = None
}

如果那个视图不想验证,只需要配置authentication_classes=[ ]等于空即可。

对于局部使用:

在视图的类里面声明authentication_classes=[...],单纯只对这个视图做认证,里面写的是导入的类名

版权声明:本文为原创文章,版权归 heroyf 所有。
本文链接: https://heroyf.club/2019/07/django_learn3/


“苹果是给那些为了爱选择死亡的人的奖励”