drf框架之三大认证、异常组件
三大认证
认证模块
案例
多方式登录
urls.py
# 自定义登录(重点):post请求 => 查操作(签发token返回给前台) - 自定义路由映射
url('^user/login/$', views.LoginViewSet.as_view({'post': 'login'})),
views.py
# 重点:自定义login,完成多方式登录
from rest_framework.viewsets import ViewSet
from rest_framework.response import Response
class LoginViewSet(ViewSet):
# 需要和mixins结合使用,继承GenericViewSet,不需要则继承ViewSet
# 为什么继承视图集,不去继承工具视图或视图基类,因为视图集可以自定义路由映射:
# 可以做到get映射get,get映射list,还可以做到自定义(灵活)
def login(self, request, *args, **kwargs):
serializer = serializers.LoginSerializer(data=request.data, context={'request': request})
serializer.is_valid(raise_exception=True)
token = serializer.context.get('token')
return Response({"token": token})
serializers.py
from rest_framework_jwt.serializers import jwt_payload_handler, jwt_encode_handler
# 重点:自定义login,完成多方式登录
class LoginSerializer(serializers.ModelSerializer):
# 登录请求,走的是post方法,默认post方法完成的是create入库校验,所以唯一约束的字段,会进行数据库唯一校验,导致逻辑相悖
# 需要覆盖系统字段,自定义校验规则,就可以避免完成多余的不必要校验,如唯一字段校验
username = serializers.CharField()
class Meta:
model = models.User
# 结合前台登录布局:采用账号密码登录,或手机密码登录,布局一致,所以不管账号还是手机号,都用username字段提交的
fields = ('username', 'password')
def validate(self, attrs):
# 在全局钩子中,才能提供提供的所需数据,整体校验得到user
# 再就可以调用签发token算法(drf-jwt框架提供的),将user信息转换为token
# 将token存放到context属性中,传给外键视图类使用
user = self._get_user(attrs)
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
self.context['token'] = token
return attrs
# 多方式登录
def _get_user(self, attrs):
username = attrs.get('username')
password = attrs.get('password')
import re
if re.match(r'^1[3-9][0-9]{9}$', username):
# 手机登录
user = models.User.objects.filter(mobile=username, is_active=True).first()
elif re.match(r'^.+@.+$', username):
# 邮箱登录
user = models.User.objects.filter(email=username, is_active=True).first()
else:
# 账号登录
user = models.User.objects.filter(username=username, is_active=True).first()
if user and user.check_password(password):
return user
raise ValidationError({'user': 'user error'})
token刷新机制(了解)
drf-jwt直接提供了刷新功能
- 运用在像12306这样极少数安全性要求高的网站
- 第一个token由登录签发
- 之后的所有正常逻辑,都需要发送两次请求,第一次是刷新token的请求,第二次是正常逻辑的请求
settings.py
import datetime
JWT_AUTH = {
# 配置过期时间
'JWT_EXPIRATION_DELTA': datetime.timedelta(minutes=5),
# 是否可刷新
'JWT_ALLOW_REFRESH': True,
# 刷新过期时间
'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),
}
urls.py
from rest_framework_jwt.views import ObtainJSONWebToken, RefreshJSONWebToken
urlpatterns = [
url('^login/$', ObtainJSONWebToken.as_view()), # 登录签发token接口
url('^refresh/$', RefreshJSONWebToken.as_view()), # 刷新toekn接口
]
postman
# 接口:/api/refresh/
# 方法:post
# 数据:{"token": "登录签发的token"}
权限组件
案例
vip权限认证
数据准备
- User表创建两条数据
- Group表创建一条数据,name叫vip
- 操作User和Group的关系表,让1号用户属于1号vip组
permissions.py
from rest_framework.permissions import BasePermission
from django.contrib.auth.models import Group
class IsVipUser(BasePermission):
def has_permission(self, request, view):
if request.user and request.user.is_authenticated: # 必须是合法用户
try:
vip_group = Group.objects.get(name='vip')
if vip_group in request.user.groups.all(): # 用户可能不属于任何分组
return True # 必须是vip分组用户
except:
pass
return False
views.py
from .permissions import IsVipUser
class CarViewSet(ModelViewSet):
permission_classes = [IsVipUser]
queryset = models.Car.objects.all()
serializer_class = serializers.CarSerializer
serializers.py
class CarSerializer(serializers.ModelSerializer):
class Meta:
model = models.Car
fields = ('name', )
urls.py
router.register('cars', views.CarViewSet, 'car')
频率组件(重点)
- 如何自定义频率类
- 频率校验的规则
- 自定义频率类是最常见的:短信验证码接口一分钟只能发送一条短信
自定义频率类
- 自定义类继承SimpleRateThrottle
- 设置类实现scope,值就是一个字符串,与settings中的DEFAULT_THROTTLE_RATES进行对应 DEFAULT_THROTTLE_RATES就是设置scope绑定的类的频率规则:1/min 就代表一分钟只能访问一次
- 重写 get_cache_key(self, request, view) 方法,指定限制条件 不满足限制条件,返回None:代表对这类请求不进行频率限制 满足限制条件,返回一个字符串(是动态的):代表对这类请求进行频率限制 短信频率限制类,返回 “throttling_%(mobile)s” % {“mobile”: 实际请求来的电话}
系统频率类
1. UserRateThrottle:限制所有用户访问频率
2. AnonRateThrottle:只限制匿名用户访问频率
案例
请求方式的频率限制
throttles.py
from rest_framework.throttling import SimpleRateThrottle
# 只限制查接口的频率,不限制增删改的频率
class MethodRateThrottle(SimpleRateThrottle):
scope = 'method'
def get_cache_key(self, request, view):
# 只有对get请求进行频率限制
if request.method.lower() not in ('get', 'head', 'option'):
return None
# 区别不同的访问用户,直接的限制是不冲突的
if request.user.is_authenticated:
ident = request.user.pk
else:
# get_ident是BaseThrottle提供的方法,会根据请求头,区别匿名用户,
# 保证不同客户端的请求都是代表一个独立的匿名用户
ident = self.get_ident(request)
return self.cache_format % {'scope': self.scope, 'ident': ident}
settings.py
REST_FRAMEWORK = {
# ...
# 频率规则配置
'DEFAULT_THROTTLE_RATES': {
# 只能设置 s,m,h,d,且只需要第一个字母匹配就ok,m = min = maaa 就代表分钟
'user': '3/min', # 配合drf提供的 UserRateThrottle 使用,限制所有用户访问频率
'anon': '3/min', # 配合drf提供的 AnonRateThrottle 使用,只限制匿名用户访问频率
'method': '3/min',
},
}
views.py
from .permissions import IsVipUser
from .throttles import MethodRateThrottle
class CarViewSet(ModelViewSet):
permission_classes = [IsVipUser]
throttle_classes = [MethodRateThrottle]
queryset = models.Car.objects.all()
serializer_class = serializers.CarSerializer
异常组件
记录异常信息到日志文件(一般配合日志模块使用),因为后台的程序异常返回给前台没有意义,通常记录错误日志便于修改bug(日志模块联合使用后面博客补充)
exception.py
from rest_framework.views import exception_handler as drf_exception_handler
from rest_framework.response import Response
def exception_handler(exc, context):
# 只处理客户端异常,不处理服务器异常,
# 如果是客户端异常,response就是可以直接返回给前台的Response对象
response = drf_exception_handler(exc, context)
if response is None:
# 没有处理的服务器异常,处理一下
# 其实给前台返回 服务器异常 几个字就行了
# 那我们处理异常模块的目的是 不管任何错误,都有必要进行日志记录(线上项目只能通过记录的日志查看出现过的错误)
response = Response({'detail': '%s' % exc})
# 需要结合日志模块进行日志记录的:项目中讲
return response
settings.py
REST_FRAMEWORK = {
# ...
# 异常模块
# 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler', # 原来的,只处理客户端异常
'EXCEPTION_HANDLER': 'api.exception.exception_handler',
}