认证
  # 链接资料
博客园极其内向的帅小伙
drf官方文档
drf官方源码地址
GitHub - encode/django-rest-framework: Web APIs for Django. 🎸 (opens new window)
功能大致脑图
# 1. 认证
# 1.1 初始认证
有些API需要用户登录成功之后,才能访问;有些无需登录就能访问。
	把token值存放起来
请求来了,执行了View类中as_view的view()方法,本质上执行了self.dispatch方法
按顺序查找dispatch方法(自己类中,父类中,父父类中...)
在APIView中的dispatch方法中先把原来request封装进去,变成新的request对象
接下来又执行了三个组件,分别是认证,权限和频率
如果三个中有一个不满足,则不继续执行
再走分发方法,最后返回response出去
即在请求进入视图函数前加了一些东西,重写了dispatch方法
 2
3
4
5
6
7
8
9
10
现在我们写的接口,会发现一个问题,就是任何人都可以创建数据库,修改数据。这样肯定是不行的,我们希望只有数据的创建者才能有权限修改数据,如果不是,只能有只读权限。这个好比什么呐?好比我们有一个购物车的话,去访问,我们就要去登录的情况下去访问。
 其实在Django rest famework 已经默认帮我做认证了。我们可以去 看下源码:我们按 Ctrl + 类名:
views.APIView => APIView(View)
 来我们看下 APIView 的源码:
class APIView(View):
    ....
    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES   #权限认证
    ....
 2
3
4
我们进入 Ctrl + api_settings 继续进去看:
api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)  
# 原来在这边,意思就是说 有配置权限的话就去读自己配置的,没有的话就读DEFAULTS
 2
那我们进去看看 Ctrl + DEFAULTS 继续看看,默认的权限是啥?
EFAULTS = {
    # Base API policies
    .....,
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.SessionAuthentication', #session验证
        'rest_framework.authentication.BasicAuthentication' #客户端 用户名密码验证
    ),
    ....
}
 2
3
4
5
6
7
8
9
DRF的的默认验证是 SessionAuthentication 和 BasicAuthentication。
认证失败会有两种可能的返回值
- 401 Unauthorized 未认证
 - 403 Permission Denied 权限被禁止
 
# 1、DRF中的认证方法
- BasicAuthentication:此身份验证方案使用**HTTP基本身份验证 (opens new window)*,根据用户的用户名和密码进行签名。基本身份验证通常仅适用于测试。
 - TokenAuthentication: 此身份验证方案使用基于令牌的简单HTTP身份验证方案。令牌认证适用于客户端 - 服务器设置,例如本机桌面和移动客户端。
 - SessionAuthentication: 此身份验证方案使用Django的默认会话后端进行身份验证。会话身份验证适用于与您的网站在同一会话上下文中运行的AJAX客户端。
 - RemoteUserAuthentication(不经常用,忘记):此身份验证方案允许您将身份验证委派给Web服务器,该服务器设置
REMOTE_USER环境变量。 
说明:
- BasicAuthentication:客户端用户名密码认证
 - TokenAuthentication:采用token值进行校验
 - SessionAuthentication:采用sessionid的方式进行校验。
 
# 2、示例
models.py
from django.db import models
class User(models.Model):
    """用户"""
    username = models.CharField(max_length=128, verbose_name="用户账号")
    password = models.CharField(max_length=128, verbose_name="用户密码")
class Game(models.Model):
    """游戏"""
    name = models.CharField(verbose_name="游戏名", max_length=64)
    desc = models.CharField(verbose_name="描述", max_length=20)
    user = models.ForeignKey(User, on_delete=models.CASCADE)  # 继承django自带的用户
    def __str__(self):
        return self.name
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
    url(r'^user/$', views.UserView.as_view()),  # 查看所有用户
    url(r'^user/(?P<pk>\d+)/$', views.UserView.as_view()),  # 查看所有用户
    url(r'^game/$', views.GameList.as_view()),  # 游戏
    url(r'^game/<int:pk>/$', views.GameList.as_view()),
]
 2
3
4
5
6
7
8
9
10
11
views.py
# 游戏
class GameSerializer(serializers.ModelSerializer):
    class Meta:
        model = Game
        fields = "__all__"
class GameList(APIView):  # ListCreateAPIView=>GenericAPIView => views.APIView => APIView(View)
	
    def get(self, request, *args, **kwargs):
        # http://127.0.0.1:8000/api/app01/game/ get
        data = Game.objects.filter()
        serializer = GameSerializer(data=data, many=True)
        serializer.is_valid()
        return Response({"code": 0, "message": "success", "data": serializer.data})
    def post(self, request, *args, **kwargs):
        # http://127.0.0.1:8000/api/app01/game/ post
        data = request.data
        serializer = GameSerializer(data=data)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response({"code": 0, "message": "success", "data": serializer.data})
class GameDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Game.objects.all()
    serializer_class = GameSerializer
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 想要功能
我们需要用户自己添加自己喜欢的游戏,我们就需要登录
# 2. DRF认证方式
# 2.1 BasicAuthentication(用户名密码认证)
DRF的 APIView视图其实是自带了验证
# 全局认证
在settings.py中配置
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.SessionAuthentication',  # session认证
        'rest_framework.authentication.BasicAuthentication',   # 基本认证
    )
}
 2
3
4
5
6
全局配置单个不配置
authentication_classes = [] 
# 如果全局配置了,但是这个视图不需要验证,就authentication_classes 变成空列表
 2
# 局部认证
在视图中单独声明
from rest_framework.views import APIView
from django.http import JsonResponse
from rest_framework.authentication import BasicAuthentication
from rest_framework.permissions import IsAuthenticated
 
class CartView(APIView):
    # 基于什么登录认证的(通过什么方式登录)
    authentication_classes = [BasicAuthentication] #对APIView的authentication_classes重写
    # 只有登录才能访问(权限控制你是否登录)
    permission_classes = [IsAuthenticated]  #查看IsAuthenticated源码 => Ctrl + IsAuthenticated
    def get(self, request, *args, **kwargs):
        data = {"code": 1,}
        return JsonResponse(data)
 2
3
4
5
6
7
8
9
10
11
12
13
# 2.2 TokenAuthentication认证
# 基础配置
# a. 安装rest_framework.authtoken
说明:在settings.py的文件中INSTALLED_APPS配置
# settings.py
INSTALLED_APPS = (
    ...
    'rest_framework.authtoken'
)
 
#迁入数据
>python manage.py migrate
 2
3
4
5
6
7
8
# b. 全局配置和局部配置
说明:
- 如果是全局配置的话,需要在 settings.py文件中配置
 - 但是如果是局部配置,就要在具体的视图中配置。这个根据自己的需求来配置。
 
# settings.py配置  全局配置:表示对所有视图有效
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication', 
        'rest_framework.authentication.TokenAuthentication',
    ),
}
 
# 局部配置 :只对当前视图有效
from rest_framework.authentication import SessionAuthentication
from rest_framework.authentication import BasicAuthentication
from rest_framework.authentication import TokenAuthentication
 
class XXXX(APIView):
            authentication_classes = [BasicAuthentication,TokenAuthentication,SessionAuthentication]
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# c. 全局配置单个不配置
说明:我们可能遇到这种情况,就是全局需要配置,但是我只是单单某个接口不需要认证。
class CartView(APIView):
    #局部的
 
    # 基于什么登录认证的
    authentication_classes = [] # 如果全局配置了,但是这个视图不需要验证,就authentication_classes 变成空列表
 
    # 只有登录才能访问
    permission_classes = [IsAuthenticated]
 
    def get(self, request, *args, **kwargs):
 
        .....
 2
3
4
5
6
7
8
9
10
11
12
# 路由配置
我们配置token验证的时候,还有配置路由,验证登录之后,产生token值,后面其他接口 拿到 这个token 值放在 请求 header中,去校验。
在根级路由或者子路由中配置,这个你自己看,我们这边是根级路由配置
from django.contrib import admin
from django.urls import path, include
from rest_framework.authtoken import views  #设置路由,必须导入view
 
urlpatterns = [
    path('admin/', admin.site.urls),
    path('api-token-auth/', views.obtain_auth_token) #token哪里来,我们这边还需要配置一个路由,请求生成token
]
 2
3
4
5
6
7
8
我们来看一下 obtain_auth_token 源码:Ctrl + obtain_auth_token 看一下:
obtain_auth_token =>  ObtainAuthToken
 obtain_auth_token这个类自动帮我们生成一个token。
# 传递方式
封装到请求头中,已下面的格式
Authorization: Token 401f7ac837da42b97f613d789819ff93537bee6a
 2
# 2.3 SessionAuthentication验证
使用方式配置方式和上面一样
from rest_framework.authentication import BasicAuthentication, TokenAuthentication, SessionAuthentication  #导入SessionAuthentication验证
from rest_framework.permissions import IsAuthenticated
 
# Create your views here.
 
class CartView(APIView):
    # 基于什么登录认证的
    authentication_classes = [BasicAuthentication, TokenAuthentication, SessionAuthentication]  #支持session验证,三者任何一种验证都可以,这边我们主要用 Session验证
    # 只有登录才能访问
    permission_classes = [IsAuthenticated]
    def get(self, request, *args, **kwargs):
 
        ....
 
        return JsonResponse(ctx)
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
# django中有自带的登录和退出
from django.contrib.auth import login
from django.contrib.auth import logout
 2
# 详细查看
6-5 认证和权限-DRF认证之SessionAuthentication认证 (opens new window)
# 2.4 自定义认证
# a. 初始化操作
模型models.py
说明:我们自定义 用户表 和 用户Token表。关系是1对1关系
#自己定义的跟django自带的没有关系
class User(models.Model):
    username = models.CharField(max_length=32, unique=True)
    password = models.CharField(max_length=64)
 
#存放token表
class UserToken(models.Model):
    user = models.OneToOneField('User', models.CASCADE) #token跟user是1对1关系,一般都是放两张表做。
    token = models.CharField(max_length=64)
    
>python manage.py makemigrations
>python manage.py migrate
 2
3
4
5
6
7
8
9
10
11
12
视图views.py
说明:我们必须要有一个登录视图,以及如何获取 md5值的设置。=> 编辑 views.py
# 获取MD5加密取值
def get_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 LoginView(APIView):
    """用户登录"""
    authentication_classes = []
    permission_classes = []
    def post(self, request, *args, **kwargs):
        username = request.data.get("username")
        password = request.data.get("password")
        user_obj = User.objects.filter(username=username, password=password).first()
        if not user_obj:
            return Response({"code": 1, "message": "密码错误"})
        token = get_md5(username)  # 用账号去加密
        UserToken.objects.update_or_create(user=user_obj, defaults={"token": token})  # 不存在就创建,创建就更新defaults中的值
        return Response({"code": 0, "message": "success", "data": {"id": user_obj.id, "token": token}})
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
路由urls.py
url(r"login/$", views.LoginView.as_view())  # 配置登录的路由
 # b. 自定义认证编写(认证源码分析)
说明:我们都知道我们验证有三种方式:asicAuthentication, TokenAuthentication, SessionAuthentication。他们都是继承的谁呐?他们都是继承的是 BaseAuthentication。好啦,我们去看下BaseAuthentication的源码:Ctrl + BasicAuthentication => BaseAuthentication:
 重写源码
class BaseAuthentication(object):
    """
    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
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# c. 自定义认证类
说明:继续编辑视图views.py文件,继承BaseAuthentication类,重写authenticate方法。
from rest_framework import exceptions
from rest_framework.authentication import BaseAuthentication
from .models import UserToken
class MyAuthtication(BaseAuthentication):
    def authenticate(self, request):
        # token = request.data.get("token")
        token = request.META.get('HTTP_AUTHORIZATION', None)
        user_obj = UserToken.objects.filter(token=token).first()
        if not user_obj:
            raise exceptions.AuthenticationFailed('用户未认证')
        return (user_obj.user, token)
    def authenticate_header(self, request):
        pass
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# d. 在视图中使用
全局
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': ['app01.utils.auth.MyAuthtication',]
}
 2
3
局部
class GameList(APIView):
    authentication_classes = [MyAuthtication, ]  # 方式二:
 2
# 2.5 JWT 认证示例
6-7 认证和权限-DRF认证之jwt认证 (opens new window)
# 2.6 匿名用户(未登录用户)
settings.py
匿名用户就是设置request.user和request.auth为None
REST_FRAMEWORK = { # 'DEFAULT_AUTHENTICATION_CLASSES': ['app02.utils.auth.FirstAuthtication', 'app02.utils.auth.Authtication'], 'DEFAULT_AUTHENTICATION_CLASSES': ['app02.utils.auth.FirstAuthtication'], # 匿名用户(因为我们在封装的时候什么都没有写,让它返回None) # 'UNAUTHENTICATED_USER': lambda: '匿名用户', 'UNAUTHENTICATED_USER': None, 'UNAUTHENTICATED_TOKEN': None, }1
2
3
4
5
6
7app02.utils.auth.py
class FirstAuthtication(object): def authenticate(self, request): print('匿名用户') pass # pass返回的是None (搞成匿名用户的示例) def authenticate_header(self, request): pass1
2
3
4
5
6
7
# 3. 认证流程原理
dispatch开始
# 1.APIView下的dispatch
    def dispatch(self, request, *args, **kwargs):
        """
        `.dispatch()` is pretty much the same as Django's regular dispatch,
        but with extra hooks for startup, finalize, and exception handling.
        """
        self.args = args
        self.kwargs = kwargs
        # 获取的参数
        # 把获取的参数,经过initialize_request构建成新的request
        request = self.initialize_request(request, *args, **kwargs)
        # 获取原生request,request._request
        # 获取认证类的对象,request.authenticators
        self.request = request
        self.headers = self.default_response_headers  # deprecate?
        try:
            self.initial(request, *args, **kwargs)
            # Get the appropriate handler method
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed
            response = handler(request, *args, **kwargs)
        except Exception as exc:
            response = self.handle_exception(exc)
        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 2.initialize_request  
 def initialize_request(self, request, *args, **kwargs):
        """
        Returns the initial request object.
        """
        parser_context = self.get_parser_context(request)
        return Request(  # 构建新的request
            request,
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(), # [BasicAuthentication(),]
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 3.get_authenticators
def get_authenticators(self):
        """
        Instantiates and returns the list of authenticators that this view can use.
        """
        return [auth() for auth in self.authentication_classes]  # 遍历身份认证对象
 2
3
4
5
6
# 4.perform_authentication 传入的对象进行身份认证 
def perform_authentication(self, request):  # 执行身份认证
    """
    Perform authentication on the incoming request.
    Note that if you override this and simply 'pass', then authentication
    will instead be performed lazily, the first time either
    `request.user` or `request.auth` is accessed.
    """
    request.user  # 进入封装的Respons方法
 2
3
4
5
6
7
8
9
10
# 5.原生的request
@property
def user(self):
    """
    Returns the user associated with the current request, as authenticated
    by the authentication classes provided to the request.
    """
    if not hasattr(self, '_user'):
        with wrap_attributeerrors():
            # 获取认证对象,进行一步的认证
            self._authenticate()  # 进入认证
    return self._user
 2
3
4
5
6
7
8
9
10
11
12
#  6.self._authenticate()、进入def _not_authenticated(self):方法,为None,或者有值
def _authenticate(self):
    """
    Attempt to authenticate the request using each authentication instance
    in turn.
    """
    # [BasicAuthentication对象,]
    for authenticator in self.authenticators:  # 遍历认证对象,执行认证方法
        try:
            # 执行认证类的authenticate方法
            # 1. 如果authenticate方法抛出异常,self_not_authenticate()执行
            # 2. 有返回值必须是元组:(request.user,request.auth)
            # 3. 返回None的话,下个认证来进行处理
            user_auth_tuple = authenticator.authenticate(self) # 执行这个对象的认证方法(认证是否以及登录),如果没有就报错
        except exceptions.APIException:
            self._not_authenticated()  # 没有进入_not_authenticated,返回对应的错误消息
            raise
        if user_auth_tuple is not None:  # 给响应的参数赋值
            self._authenticator = authenticator # 
            self.user, self.auth = user_auth_tuple  # 返回元组
            return  # 返回None下一个认证来处理
    self._not_authenticated()
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 自定义认证类示例
(一定要记住先做认证在做权限)
class Authtication(object):
    def authenticate(self, request):
        token = request._request.GET.get('token')
        token_obj = models.UserToken.objects.filter(token=token).first()
        if not token_obj:
            raise exceptions.AuthenticationFailed('用户未认证')
        return (token_obj.user, token_obj)  # 在rest fromwork内部会将这二个字段赋值给request,以供后续的使用(对应request.user,request.auth)
    def authenticate_header(self, request):
        pass
class OrderView(APIView):
    authentication_classes = [Authtication, ]  # 方式二:
    def get(self, request, *args, **kwargs):
        # 方式一:
        # token = request._request.GET.get('token')
        # if not token:
        #     raise exceptions.AuthenticationFailed('用户未认证')
        ret = {'code': 1000, 'message': 'ok!', 'data': None}
        try:
            ret['data'] = ORDER_DICT
        except Exception as e:
            ret['code'] = 1001
            ret['message'] = '找不到服务器'
        return JsonResponse(ret)
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 4. 梳理
1. 使用 
				- 创建类:继承BaseAuthentication; 实现:authenticate方法
				- 返回值:
					- None,我不管了,下一认证来执行。
					- raise exceptions.AuthenticationFailed('用户认证失败') # from rest_framework import exceptions
					- (元素1,元素2)  # 元素1赋值给request.user; 元素2赋值给request.auth 
					
				- 局部使用
					from rest_framework.authentication import BaseAuthentication,BasicAuthentication
					class UserInfoView(APIView):
						"""
						订单相关业务
						"""
						authentication_classes = [BasicAuthentication,]
						def get(self,request,*args,**kwargs):
							print(request.user)
							return HttpResponse('用户信息')
				- 全局使用:
					REST_FRAMEWORK = {
						# 全局使用的认证类
						"DEFAULT_AUTHENTICATION_CLASSES":['app02.utils.auth.FirstAuthtication','app02.utils.auth.Authtication', ],
						# "UNAUTHENTICATED_USER":lambda :"匿名用户"
						"UNAUTHENTICATED_USER":None, # 匿名,request.user = None
						"UNAUTHENTICATED_TOKEN":None,# 匿名,request.auth = None
					}
			2. 源码流程
				- dispatch
					- 封装request
						- 获取定义的认证类(全局/局部),通过列表生成时创建对象。
					- initial
						- perform_authentication
							request.user(内部循环....)
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32