三大认证使用及异常处理

三大认证使用及异常处理

1、系统权限类使用

图书接口:游客只读,用户可增删改权限使用

from rest_framework.permissions import IsAuthenticatedOrReadOnly
# ModelViewset即可增删改查
class BookViewSet(ModelViewSet):
    # 游客只读,用户可增删改查
    permission_classes = [IsAuthenticatedOrReadOnly]
​
    queryset = models.Book.objects.all()
    serializer_class = serializers.BookSerializer

2、特殊路由映射的请求

用主键为2的账号登录获得的token,却能够通过 /api/user/center/1/ 访问的是主键为1的账号信息,这是有问题的。如何实现用 /api/user/center/ 接口访问的是自己的详情信息:

实现用户中心信息自查,不带主键的get请求,走单查逻辑

urls.py

# 我们不走pk直接实现只能单查逻辑就不会出现可以用主键1的token访问2的账号
urlpatterns = [
    # ...
    # /user/center/ => 单查,不能走路由组件,只能自定义配置映射关系
    url('^user/center/$', views.UserCenterViewSet.as_view({'get': 'user_center'})),
]

views.py

from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
class UserCenterViewSet(GenericViewSet):
    permission_classes = [IsAuthenticated, ]
    queryset = models.User.objects.filter(is_active=True).all()
    serializer_class = serializers.UserCenterSerializer
​
    def user_center(self, request, *args, **kwargs):
        # request.user就是前台带token,在经过认证组件解析出来的,
        # 再经过权限组件IsAuthenticated的校验,所以request.user一定有值,就是当前登录用户
        serializer = self.get_serializer(request.user)
        return Response(serializer.data)

3、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接口
]

测试:

post请求获取token,再通过刷新接口发送带token的数据即可

4、认证组件项目使用:多方式登录

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):
    # 登录接口,要取消所有的认证与权限规则,也就是要做局部禁用操作(空配置)
    authentication_classes = []
    permission_classes = []
​
    # 需要和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)  # 将user放入产生payload
        token = jwt_encode_handler(payload)  # 将payload放入产生token
        self.context['token'] = 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'})

5、权限组件项目使用:VIP用户权限

首先在user表中有两个用户,然后创建vip组,操作user与group关系表使一个用户是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:
                passreturn False

urls.py

router.register('cars', views.CarViewSet, 'car')

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', )

6、频率组件

1、频率校验规则

1、只有认证、权限校验都通过了才会执行,频率组件的目的是限制接口的返回频率,判断是否在规定时间内超过访问

2、需要配置文件settings中配置访问的频率

3、在频率组件中设置缓存来存储接口访问的key,一旦清除缓存就不会进行校验

4、将频率组件配置给需要限制的视图类进行局部配置即可

2、自定义频率类

自定义频率类是最常见的:短信接口,一分钟只能发一次

  1. 继承SimpleRateThrottle

  2. 设置类实现scope,值就是一个字符串,与settings中的DEFAULT_THROTTLE_RATES进行对应,DEFAULT_THROTTLE_RATES设置scope绑定频率规则

  3. 重写get_cache_key(self, request, view) 方法,字段限制条件

    • 不满足限制条件,返回None,代表该请求不需要进行频率限制

    • 满足限制条件,返回一个字符串,返回一个字符串(动态的),代表该请求需要进行频率限制 (短信频率限制类,返回 "throttling_%(mobile)s" % {"mobile": 实际请求来的电话})

系统频率类:

  1. UserRateThrottle: 限制所有用户访问频率

  2. AnonRateThrottle:只限制匿名用户访问频率

3、频率组件的使用:请求方式频率限制

自定义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 .throttles import MethodRateThrottle
class CarViewSet(ModelViewSet):
    throttle_classes = [MethodRateThrottle]
​
    queryset = models.Car.objects.all()
    serializer_class = serializers.CarSerializer

7、异常组件项目使用:记录异常信息到日常文件中

如果前端错误的请求返回的信息具有格式的,而后端异常返回的信息没有处理很难看,因此我们可以自定义异常组件处理服务器异常信息

自定义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',
}

 

posted @ 2020-02-25 23:04  Mr沈  阅读(433)  评论(0编辑  收藏  举报