一、认证源码
'''
通过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