在drf中使用认证组件,要两个步骤。一是组件的编写,二是组件的应用。
组件应用
组件的应用,组件的应用分为两种模式,一种是全局应用,一种是局部应用。
- 全局应用就是写到drf的全局配置文件中,也就是django的
setting.py
文件的drf配置内容中。这种情况下,视图文件中所有的视图类都会默认带上全局配置中的认证组件。 - 局部应用是写到具体的视图类中,也就是写到views.py中。这种情况下,就只有视图类中配置了认证组件的视图类才会去认证。
先说组件应用方式的原因,是因为不同的应用,组件编写的位置是不同的。如果是全局应用,那么这个组件就一定不能 编写到视图文件views.py
中,而如果只是局部应用,那么组件的编写就是可以放到视图文件views.py
中的。
在drf中一般来说,认证组件应该是局部应用和全局应用都会一起用,所有这个组件就都不要写到视图文件view.py
中去。所以下面以组件不写到视图文件中,写到项目根目录下的utils/auth.py
目录下为例来分别介绍。
示例目录结构如下:
powershell
drf_study/$
├── apps/
│ └── api/
│ ├── migrations/
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── drf_study/
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── utils/
│ └── auth.py
├── db.sqlite3
├── manage.py
└── requirements.txt
同时我们在apps/api/views.py
中定义三个视图类:
- login,可以不认证直接访问。
- user,必须要认证才可以访问。
- order,必须要认证才可以访问。
python
# apps/api/views.py
from rest_framework.response import Response
from rest_framework.views import APIView
class LoginView(APIView):
def get(self, request):
context = {
'content':'login View',
}
return Response(context)
class UserView(APIView):
def get(self, request):
context = {
'content':'user View',
}
return Response(context)
class OrderView(APIView):
def get(self, request):
context = {
'content':'order View',
}
return Response(context)
# drf_study/urls.py
from django.urls import path
from apps.api import views
urlpatterns = [
path('login/', views.LoginView.as_view(), name='login'),
path('user/', views.UserView.as_view(), name='user'),
path('order/', views.OrderView.as_view(), name='order'),
]
全局应用
全局应用就是在setting.py文件中加入如下的配置项,主要配置项实际上就是组件的编写的路径,在上面的例子中,当然是utils/auth/MyAuthentication
。键就是固定的DEFAULT_AUTHENTICATION_CLASSES
,这个键是存在于APIView
这个类中的一个变量。
python
REST_FRAMEWORK = {
'UNAUTHENTICATED_USER': None,
# '键':['组件的路径,要具体到组件这个类']
'DEFAULT_AUTHENTICATION_CLASSES':['utils.auth.MyAuthentication']
}
配置了全局,访问下面三个都必须要认证,也就是说必须要带上token才可以。
python
path('login/', views.LoginView.as_view(), name='login'),
path('user/', views.UserView.as_view(), name='user'),
path('order/', views.OrderView.as_view(), name='order'),
如果要让login
不需要认证,就只需要在LoginView
中加上一个局部的认证配置项authentication_classes
,这个配置项的值是一个列表,为空就不需要进行认证。
python
class LoginView(APIView):
authentication_classes = [] # 这个就是在全局应用下的局部配置
def get(self, request):
context = {
'content':'login View',
}
return Response(context)
如果全局配置和局部配置都有值,那么drf会只取局部配置中的值,全局的就不再生效了。
这里再来说一下,为什么全局应用的情况下,为什么组件不可以写到views.py
中。
如果把认证组件写到views.py
中,在全局应用下,drf在加载setting.py
的时候会去加载组件的路径,也就是views.py
中的那个组件,而在加载views.py
的中的视图类的时候,又会去加载在views.py
中导入的APIView
,而在APIView
中又会去读取api_setting
中全局配置中的值,而drf中api_setting
就是指的setting.py
中的drf的配置,然后又会去views.py
中找这个组件,导致死循环了,所有全局应用下,认证组件不可以写到views.py
中。
局部应用
局部应用可以直接写到views.py视图中,只需要把utils/auth.py中的内容在views.py中写进去就可以了,就是把组件MyAuthentication
写到views.py文件中。然后再在后续的每个视图类中,分别去应用主键就可以了。
python
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
class MyAuthentication(BaseAuthentication):
def authenticate(self, request):
token = request.query_params.get('token')
# 认证成功,假设获取到token就算认证成功
if token:
return ('stark', token)
# 认证失败返回
context = {
'code':2000,
'error':'认证失败'
}
raise AuthenticationFailed(context)
class LoginView(APIView):
authentication_classes = [] # 列表中没有组件,就不会去做认证操作
def get(self, request):
context = {
'content':'login View',
}
return Response(context)
class UserView(APIView):
authentication_classes = [MyAuthentication,] # 局部应用中就是在每个视图类中增加这个配置,注意不加引号了
def get(self, request):
context = {
'content':'user View',
}
return Response(context)
class OrderView(APIView):
authentication_classes = [MyAuthentication,] # 局部应用中就是在每个视图类中增加这个配置,注意不加引号了
def get(self, request):
context = {
'content':'order View',
}
return Response(context)
组件编写
组件的编写就是编写一个类,在上面应用中的就是一个认证组件类。
python
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed # 抛出异常需要用到这个
class MyAuthentication(BaseAuthentication):
def authenticate(self, request):
token = request.query_params.get('token')
# 认证成功,假设获取到token就算认证成功
if token:
return ('stark', token)
# 认证失败返回
context = {
'code':2000,
'error':'认证失败'
}
raise AuthenticationFailed(context)
这个认证组件类要继承自rest_framework.authentication.BaseAuthentication
,同时里面必须要定义一个函数authenticate
,这个函数中具体来定义认证流程。上面的例子中,假设的就是只要在链接中获取到了token
的值就算认证成功。
在BaseAuthentication
的authenticate
函数中,就是做认证具体的地方,在这个函数中,一般主要做三件事情:
- 读取请求传递的token
- 校验token的合法性
- 返回结果,而返回结果一般来说有三种情况
- 返回元组,认证成功,必须返回包含两个参数的元组。这两个参数就是
(user,auth)
,返回了之后,后续的用这个组件的视图类中就可以使用request.user
、request.auth
来获取这两个值。注意这里的request
是drf中的Request
对象中的request
。 - 抛出异常,认证失败,返回错误信息。抛出异常就需要用到
AuthenticationFailed
类。 - 返回None。如果认证组件有多个类,比如
[类1,类2,类3]
,那么在类1authenticate
认证返回None的时候,就会去类2中执行authenticate
函数,如果还是返回None,就去类3中执行authenticate
,如果都返回None,那么就返回匿名用户,如果中间有一个认证成功或者认证失败,就不往下去执行了。这个None有点像表达"我不知道该认证成功,还是认证失败,所以返回None"。
- 返回元组,认证成功,必须返回包含两个参数的元组。这两个参数就是
这里面获取token的这句本质上也是调用的django的request.GET.get('token')
,django中的query_params
就是_request.GET
。这里的query_params
后跟的.get
实际上字典自带的一个内置方法get()
。
小知识点:
- 在python中
dict['key']
和dict.get('key')
的区别主要在于get()
方法可以配置一个默认值,即使从字典中没有取到值,也不会报错。get()
方法默认返回None,dict.get(key, default=None)
。
源码示意流程
认证组件的加载与authenticate
函数的执行

状态码的问题
是指响应头中的状态码,比如最上面如果访问path('user/', views.UserView.as_view(), name='user'),
的时候没有认证通过,返回的是 HTTP 403 Forbidden
:
python
**HTTP 403 Forbidden**
**Allow:** GET, HEAD, OPTIONS
**Content-Type:** application/json
**Vary:** Accept
{
"code": "2000",
"error": "认证失败"
}
但是实际上在源码中的流程大概是这样:

所以在认证类中也需要加上authenticate_header
方法。
python
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
class MyAuthentication(BaseAuthentication):
def authenticate(self, request):
token = request.query_params.get('token')
# 认证成功,假设获取到token就算认证成功
if token:
return ('stark', token)
# 认证失败返回
context = {
'code':2000,
'error':'认证失败'
}
raise AuthenticationFailed(context)
def authenticate_header(self, request):
return 'API' #随便返回什么,然后就可以正常响应到响应头中
加了authenticate_header
方法后的响应:
python
**HTTP 401 Unauthorized** # 状态码变成正常的401
**Allow:** GET, HEAD, OPTIONS
**Content-Type:** application/json
**Vary:** Accept
**WWW-Authenticate:** API # 这就是authenticate_header方法返回的内容
{
"code": "2000",
"error": "认证失败"
}