认证
# 链接资料
博客园极其内向的帅小伙
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): pass
1
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