在开发API过程中,有些功能需要登录才能访问,有些无需登录。drf中的认证组件主要就是用来实现此功能
1. 快速使用
通过编写类的形式实现,一个类就是一个认证组件
- 编写类 -> 认证组件
- 应用组件
from rest_framework.authentication import BaseAuthentication
class MyAuthentication(BaseAuthentication):
def authenticate(self, request):
# 去做用户认证
# 1.读取请求传递的token
# 2.校验合法性
# 3.返回值
# 3.1 返回元组 (111,222) 认证成功 request.user request.auth
# 3.2 抛出异常 认证失败 -> 返回错误信息
# 3.1 返回None 多个认证类 [类1,类2,类3,类4,,,] -> 匿名用户
pass
案例1
项目要开发3个接口,其中1个无需登录接口、2个必须登录才能访问的接口。
局部配置 authentication_classes = []
- 认证失败信息定制
- 匿名用户
- 认证组件中返回的两个值,分别赋值给:
request.user
和request.auth
urls.py
from django.urls import path
from api import views
urlpatterns = [
path('login/', views.LoginView.as_view()),
path('user/', views.UserView.as_view()),
path('order/', views.OrderView.as_view()),
]
views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.request import Request
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
class MyAuthentication(BaseAuthentication):
def authenticate(self, request):
# /xxx/xxx/xxx?token=123123123
# token = request._request.GET.get('token')
token = request.query_params.get('token')
if token:
return "user", token
# raise AuthenticationFailed("认证失败")
raise AuthenticationFailed({"code": 20000, "error": "认证失败"})
class LoginView(APIView):
authentication_classes = []
def get(self, request):
print(request.user, request.auth)
return Response("LoginView")
class UserView(APIView):
authentication_classes = [MyAuthentication, ]
def get(self, request):
print(request.user, request.auth)
return Response("UserView")
class OrderView(APIView):
authentication_classes = [MyAuthentication, ]
def get(self, request):
print(request.user, request.auth)
return Response("OrderView")
案例2
项目要开发100个接口,其中1个无需登录接口、99个必须登录才能访问的接口。
此时,就需要用到drf的全局配置(认证组件的类不能放在视图view.py中,会因为导入APIView导致循环引用,该案例放置在ext/auth.py中)
drf会先加载全局配置,在加载局部配置,局部配置会覆盖全局配置
ext\auth.py
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
class MyAuthentication(BaseAuthentication):
def authenticate(self, request):
# /xxx/xxx/xxx?token=123123123
# token = request._request.GET.get('token')
token = request.query_params.get('token')
if token:
return "user", token
# raise AuthenticationFailed("认证失败")
raise AuthenticationFailed({"code": 20000, "error": "认证失败"})
urls.py
from django.urls import path
from api import views
urlpatterns = [
path('login/', views.LoginView.as_view()),
path('user/', views.UserView.as_view()),
path('order/', views.OrderView.as_view()),
]
views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from ext.auth import MyAuthentication
class LoginView(APIView):
authentication_classes = []
def get(self, request):
print(request.user, request.auth)
return Response("LoginView")
class UserView(APIView):
def get(self, request):
print(request.user, request.auth)
return Response("UserView")
class OrderView(APIView):
def get(self, request):
print(request.user, request.auth)
return Response("OrderView")
案例3
状态码一致性问题:
如果认证组件中未定义,则统一为 HTTP_403_FORBIDDEN如果自定义了,还会加上一个响应头
- 参考源码
def handle_exception(self, exc):
if isinstance(exc, (exceptions.NotAuthenticated,exceptions.AuthenticationFailed)):
auth_header = self.get_authenticate_header(self.request)
if auth_header:
exc.auth_header = auth_header
else:
exc.status_code = status.HTTP_403_FORBIDDEN
return response
def get_authenticate_header(self, request):
authenticators = self.get_authenticators()
if authenticators:
return authenticators[0].authenticate_header(request)
案例4
项目要开发100个接口,其中1个无需登录接口、98个必须登录才能访问的接口、1个公共接口(未登录时显示公共/已登录时显示个人信息)。
原来的认证信息只能放在URL中传递,如果程序中支持放在很多地方,例如:URL中、请求头中等。
认证组件中,如果是使用了多个认证类,会按照顺序逐一执行其中的
authenticate
方法
- 返回None或无返回值,表示继续执行后续的认证类
- 返回 (user, auth) 元组,则不再继续并将值赋值给request.user和request.auth
- 抛出异常
AuthenticationFailed(...)
,认证失败,不再继续向后走。多个认证组件每一个都返回None,都没有认证成功,视图函数也会被执行,只不过
self.user,self.auth
为None
ext\auth.py
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
class ParamAuthentication(BaseAuthentication):
def authenticate(self, request):
token = request.query_params.get('token')
if not token:
return
return "user", token
def authenticate_header(self, request):
return 'Token'
class HeaderAuthentication(BaseAuthentication):
def authenticate(self, request):
token = request.META.get('HTTP_AUTHORIZATION')
if not token:
return
return "user", token
def authenticate_header(self, request):
return 'Token'
class NotAuthentication(BaseAuthentication):
def authenticate(self, request):
raise AuthenticationFailed({"code": 20000, "error": "认证失败"})
def authenticate_header(self, request):
return 'Token'
views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from ext.auth import ParamAuthentication, HeaderAuthentication
# 无需登录
class UserView(APIView):
authentication_classes = []
def get(self, request):
return Response("用户信息")
# 公共接口
class OrderView(APIView):
authentication_classes = [ParamAuthentication, HeaderAuthentication, ]
def get(self, request):
if request.user:
message = f"{request.user}的订单信息"
else:
message = "公共订单信息"
return Response(message)
# 必须登录
class InfoView(APIView):
def get(self, request):
message = f"{request.user}的用户信息"
return Response(message)
settings.py
REST_FRAMEWORK = {
"UNAUTHENTICATED_USER": None, # 未登录时,request.user=None
"UNAUTHENTICATED_TOKEN": None, # 未登录时,request.auth=None
"DEFAULT_AUTHENTICATION_CLASSES": [
"ext.auth.ParamAuthentication",
"ext.auth.HeaderAuthentication",
"ext.auth.NotAuthentication",
]
}
2. 面向对象-继承
class APIView(View):
authentication_classes = 读取配置文件中的列表
def dispatch(self):
self.authentication_classes
class UserView(APIView):
authentication_classes = [11,22,33,44]
obj = UserView()
obj.dispatch() # authentication_classes = [11,22,33,44]
3. 组件源码
class Request:
def __init__(self, request, authenticators=None):
self._request = request
self.authenticators = authenticators or ()
@property
def user(self):
if not hasattr(self, '_user'):
with wrap_attributeerrors():
self._authenticate()
return self._user
@user.setter
def user(self, value):
self._user = value
self._request.user = value
def _authenticate(self):
# authenticator是认证类实例化后的对象,循环逐一认证
# 如果认证过程抛出异常,认证不通过
# 若认证过程没有抛出异常,也没有返回self.user, self.auth,则匿名执行视图函数
for authenticator in self.authenticators:
try:
user_auth_tuple = authenticator.authenticate(self) # 调用认证类的authenticate方法,返回元组
except exceptions.APIException:
self._not_authenticated()
raise
if user_auth_tuple is not None:
self._authenticator = authenticator
self.user, self.auth = user_auth_tuple
return
self._not_authenticated()
def _not_authenticated(self):
self._authenticator = None
if api_settings.UNAUTHENTICATED_USER:
self.user = api_settings.UNAUTHENTICATED_USER()
else:
self.user = None
if api_settings.UNAUTHENTICATED_TOKEN:
self.auth = api_settings.UNAUTHENTICATED_TOKEN()
else:
self.auth = None
class APIView(View):
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
def as_view(cls, **initkwargs):
view = super().as_view(**initkwargs)
return csrf_exempt(view)
def dispatch(self, request, *args, **kwargs):
# 第一步:请求的封装(django的 request + drf的 authenticators认证组件) --> 加载认证组件过程
# request.authenticators [对象1,对象2,...]
request = self.initialize_request(request, *args, **kwargs)
self.request = request
# 第二步:认证过程
try:
self.initial(request, *args, **kwargs)
handler = getattr(self, request.method.lower(), 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
def initialize_request(self, request, *args, **kwargs):
return Request(
request,
authenticators=self.get_authenticators(), # [对象1,对象2,...]
)
def initial(self, request, *args, **kwargs):
self.perform_authentication(request)
self.check_permissions(request)
self.check_throttles(request)
def perform_authentication(self, request):
request.user
def get_authenticators(self):
# 返回一个列表每一个元素都是认证类对象 [对象1,对象2,...]
return [auth() for auth in self.authentication_classes] # 优先读取局部配置,后全局配置
def handle_exception(self, exc):
if isinstance(exc, (exceptions.NotAuthenticated,exceptions.AuthenticationFailed)):
auth_header = self.get_authenticate_header(self.request)
if auth_header:
exc.auth_header = auth_header
else:
exc.status_code = status.HTTP_403_FORBIDDEN
return response
def get_authenticate_header(self, request):
authenticators = self.get_authenticators()
if authenticators:
return authenticators[0].authenticate_header(request)
class View:
def as_view(cls, **initkwargs):
def view(request, *args, **kwargs):
return self.dispatch(request, *args, **kwargs)
return view
class UserView(APIView):
def get(self, request):
print(request.user, request.auth)
return Response("UserView")
4. 拓展-子类约束
class Foo(object):
# 对子类进行约束,约束子类中必须要定义该方法,不然就报错
def f1(self):
raise NotImplementedError("...")
class Bar(Foo):
def f1(self):
print("123")
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.")