DRF 三大认证

用户权限管理

一般项目中,采用的是传统的RBAC(Role-BasedAccessControl)。

传统的RBAC有两种实现方式:权限三表和权限五表

  • 权限三表:User、Group、Permission
  • 权限五表:User、Group、Permission、UG关系表、GP关系表

基于RBAC的权限管理

RBAC(role-Bases Access Control)基于角色的访问控制,就是用户通过角色与权限进行关联。

简单的说一个用户拥有若干角色,一个角色拥有若干权限,这样,就构成“用户-角色-权限”的授权模型,

在这种模型中,用户与角色之间,角色与权限之间都是多对多的关系

img

django中auth组件认证六表

六表:User、Group、Permission、UG关系表、GP关系表、UP关系表

自定义扩展auth组件的User表

  • 自定义User表继承AbstractUser
  • 在settings中配置AUTH_USER_MODEL
  • admin注册自定义User表,配置UserAdmin

认证方式

session认证、jwt认证

详见jwt认证

DRF三大认证

认证组件

在请求的时候,认证组件被触发。

主要用于身份认证,用于校验用户:游客、合法用户、非法用户

DRF的APIView类中:
    def dispatch(self, request, *args, **kwargs):
        ...
        try:
            # django 三大认证
            self.initial(request, *args, **kwargs)
        except Exception as exc:
            response = self.handle_exception(exc)
            ...
            
            
    def initial(self, request, *args, **kwargs):
        ...
        # Ensure that the incoming request is permitted
        # 认证模块:校验用户是否登录:合法用户、非法用户、匿名用户
        self.perform_authentication(request)
        
        # 权限模块:校验用户是否拥有权限
        self.check_permissions(request)
        
        # 频率模块:访问接口的次数是否在设定的时间范围内
        self.check_throttles(request)

源码分析

认证组件的模块是self.perform_authentication(request)

二次封装过后的request对象调用.user实际上调用了Request类中的user方法,通过@property装饰器来装饰的数据方法的属性

@property
def user(self):
    if not hasattr(self, '_user'):
         # self是二次封装的request
         # 调用这个_authenticate()方法
        with wrap_attributeerrors():
            self._authenticate()
        return self._user

self.authentication_classes的配置信息就是在APIView类中全局配置的内容。因此我们也可以通过重写该属性来完成局部配置。该全局配置的默认配置内容就是:

authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES


'DEFAULT_AUTHENTICATION_CLASSES': [
    'rest_framework.authentication.SessionAuthentication',
    'rest_framework.authentication.BasicAuthentication'
],

发现默认的配置就是使用authentication文件下的SessionAuthentication类和BasicAuthentication

配置认证模块

通过上面的源码分析,我们知道了全局配置和局部配置的实现方法

全局配置:

REST_FRAMEWORK = {
    # 认证组件的全局配置
    'DEFAULT_AUTHENTICATION_CLASSES': [
        # 系统默认的session的认证方法
        # 'rest_framework.authentication.SessionAuthentication',
        # 'rest_framework.authentication.BasicAuthentication',

        # 使用drf-jwt认证
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
    ],
}

局部配置:

from rest_framework_jwt.authentication import JSONWebTokenAuthentication
class UserListAPIView(ListAPIView):
    authentication_classes = [JSONWebTokenAuthentication]

使用drf-jwt认证获取token

首先需要安装这个插件

pip3 install djangorestframework-jwt

使用自带设定好的jwt

from rest_framework_jwt.views import obtain_jwt_token
'''
url('^login/$', obtain_jwt_token)其实相当于 url('login/', ObtainJSONWebToken.as_view())
因为我们之间进源码可以看到
obtain_jwt_token = ObtainJSONWebToken.as_view()     #获得
refresh_jwt_token = RefreshJSONWebToken.as_view()   #刷新
verify_jwt_token = VerifyJSONWebToken.as_view()     #验证
'''


urlpatterns = [
    url(r'^login/',obtain_jwt_token),
]

测试接口:post请求

postman发生post请求
接口:http://127.0.0.1:8000/api/login/

数据:
{
    "username":"admin",
    "password":"admin123"
}

返回一个token字符串
{
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNTc3ODc0MzU0LCJlbWFpbCI6IiJ9.5z8Ya-mxj-oPSOwdXenSKUWf7M5pt3r8YVlFKu1cskY"
}

自定义签发token的登录接口(多方式登录)

  • 将请求数据交给序列化类,执行序列化校验(请求的账号可能是用户名、邮箱或手机号,采用正则匹配不同字段校验数据库即可)
  • 在序列化全局校验钩子函数中,完成user的认证与token的签发,保存在序列化的content属性中
  • 在视图类汇总从序列化对象的content属性中拿到user对象与token相关信息

需要注意的:

  • token只能由登录接口签发
  • 登录接口也是APIView的子类,使用它的时候一定也要进行三大认证的校验,与自定义出现冲突
  • 应该局部应禁用认证与权限的系统的认证类

配置路由

urlpatterns = [
    # 自定义签发token的登录login接口
    url(r'login/$',views.LoginAPIView.as_view()),

]

配置视图类

from rest_framework.views import APIView
from . import serializer
class LoginAPIView(APIView):
    # 局部禁用认证和权限组件
    authentication_classes = []
    permission_classes = []

    def post(self,request,*args,**kwargs):
        serializer_obj = serializer.LoginModelSerializer(data=request.data)
        serializer_obj.is_valid(raise_exception=True)
        return APIResponse({
            "username":serializer_obj.content.get('user').username,
            "token":serializer_obj.content.get('token')
        })

序列化类配置

from rest_framework_jwt.serializers import jwt_payload_handler
from rest_framework_jwt.serializers import jwt_encode_handler
import re
class LoginModelSerializer(serializers.ModelSerializer):
    # post请求,默认当做create方法会入库并进行校验,
    # 自定义序列化字段,不要入库
    username = serializers.CharField(max_length=16,min_length=3)
    password = serializers.CharField(min_length=3,max_length=16)

    class Meta:
        model = models.User
        fields = ('username','password')

    def _validate_user(self,attrs):
        username = attrs.get('username')
        password = attrs.get('password')
        # 使用re正则匹配账号类型,然后去数据库查询用户对象
        if re.match(r'.*@.*',username):
            user_obj = models.User.objects.filter(email=username).first()
        elif re.match(r'^1[3-9][0-9]{9}$',username):
            user_obj = models.User.objects.filter(mobile=username).first()
        else:
            user_obj = models.User.objects.filter(username=username).first()

        if not user_obj or not user_obj.check_password(password):
            raise serializers.ValidationError({'message':'用户信息异常'})
        return user_obj

    # 使用全局钩子,完成token的签发
    def validate(self, attrs):
        # 使用自定义的方法获取用户对象
        user_obj = self._validate_user(attrs)
        # 将user对象包装到载荷里加密
        payload = jwt_payload_handler(user_obj)
        # 载荷签发token字符串
        token = jwt_encode_handler(payload)
        # 将生成好的user对对象与token封装到serializer对象中,方便在视图类中使用
        # 在视图类中使用:
        #     serializer_obj.content.get('user').username,
        #     serializer_obj.content.get('token')
        self.content = {
            "user":user_obj,
            "token":token
        }
        return attrs

postman接口测试

postman发生post请求
接口:http://127.0.0.1:8000/api/login/

数据:
{
    "username":"admin",
    "password":"admin123"
}

返回一个token字符串
{
    "status": {
        "username": "admin",
        "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoyLCJ1c2VybmFtZSI6InFpbnlqIiwiZXhwIjoxNTc3OTY4MTEzLCJlbWFpbCI6IiJ9.ZeJFCZoI-HFcI0EgeGoeEHycj8MtGy_rAAk887PAUIU"
    },
    "msg": "ok"
}

权限组件

drf默认提供的权限模块:

  • AllowAny:匿名用户和合法用户用有所有权限
  • IsAuthenticated:只有合法用户拥有所有权限
  • IsAdminUser:只有后台用户(admin)拥有全部权限
  • IsAuthenticatedOrReadOnly:匿名用户只有只读权限,合法用户拥有全部权限

如果有特殊需要,需要自定义权限类:如针对VIP用户某个视图类有权限

配置权限模块

全局配置:

# drf的配置
REST_FRAMEWORK = {
    # 权限模块的全局配置
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny',
        'rest_framework.permissions.IsAuthenticated',

        # 自定义权限类
        "api.permissions.VIPUserPermission",
    ],
}

局部配置:

from rest_framework.permissions import AllowAny
class UserListViewSet(mixins.ListModelMixin,GenericViewSet):
    permission_classes = [AllowAny]

自定义权限类

例子:以验证用户是否所属于vip组为例,演示自定义权限类的使用

  • 继承BasePermission类,重写has_permission方法
  • 权限规则(has_permission实现体)
    • 返回True:代表有权限
    • 返回False:代表无权限

前提工作准备:

  • 登录admin后台管理,创建一个用户
  • 添加一个组,名叫vip
  • 将此用户添加到vip组中

url路由层配置

router.register('user_detail',views.UserViewSet,basename='user_detail')

# 登录需要服务器签发token
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
    # 签发token的登录login接口
    url(r'^login/',obtain_jwt_token),
    url(r'',include(router.urls))
]

视图类配置

from rest_framework.viewsets import ViewSet
from .permissions import VIPUserPermission

class UserViewSet(ViewSet):
    # 局部配置只有VIP用户才可以查看个人详情
    permission_classes = [VIPUserPermission]

    def retrieve(self,request,*args,**kwargs):
        return APIResponse(results={
            "username":request.user.username,
            "email":request.user.email,
            "mobile":request.user.mobile
        })

自定义权限的类VIPUserPermission

from rest_framework.permissions import BasePermission

class VIPUserPermission(BasePermission):
    def has_permission(self, request, view):
        # 循环遍历请求用户的所属组
        for group in request.user.groups.all():
            # 如果是vip用户才有权限访问
            if group.name.lower() == "vip":
                return True
            # 如果不是vip用户没有权限访问
        return False

postman测试:

首先post请求方式,并携带数据包参数(body --> json)
{
	"username":"admin",
	"password":"admin123"
}
接口:http://127.0.0.1:8000/api/login/
        
获得token
{
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNTc3OTY0MjQ5LCJlbWFpbCI6IiJ9.yS-hvJxCdLXdkfWa7yfR0b65t_rFc3yBYuf2uJbVofU"
}
将此token粘贴到headers中,
	key					VALUE
authorization			jwt eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNTc3OTY0MjQ5LCJlbWFpbCI6IiJ9.yS-hvJxCdLXdkfWa7yfR0b65t_rFc3yBYuf2uJbVofU


发送get请求
接口:http://127.0.0.1:8000/api/user_detail/1/

此用户是admin,并不属于vip组的用户,所以会报权限不足的异常:
{
    "detail": "<api.views.UserViewSet object at 0x000000000DB44550> - GET - You do not have permission to perform this action."
}


再次使用新创建的用户来请求token并发送get请求
接口:http://127.0.0.1:8000/api/user_detail/1/
权限验证通过,查询结果如下:
{
    "status": 0,
    "msg": "ok",
    "results": {
        "username": "qinyj",
        "email": "",
        "mobile": "11111111111"
    }
}

频率组件

频率模块就是显示规定时间内的访问次数,drf中默认提供了一些频率模块类

from rest_framework.throttling import SimpleRateThrottle

  • AnonRateThrottle:对匿名用户进行频率限制
  • UserRateThrottle:对所有用户进行频率限制

若有特殊需求,需要自定义频率类,如对手机发送短信进行限制,1/min (1分钟之内一个手机号只能发送一次验证码)

配置频率模块

全局配置:

# drf的配置
REST_FRAMEWORK = {
    # 频率组件:频率类一般做局部配置,但是频率调节在settings中配置
    'DEFAULT_THROTTLE_RATES': {
        'user': '5/day',	# 表示一天之内只能访问5次
        'anon': '3/min',	# 表示一分钟之内只能访问3次
    },
}

局部配置:

from rest_framework.throttling import AnonRateThrottle, UserRateThrottle
class UserListViewSet(mixins.ListModelMixin,GenericViewSet):
    throttle_classes = [UserRateThrottle]

自定义频率类

  • 继承SimpleRateThrottle,重写get_cache_key方法,设置scope类属性
  • scope就是一个认证字符串,在配置文件中配置scope字符串对应的频率设置
  • get_cache_key的返回值是字符串,该字符串是缓存访问次数的缓存key
    • 返回与限制条件有关的字符串,表示限制,一般配合全局配置DEFAULT_THROTTLE_RATES来使用
    • 返回None,表示不限制

我们还沿用上面验证用户是否所属于vip组为例,演示自定义频率类的使用

配置自定义频率类:

throttls.py:
    
from rest_framework.throttling import SimpleRateThrottle
class MobileRateThrottle(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
        }

配置视图类

from rest_framework.viewsets import ViewSet
from .permissions import VIPUserPermission

# 导入自定义的频率类
from .throttls import MobileRateThrottle

class UserViewSet(ViewSet):
    # 局部配置只有VIP用户才可以查看个人详情
    permission_classes = [VIPUserPermission]
    # 局部配置自定义的频率类
    throttle_classes = [MobileRateThrottle]

    def retrieve(self,request,*args,**kwargs):
        return APIResponse(results={
            "username":request.user.username,
            "email":request.user.email,
            "mobile":request.user.mobile
        })

需要配合settings中配置scope字符串的频率

'DEFAULT_THROTTLE_RATES': {
        'user': '5/min',
        'anon': '3/min',
    	# 限制自定义的scope字符串的频率
        'mobile': '3/min',
    },

访问测试:

将此token粘贴到headers中,
	key					VALUE
authorization			jwt eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNTc3OTY0MjQ5LCJlbWFpbCI6IiJ9.yS-hvJxCdLXdkfWa7yfR0b65t_rFc3yBYuf2uJbVofU


发送get请求
接口:http://127.0.0.1:8000/api/user_detail/1/


连续使用新创建的用户来请求token并发送get请求三次
会出现异常,此时自定义频率类已生效
{
    "detail": "<api.views.UserViewSet object at 0x000000000DB97F98> - GET - Request was throttled. Expected available in 53 seconds."
}

posted @ 2020-01-02 21:08  GeminiMp  阅读(502)  评论(0编辑  收藏  举报