认证、权限、频率、自定义签发token-多方式登录
三大认证流程图
路由配置
在应用下新建文件router.py
# router.py
from rest_framework.routers import Route, DynamicRoute, SimpleRouter as DRFSimpleRouter
class SimpleRouter(DRFSimpleRouter):
routes = [
# List route.
Route(
url=r'^{prefix}{trailing_slash}$',
mapping={
'get': 'list',
'post': 'create',
'put': 'multiply_update',
'patch': 'multiply_partial_update',
'delete': 'multiply_destroy'
},
name='{basename}-list',
detail=False,
initkwargs={'suffix': 'List'}
),
# Dynamically generated list routes. Generated using
# @action(detail=False) decorator on methods of the viewset.
DynamicRoute(
url=r'^{prefix}/{url_path}{trailing_slash}$',
name='{basename}-{url_name}',
detail=False,
initkwargs={}
),
# Detail route.
Route(
url=r'^{prefix}/{lookup}{trailing_slash}$',
mapping={
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy'
},
name='{basename}-detail',
detail=True,
initkwargs={'suffix': 'Instance'}
),
# Dynamically generated detail routes. Generated using
# @action(detail=True) decorator on methods of the viewset.
DynamicRoute(
url=r'^{prefix}/{lookup}/{url_path}{trailing_slash}$',
name='{basename}-{url_name}',
detail=True,
initkwargs={}
),
]
router = SimpleRouter()
urls.py
# urls.py
from django.conf.urls import url, include
from .router import router
from . import views
router.register('users', views.UserListAPIViewSet, basename='user')
urlpatterns = [
url(r'', include(router.urls))
]
认证组件
配置drf-jwt
框架的认证类 认证组件只能决定request.user,不是断定权限的地方,所以一般配置全局
# settings.py
REST_FRAMEWORK = {
# 认证组件
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_jwt.authentication.JSONWebTokenAuthentication'
],
}
新建文件authentications.py
,自定义认证
from rest_framework.authentication import BaseAuthentication
# 自定义认证类
# 如果使用session认证,drf默认提供了SessionAuthentication
# 如果使用drf-jwt认证,drf-jwt默认提供了JSONWebTokenAuthentication
# 如果自定义签发与校验token,才需要将校验token的算法封装到自定义的认证类
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
class MyAuthentication(BaseAuthentication):
def authenticate(self, request):
pass
# 1、从请求头中拿到前台提交的token(一般从HTTP_AUTHORIZATION中拿,也可以与前台约定)
# 如果设置了反爬
# 2、没有token,返回None,代表游客
# 3、有token,进入校验 不通过,抛异常,代表非法用户;通过,返回(user, token),代表合法用户
自定义认证规则:
-
从请求头中拿到前台提交的token(一般从HTTP_AUTHORIZATION中拿,也可以与前台约定)
- 如果设置了反爬等措施,校验一下反爬(头 token)
-
没有token,返回None,代表游客
-
有token,进入校验
- 不通过,抛异常,代表非法用户
- 通过,返回(user, token),代表合法用户
认证组件源码分析:
权限组件
权限类就是实现 BasePermission类,重写has_permission 方法,如果有权限返回True,没权限返回False。
局部配置权限:
# views.py
# 权限组件
permission_classes = [permissions.MyPermission]
自定义权限类
# permissions.py
from rest_framework.permissions import BasePermission
class MyPermission(BasePermission):
def has_permission(self, request, view):
"""
根据需求,request和view的辅助,制定权限规则判断条件
如果条件通过,返回True
如果条件不通过,返回False
"""
pass
配置drf自带的权限类
- drf默认提供了一些权限类
- AllowAny:游客和登录用户全权限
- IsAuthenticated:只有登录用户有全权限
- IsAdminUser:只有后台用户(admin)有全权限
- IsAuthenticatedOrReadOnly:游客有读权限,登录用户有全权限
- 如果有特殊需要,需要自定义权限类
- 如:只有superuser有权限、只有vip用户有权限、只有某ip网段用户有权限、只有某个视图及其子类有权限
# views.py
from rest_framework.viewsets import GenericViewSet
from rest_framework import mixins
from .response import APIResponse
from rest_framework.permissions import IsAuthenticated
class UserListAPIViewSet(mixins.ListModelMixin, GenericViewSet):
permission_classes = [IsAuthenticated]
pass
drf-jwt 签发token源码分析
"""
drf-jwt 签发token
1、username、password 通过auth组件的authenticate方法得到user对象
2、user对象、通过drf-jwt框架的jwt_payload_handler函数包装 payload载荷
3、payload载荷 通过drf-jwt框架的jwt_encode_handler函数签发 token字符串
注:可以借助jwt_payload_handler和jwt_encode_handler 两个函数完成自定义token的签发
"""
多方式登录 签发token
-
token只能 由在登录接口 签发
-
登录接口也是APIView的子类,使用一定会进行 认证、权限、组件的校验
-
结论:不管系统默认、或是全局settings配置的是何 认证与权限组件,登录接口不用参与任何认证与权限的校验。所以,登录接口一定要进行 认证与权限的局部禁用。
from rest_framework.views import APIView
class LoginAPIView(APIView):
authentication_classes = []
permission_classes = []
def post(self, request, *args, **kwargs):
serializers = serializer.LoginModelSerializer(data=request.data)
serializers.is_valid(raise_exception=True) # 内部在全局钩子中完成token的签发
return APIResponse(results={
'username': '',
'token': ''
})
这样会有个问题:post方法默认走的是数据库的增方法
默认的校验规则里面是走的数据库,我们可以自定义字段
# 序列化类 serializer.py
from rest_framework_jwt.serializers import jwt_payload_handler, jwt_encode_handler
import re
class LoginModelSerializer(serializers.ModelSerializer):
username = serializers.CharField(min_length=3, max_length=16)
password = serializers.CharField(min_length=3, max_length=16)
class Meta:
model = models.User
fields = ['username', 'password']
# 用全局钩子,完成token的签发
def validate(self, attrs):
# 1、通过username、password完成多方式登录校验,得到user对象
user = self._validate_user(attrs)
# 2、user对象包装怕payload载荷
payload = jwt_payload_handler(user)
# 3、payload载荷签发token
token = jwt_encode_handler(payload)
# 4、将user与token存储到serializer对象中,方便在视图类中使用
self.content = {
'user': user,
'token': token
}
return attrs
def _validate_user(self, attrs):
username = attrs.get('username')
password = attrs.get('password')
if re.match(r'.*@.*', username): # 邮箱
user = models.User.objects.filter(email=username).first()
elif re.match(r'^1[1-9][0-9]{9}$', username): # 电话
user = models.User.objects.filter(mobile=username).first()
else: # 用户名
user = models.User.objects.filter(username=username).first()
if not user or not user.check_password(password):
raise serializers.ValidationError({'message': '用户信息异常'})
return user
# views.py
from rest_framework.views import APIView
class LoginAPIView(APIView):
authentication_classes = []
permission_classes = []
def post(self, request, *args, **kwargs):
serializers = serializer.LoginModelSerializer(data=request.data)
serializers.is_valid(raise_exception=True) # 内部在全局钩子中完成token的签发
print(serializers.content)
return APIResponse(results={
'username': serializers.content.get('user').username,
'token': serializers.content.get('token')
})
总结:认证与权限绑定使用
- 每一个视图类都要进行认证,且认证规则一致,多余全局配置认证即可
- 每一个视图类都要进行权限校验,默认配置的是 不限制(AllowAny),但实际开发中,视图类的访问权限不完全相同,所以要在具体的视图类,配置具体的权限规则
VIP用户认证权限例子
# views.py
from rest_framework.viewsets import ViewSet
class UserViewSet(ViewSet):
# 权限:只要VIP用户可以查看 个人详细信息
permission_classes = [permissions.VIPUserPermission]
def retrieve(self, request, *args, **kwargs):
return APIResponse(results={
'username': request.user.username,
'email': request.user.username,
'mobile': request.user.username,
'data_joined': request.user.date_joined,
})
# permission.py
from rest_framework.permissions import BasePermission
# VIP用户权限
class VIPUserPermission(BasePermission):
def has_permission(self, request, view):
for group in request.user.groups.all():
if group.name.lower() == 'vip':
return True
return False
频率组件
# views.py
class UserListAPIViewSet(mixins.ListModelMixin, GenericViewSet):
from rest_framework.throttling import AnonRateThrottle, UserRateThrottle
# 频率组件
# throttle_classes = [AnonRateThrottle]
throttle_classes = [UserRateThrottle]
queryset = models.User.objects.filter(is_active=True).all()
serializer_class = serializer.UserModelSerializer
def update(self, request, *args, **kwargs):
return APIResponse()
def multiply_update(self, request, *args, **kwargs):
return APIResponse(msg='群改')
在settings.py配置文件中配置
REST_FRAMEWORK = {
# 频率组件,频率类一般做局部配置,但是频率调节在settings中配置
'DEFAULT_THROTTLE_RATES': {
'user': '5/min', # 登录用户限制
'anon': '3/min', # 匿名用户限制
},
}
自定义频率类
自定义频率类:
- drf默认提供了一些频率类
- AnonRateThrottle:只对游客进行频率限制
- UserRateThrottle:对所有用户进行频率限制
- 如果由特殊需要,需要自定义频率类
- 如:对ip进行限次、对电话进行限次、对视图某些信息进行限次
方法步骤:
- 设置scope字符串类属性,同时在settings中进行drf配置DEFAULT_THROTTLE_RATES
- 重写get_catch_key方法
- 返回与限制条件有关的字符串,表示限制
- 返回None,表示不限制
# throttles.py
from rest_framework.throttling import SimpleRateThrottle
class MobileReateThrottle(SimpleRateThrottle):
scope = 'mobile'
def get_cache_key(self, request, view):
if not request.user.is_authenticated or not request.user.mobile:
return None # 匿名用户 没有电话号的用户都不限制
return self.cache_format % {
'scope': self.scope,
'ident': request.user.mobile
}