Django rest_framework 源码学习笔记(三)鉴权相关
关于django DRF
的鉴权方式其实比较灵活,你可以采用自己引入jwt
,手动来控制,当然你也可以引入第三方库djangorestframework-jwt
。相对比之前,我更推荐使用djangorestframework-jwt
原因也很简单,出活快,功能全。
运行环境
djangorestframework 3.12.4
djangorestframework-jwt 1.11.0
django-cors-headers 3.11.0
django-filter 21.1
django-taggit 2.0.0
Markdown 3.3.6
Django 3.2.9
rest_framework_jwt.views
的方法
rest_framework_jwt.views
提供了三个对外的方法,分别是obtain_jwt_token
、refresh_jwt_token
、verify_jwt_token
obtain_jwt_token登陆
这个比较简单,只要使用post
方法,传递username
、password
两个参数就可以了。
在请求完毕后,服务器会反馈类似下面的信息。
POST /obtain_jwt_token/
HTTP 200 OK
Allow: POST, OPTIONS
Content-Type: application/json
Vary: Accept
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNjQ5ODIxOTI0LCJlbWFpbCI6IiJ9.eZbFu1532eIHW8OCUAn5qC2q-JaFdxntdaF2iUbygCQ"
}
对上面的jwt
进行base64
转码
# eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNjQ5ODIxOTI0LCJlbWFpbCI6IiJ9.eZbFu1532eIHW8OCUAn5qC2q-JaFdxntdaF2iUbygCQ
# 加密方式说明
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 = {"typ":"JWT","alg":"HS256"}
# jwt body
eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNjQ5ODIxOTI0LCJlbWFpbCI6IiJ9 = {"user_id":1,"username":"admin","exp":1649821924,"email":""}
refresh_jwt_token刷新Token
使用post
传递给他一个有效期内的token
,他会在返回一个新的给到你。类似延长续期。
Verify Json Web Token校验Token
使用post
传递给他一个有效期内的token
,他会验证是否正确。
补充说明
关于CSRF
的补充问题,可以查阅 djangoCSRF的使用和禁止说明-佩恩的博客
上手
demo1
先手动实现一个最基础的,
这个时候如果我通过127.0.0.1/dog/
则返回我{ "detail": "用户认证失败" }
,而如果是访问http://127.0.0.1:8000/dog/?token=123
就会返回{ "code": 20000, "msg": "OK" }
. 实现了基本的函数前置拦截验证
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import exceptions
class Myauthentication(object):
# 自己实现的认证类
def authenticate_header(self,val):
pass
def authenticate(self,request):
token = request._request.GET.get('token')
if not token:
raise exceptions.AuthenticationFailed('用户认证失败')
return ('admin',None)
class DogView(APIView):
authentication_classes = [Myauthentication,]
def get(self,request,*args,**kwargs):
ret = {
'code': 20000,
'msg':'OK'
}
return Response(ret)
demo2
在请求中完善以下4点。
- 认证
认证分为局部使用和全局使用,rest_framework
本身也提供了内置的认证类rest_framework.authentication
(都是基于Django本身认证实现)。
- BaseAuthentication
- BasicAuthentication
- SessionAuthentication
- TokenAuthentication
- RemoteUserAuthentication
# 局部使用,在view增加 authentication_classes =
class Myauthentication(BaseAuthentication):
# 自己实现的认证类
def authenticate(self,request):
token = request._request.GET.get('token')
if not token:
raise exceptions.AuthenticationFailed('用户认证失败')
return ('admin',None)
class DogView(APIView):
authentication_classes = [Myauthentication,]
def get(self,request,*args,**kwargs):
ret = {
'code': 20000,
'msg':'OK'
}
return Response(ret)
#全局使用,需要在settings 添加。api.utils.auth.Myauthentication,字符串路径
REST_FRAMEWORK = {
# 全局使用的认证类
"DEFAULT_AUTHENTICATION_CLASSES":['api.utils.auth.Myauthentication',],
"UNAUTHENTICATED_USER":None, # 匿名 request.user = None
"UNAUTHENTICATED_TOKEN":None, # 匿名 request.auth = None
}
- 权限
权限同样分为局部使用和全局使用,
rest_framework
本身也提供了内置的认证类rest_framework.permissions
(都是基于Django本身权限实现)。 - BasePermissionMetaclass
- AllowAny
- IsAuthenticated
- IsAdminUser
- IsAuthenticatedOrReadOnly
- DjangoModelPermissions
from rest_framework.permissions import BasePermission# 局部使用,自己可以实现各种验证规则
class MyPermission(BasePermission):
message = "无权访问" # 自定义无权时候返回的内容
def has_permission(self,request,view):
return False
class DogView(APIView):
# 权限
permission_classes = [MyPermission,]
def get(self,request,*args,**kwargs):
ret = {
'code': 20000,
'msg':'OK'
}
return Response(ret)
# 返回的是{
# "detail": "You do not have permission to perform this action."
#}
# 全局使用 DEFAULT_PERMISSION_CLASSES
REST_FRAMEWORK = {
# 全局使用的认证类
"DEFAULT_AUTHENTICATION_CLASSES":['api.utils.auth.Myauthentication',],
"UNAUTHENTICATED_USER":None, # 匿名 request.user = None
"UNAUTHENTICATED_TOKEN":None, # 匿名 request.auth = None
"DEFAULT_PERMISSION_CLASSES":['api.utils.permission.MyPermission',], # 统一的鉴权
}
- 节流
节流同样分为局部使用和全局使用,
rest_framework
本身也提供了内置的认证类rest_framework.throttling
。 - BaseThrottle
- SimpleRateThrottle
- AnonRateThrottle
- UserRateThrottle
- ScopedRateThrottle
# 局部使用
from rest_framework.throttling import SimpleRateThrottle
class VisitThrottle(SimpleRateThrottle):
# 标识
scope = 'guest'
def get_cache_key(self, request, view):
# 以IP来作为判断条件
return self.get_ident(request)
class DogView(APIView):
throttle_classes = [VisitThrottle,]
def get(self,request,*args,**kwargs):
ret = {
'code': 20000,
'msg':'OK'
}
return Response(ret)
# 全局使用 DEFAULT_THROTTLE_RATES
REST_FRAMEWORK = {
# 全局使用的认证类
"DEFAULT_AUTHENTICATION_CLASSES":['api.utils.auth.Myauthentication',],
"UNAUTHENTICATED_USER":None, # 匿名 request.user = None
"UNAUTHENTICATED_TOKEN":None, # 匿名 request.auth = None
"DEFAULT_PERMISSION_CLASSES":['api.utils.permission.MyPermission',], # 统一的鉴权
"DEFAULT_THROTTLE_CLASSES":['api.utils.throttle.VisitThrottle',], # 节流
# 节流 {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}
"DEFAULT_THROTTLE_RATES":{
"guest":'3/m'
}
- 版本
版本同样分为局部使用和全局使用,rest_framework
本身也提供了内置的认证类rest_framework.versioning
。
- BaseVersioning
- AcceptHeaderVersioning
- URLPathVersioning
- NamespaceVersioning
- HostNameVersioning
- QueryParameterVersioning
# 局部使用
from rest_framework.versioning import BaseVersioning
class ParamVersion(BaseVersioning):
def determine_version(self,request,*args,**kwargs):
version = request.query_params.get('version')
return version
class DogView(APIView):
versioning_class = ParamVersion
def get(self,request,*args,**kwargs):
ret = {
'code': 20000,
'msg':'OK'
}
print('request.version:',request.version)
return Response(ret)
# 全局使用
REST_FRAMEWORK = {
# 全局使用的认证类
"DEFAULT_AUTHENTICATION_CLASSES":['api.utils.auth.Myauthentication',],
"UNAUTHENTICATED_USER":None, # 匿名 request.user = None
"UNAUTHENTICATED_TOKEN":None, # 匿名 request.auth = None
"DEFAULT_PERMISSION_CLASSES":['api.utils.permission.MyPermission',], # 统一的鉴权
"DEFAULT_THROTTLE_CLASSES":['api.utils.throttle.VisitThrottle',], # 节流
# 节流 {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}
"DEFAULT_THROTTLE_RATES":{
"endpain":'3/m',
},
# 版本配置相关
"DEFAULT_VERSION":"v1",
"ALLOWED_VERSIONS":['1.0','1.1'],
"VERSION_PARAM":"version"
}
# 函数中可以通过 request.version 获取实际版本
但是实际我们用url传递参数的方式日常使用中还是比较少的。更多还是通过路径来获取
url = http://127.0.0.1:8000/myviews/?version=v1 # url参数传参
url = http://127.0.0.1:8000/v1/myviews/
类似上面这种设置也是比较简单的,可以直接做全局设置
# 版本配置相关
REST_FRAMEWORK = {
# 全局使用的认证类
"DEFAULT_AUTHENTICATION_CLASSES":['api.utils.auth.Myauthentication',],
"UNAUTHENTICATED_USER":None, # 匿名 request.user = None
"UNAUTHENTICATED_TOKEN":None, # 匿名 request.auth = None
"DEFAULT_PERMISSION_CLASSES":['api.utils.permission.MyPermission',], # 统一的鉴权
"DEFAULT_THROTTLE_CLASSES":['api.utils.throttle.VisitThrottle',], # 节流
# 节流 {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}
"DEFAULT_THROTTLE_RATES":{
"endpain":'30/m',
},
# 版本配置相关
"DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning",
"DEFAULT_VERSION":"v1",
"ALLOWED_VERSIONS":['1.0','1.1'],
"VERSION_PARAM":"version"
}
# 同时也要注意URL需要修改设置
# urls.py
from django.urls import path,re_path
from api.views import views
urlpatterns = [
re_path(r'^(?P<version>[v1|v2]+)/dog/$', views.DogView.as_view()),
]
#个人认为比较叼的功能就是,直接反向传递
re_path(r'^(?P<version>[v1|v2]+)/dog/$', views.DogView.as_view(),name='dog'),
request.versioning_scheme.reverse(
viewname='dog',
request=request
)
结合jwt
一起使用鉴权案例
没有实现自己的验证规则,基本就是配置一下,实现接口登陆访问,当然如果有需要自己权限等认证方式的话,相信看完上面的内容,你肯定也会自己在添加了。
# Settings.py
# REST_FRAMEWORK配置
REST_FRAMEWORK = {
# 全局认证器
"DEFAULT_AUTHENTICATION_CLASSES":[
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
],
# 使用JWT进行权限认证
"DEFAULT_PERMISSION_CLASSES":['rest_framework.permissions.IsAuthenticated'],
# 全局序列化器
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
),
}
import datetime
# JWT设置
JWT_AUTH = {
# Token编码方法
'JWT_ENCODE_HANDLER':
'rest_framework_jwt.utils.jwt_encode_handler',
# Token解码方法
'JWT_DECODE_HANDLER':
'rest_framework_jwt.utils.jwt_decode_handler',
'JWT_SECRET_KEY': settings.SECRET_KEY,
'JWT_GET_USER_SECRET_KEY': None,
'JWT_PUBLIC_KEY': None,
'JWT_PRIVATE_KEY': None,
# 算法
'JWT_ALGORITHM': 'HS256',
# 开启验证
'JWT_VERIFY': True,
# 开启验证过期时间
'JWT_VERIFY_EXPIRATION': True,
'JWT_LEEWAY': 0,
# 一天后Token过期
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
'JWT_AUDIENCE': None,
'JWT_ISSUER': None,
# 开启Token更新
'JWT_ALLOW_REFRESH': True,
# 一天后刷新的Token过期
'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=1),
# 默认Token前缀
'JWT_AUTH_HEADER_PREFIX': 'JWT',
'JWT_AUTH_COOKIE': None,
}
按照这个配置,然后在实际页面请求的时候按照下面的样子即可。
jwt = 'xxxxx.xxxxxxxxxx.xxxxxx'
url = 'http://127.0.0.1:8000/getdata/'
headers = {
"Authorization":f"JWT {jwt}"
}
requests.get(url,headers=headers)
req = requests.get()
req.json()