图书接口:游客只读,用户可增删改权限使用前端
from rest_framework.permissions import IsAuthenticatedOrReadOnly # ModelViewset便可增删改查 class BookViewSet(ModelViewSet): # 游客只读,用户可增删改查 permission_classes = [IsAuthenticatedOrReadOnly] queryset = models.Book.objects.all() serializer_class = serializers.BookSerializer
用主键为2的帐号登陆得到的token,却可以经过 /api/user/center/1/ 访问的是主键为1的帐号信息,这是有问题的。如何实现用 /api/user/center/ 接口访问的是本身的详情信息:算法
实现用户中心信息自查,不带主键的get请求,走单查逻辑数据库
urls.pydjango
# 咱们不走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)
drf-jwt直接提供刷新功能,能够运用在像12306这样安全性要求较高的网站api
第一个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的数据便可
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'})
首先在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: pass return 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', )
一、只有认证、权限校验都经过了才会执行,频率组件的目的是限制接口的返回频率,判断是否在规定时间内超过访问
二、须要配置文件settings中配置访问的频率
三、在频率组件中设置缓存来存储接口访问的key,一旦清除缓存就不会进行校验
四、将频率组件配置给须要限制的视图类进行局部配置便可
自定义频率类是最多见的:短信接口,一分钟只能发一次
继承SimpleRateThrottle
设置类实现scope,值就是一个字符串,与settings中的DEFAULT_THROTTLE_RATES进行对应,DEFAULT_THROTTLE_RATES设置scope绑定频率规则
重写get_cache_key(self, request, view) 方法,字段限制条件
不知足限制条件,返回None,表明该请求不须要进行频率限制
知足限制条件,返回一个字符串,返回一个字符串(动态的),表明该请求须要进行频率限制 (短信频率限制类,返回 "throttling_%(mobile)s" % {"mobile": 实际请求来的电话})
系统频率类:
UserRateThrottle: 限制全部用户访问频率
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 .throttles import MethodRateThrottle class CarViewSet(ModelViewSet): throttle_classes = [MethodRateThrottle] queryset = models.Car.objects.all() serializer_class = serializers.CarSerializer
若是前端错误的请求返回的信息具备格式的,然后端异常返回的信息没有处理很难看,所以咱们能够自定义异常组件处理服务器异常信息
自定义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', }