六、认证

一、认证源码

'''
通过CBV源码,我们了解到,之所以一个类能够实现根据前端请求方式,自动调用不同的方法,根本在于dispatch方法中使用了反射原理

在研究restframework认证时,我们也需要着手与dispatch方法
'''
1、CBV不再继承django.views 的 View,而是继承restframework的APIView
	from rest_framework.views import APIView
	
2、查看APIView中的dispatch源码,可以看到其中一样使用到了与from django.views import View一样的反射,但是增加了几行代码
	def dispatch(self, request, *args, **kwargs):
		self.args = args
        self.kwargs = kwargs
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        self.headers = self.default_response_headers  

3、我们可以看到:原生request被传入initialize_request方法中,进行加工后,再返回出来,由此,我们转而研究initialize_request源码
	def initialize_request(self, request, *args, **kwargs):
		parser_context = self.get_parser_context(request)
        return Request(
            request,
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )
# 可以看出来,initialize_request方法是将原生request进行加工,然后返回Request对象,对象中存在原生request和其他附加功能
4、由于我们此刻只研究认证,所以继续查看self.get_authenticators()源码
	def get_authenticators(self):
		return [auth() for auth in self.authentication_classes]	
	
5、以上方法是返回一个列表推导式,其中存储的是 auth对象,我们继续追踪self.authentication_classes属性,可以看到
	authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES

# self.authentication_classes属性是获取restframework配置文件中的数据,略过,到此为止,我们可以确定,initialize_request方法将原生request进行加工后,返回了一个Request对象,对象中包含原生requst和一个列表[auth对象,]

6、继续研究dispatch源码
	def dispatch(self, request, *args, **kwargs):
		try:
            # 传入封装的request
            self.initial(request, *args, **kwargs)

7、可以看到将封装后的request方法传入到initial方法中,我们继续跟进源码
	def initial(self, request, *args, **kwargs):
		self.perform_authentication(request)
	
	def perform_authentication(self, request):
		request.user
	
8、由此可看,此处调用的是封装后的Request对象的user属性,那么我们继续跟进
结果发现,request.user是被property修饰的方法
	@property
    def user(self):
   		if not hasattr(self, '_user'):
            with wrap_attributeerrors():
                self._authenticate()
        return self._user
        
9、从源码中并没有发现 _user属性,那么会执行 with
	def _authenticate(self):
		for authenticator in self.authenticators:
            try:
                # 执行authenticate()方法,并将Request对象传入
                user_auth_tuple = authenticator.authenticate(self)
            
            # 如果用户登录,那么会返回一个user_auth_tuple元组
			# 如果用户没有登录,则抛出异常,异常需要是APIException
            except exceptions.APIException:
                self._not_authenticated()
                raise
             
            # 判断如果user_auth_tuple不为空
            if user_auth_tuple is not None:
                # 将auth()对象赋值给_authenticator,进行实例化
                self._authenticator = authenticator
                
                # 获取authenticate()方法的返回结果,此刻表明返回结果必须有两个
                # 且在Request对象中增加两个对象属性 user、auth
                self.user, self.auth = user_auth_tuple
                
                return

        self._not_authenticated()
 
'''
for authenticator in self.authenticators,其中authenticators就是前面Request封装原生request中的一个属性,存储的是一个列表对象 
	[auth() for auth in self.authentication_classes],

其中self.authentication_classes是testframework的配置文件中的内容

user_auth_tuple = authenticator.authenticate(self),就是存储执行该列表中存储的对象的authenticate方法的返回结果
'''


'''
从以上源码,我们可以总结关键性的两点:
	1. 存在一个auth对象,该对象由authentication_classes使用列表存储
    2. auth对象中存在一个authenticate方法,该方法会返回一个拥有两个元素的元组,且被执行 
    
从以上的两点,我们就可以想到,当用户登陆时,我们可以通过自定义auth对象,并在该对象中定义authenticate方法来进行登录认证功能

from django.shortcuts import render, HttpResponse, redirect
from rest_framework.views import APIView
from rest_framework import exceptions
import json


class MyAuthentication(object):
    def authenticate(self, request):
        token = request.query_params.get('token')
        if not token:
            raise exceptions.APIException('用户认证失败')
        return ('zhangzhuowei', None)
    def authenticate_header(self, val):
        pass

class MyView(APIView):
    authentication_classes = [MyAuthentication, ]
    def get(self, request, *args, **kwargs):
        code = {
            'code': 10001,
            'baocuo': '你错了'
        }
        return render('login.html', json.dumps(code), status=300)

二、BaseAuthentiaction

# drf提供BaseAuthentiaction类,规定了认证类的书写格式,我们可以继承他
class BaseAuthentication:
	
    # 必须重写authenticate方法,且需要返回一个元组(user, token),否则会抛出异常
    def authenticate(self, request):
        raise NotImplementedError(".authenticate() must be overridden.")
	
    # 认证失败时,给浏览器返回的响应头
    def authenticate_header(self, request):
        pass

三、restframework认证实验

1、model.py

from django.db import models

# Create your models here.


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

class UserToken(models.Model):
    user = models.OneToOneField(to='UserInfo')
    token = models.CharField(max_length=64)

2、views.py

from django.shortcuts import render, HttpResponse, redirect
from rest_framework.views import APIView
from django.http import JsonResponse
from app01 import models

def md5(user):
    import hashlib
    from time import time,ctime
    m = hashlib.md5(bytes(user, encoding='utf8'))
    m.update(bytes(ctime(time()), encoding='utf8'))
    return m.hexdigest()


class MyView(APIView):
    authentication_classes = [MyAuth, ]
    def post(self, request, *args, **kwargs):
        ret = {'code': 1000, 'msg': None}
        try:
            user = request.data.get('username')
            pwd = request.data.get('password')
            obj = models.UserInfo.object.filter(username=user,pasword=pwd).first()
            if not obj:
                ret['code'] = 1001
                ret['msg'] = '用户名或密码错误'
            token = md5(user)
            models.UserToken.objects.update_or_create(usr=obj, default={'token': token})
            ret['token'] = token
        except Exception as e:
            pass
        return JsonResponse(ret)
    def put(self, request):
        pass

3、认证类书写

xxx.py

from rest_framework.authentication import BaseAuthentication

class MyAuth(BaseAuthentication):
    def authenticate(self, request):
        token = request.query_params.get('token')
        if token:
            token_obj = models.UserToken.objects.filter(token=token).first()
            if token_obj:
            	return	token_obj.user.username, token  
        raise exceptions.APIException('用户认证失败')

四、认证配置

# 全局配置,可以有多个认证类,从上到下依次执行-setting.py
REMOVED_SETTINGS = [
    "DEFAULT_AUTHENTICATION_CLASSES":[
        "app01.认证类路径",
        ...
    ]
]

# 局部配置,在视图类中写
authentication_classes=[
    '认证类',
    ...
]

# 局部禁用,在视图类中写
authentication_classes=[]

五、drf认证类

from rest_framework.authentication import

1、BasicAuthentication

class BasicAuthentication(BaseAuthentication):

    www_authenticate_realm = 'api'

    def authenticate(self, request):

        auth = get_authorization_header(request).split()

        if not auth or auth[0].lower() != b'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:
            try:
                auth_decoded = base64.b64decode(auth[1]).decode('utf-8')
            except UnicodeDecodeError:
                auth_decoded = base64.b64decode(auth[1]).decode('latin-1')
            auth_parts = auth_decoded.partition(':')
        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_credentials(self, userid, password, request=None):
        """
        Authenticate the userid and password against username and password
        with optional request for context.
        """
        credentials = {
            get_user_model().USERNAME_FIELD: userid,
            'password': password
        }
        user = authenticate(request=request, **credentials)

        if user is None:
            raise exceptions.AuthenticationFailed(_('Invalid username/password.'))

        if not user.is_active:
            raise exceptions.AuthenticationFailed(_('User inactive or deleted.'))

        return (user, None)

    def authenticate_header(self, request):
        return 'Basic realm="%s"' % self.www_authenticate_realm

2、CSRFCheck

class CSRFCheck(CsrfViewMiddleware):
    def _reject(self, request, reason):
        return reason

3、RemoteUserAuthentication

class RemoteUserAuthentication(BaseAuthentication):
  
    header = "REMOTE_USER"

    def authenticate(self, request):
        user = authenticate(remote_user=request.META.get(self.header))
        if user and user.is_active:
            return (user, None)

4、SessionAuthentication

class SessionAuthentication(BaseAuthentication):

    def authenticate(self, request):

        user = getattr(request._request, 'user', None)

        if not user or not user.is_active:
            return None

        self.enforce_csrf(request)

        return (user, None)

    def enforce_csrf(self, request):
   
        def dummy_get_response(request):  # pragma: no cover
            return None

        check = CSRFCheck(dummy_get_response)

        check.process_request(request)
        reason = check.process_view(request, None, (), {})
        if reason:
            raise exceptions.PermissionDenied('CSRF Failed: %s' % reason)

5、TokenAuthentication

class TokenAuthentication(BaseAuthentication):

    keyword = 'Token'
    model = None

    def get_model(self):
        if self.model is not None:
            return self.model
        from rest_framework.authtoken.models import Token
        return Token


    def authenticate(self, request):
        auth = get_authorization_header(request).split()

        if not auth or auth[0].lower() != self.keyword.lower().encode():
            return None

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

        try:
            token = auth[1].decode()
        except UnicodeError:
            msg = _('Invalid token header. Token string should not contain invalid characters.')
            raise exceptions.AuthenticationFailed(msg)

        return self.authenticate_credentials(token)

    def authenticate_credentials(self, key):
        model = self.get_model()
        try:
            token = model.objects.select_related('user').get(key=key)
        except model.DoesNotExist:
            raise exceptions.AuthenticationFailed(_('Invalid token.'))

        if not token.user.is_active:
            raise exceptions.AuthenticationFailed(_('User inactive or deleted.'))

        return (token.user, token)

    def authenticate_header(self, request):
        return self.keyword

 

posted @ 2021-07-04 19:40  zzwYYYYYY  阅读(64)  评论(0)    收藏  举报