• rest-framework(七)


    三大认证组件

    认证组件

    self.perform_authentication(request)

    '''
    用户权限关系 RBAC(Role-BasedAccessControl)
    表:User、Group、Permission、UG关系表、UP关系表、GP关系表
    传统的RBAC有两种:权限三表 => 权限五表(没有UP关系表)
    Django中Auth组件采用的是 权限六表(在传统RBAC基础上增加UP关系表)
    
    	==用户管理表,一定要在第一次数据库迁移时完成==
    '''
    #在admin中注册自定义的User表,并继承UserAdmin,使在后台注册的用户密码是密文
    #通过重写UserAdmin,自定义某些属性
    from django.contrib import admin
    from . import models
    from django.contrib.auth.admin import UserAdmin as AuthUserAdmin
    
    class UserAdmin(AuthUserAdmin):
        # 添加用户页面可控制字段
        add_fieldsets = (
            (None, {
                'classes': ('wide',),
                'fields': ('username', 'password1', 'password2', 'is_staff', 'mobile'),
            }),
        )
        # 用户列表展示页面显示字段
        list_display = ('username', 'email', 'mobile', 'is_staff')
    
    # 注册自定义User表,用admin管理,配置UserAdmin,定制化管理页面
    admin.site.register(models.User, UserAdmin)
    

    RBAC三表

    RBAC六表

    jwt认证

    '''
    1. 登录时,账号密码换成token,=>签发token算法
    
    2. 处理需要登录后的请求,拿到前台传来的登录证明信息token
    	解析出登录用户user=>校验token算法(代码)
    '''
    

    jwt认证集群图

    2

    jwt优点

    """
    jwt: json web token
    优点:
    1)数据库不需要存储token,所以服务器的 IO 操作会减少(没有IO写操作)
    2)客户端存Token,服务器只存储签发与校验算法,执行效率高
    3)签发与校验算法在多个服务器上可以直接统一,所以jwt认证规则下,服务器做集群非常便捷
    
    突破点:
    1)token必须要有多个部分组成,有能反解的部分,也要有不能反解的部分 - jwt采用的都是三段式
    2)token中必须包含过期时间,保证token的安全性与时效性
    """
    

    jwt原理

    '''
    jwt原理:
    1)jwt由 头.载荷.签名 三部分组成
    2)每一部分数据都是一个json字典,头和载荷采用 base64 可逆加密算法加密,签名采用 HS256 不可逆加密
    
    内容:
    1)头(基本信息):可逆不可逆采用的加密算法、公司名称、项目组信息、开发者信息...
    {
    	"company": "小女孩",
    	...
    }
    2)载荷(核心信息):用户主键、用户账号、客户端设备信息、过期时间...
    {
    	'pk': 1,
    	...
    }
    3)签名(安全信息):头的加密结果、载荷的加密结果、服务器的安全码(盐)...
    {
    	"header": "..."
    	...
    }
    '''
    

    签发算法

    '''
    签发算法:
    1)头内容写死(可以为空{}):公司、项目组信息都是固定不变的
    	=> 将数据字典转化成json字符串,再将json字符串加密成base64字符串
    	
    2)载荷的内容:用户账号、客户端设备信息是由客户端提供,用户主键是客户端提供账号密码校验User表通过后才能确定,过期时间根据当前时间与配置的过期时间相结合产生
    	=> 将数据字典转化成json字符串,再将json字符串加密成base64字符串
    	
    3)签名的内容,先将头的加密结果,载荷的加密结果作为成员,再从服务器上拿安全码(不能让任何客户端知道),也可以额外包含载荷的部分(用户信息,设备信息)
    	=> 将数据字典转化成json字符串,再将json字符串不可逆加密成HS256字符串
    	
    4)将三个字符串用 . 连接产生三段式token
    
    校验算法:
    1)从客户端提交的请求中拿到token,用 . 分割成三段(如果不是三段,非法)
    2)头(第一段)可以不用解密
    3)载荷(第二段)一定需要解密,先base64解密成json字符串,再转换成json字典数据
    	i)用户主键与用户账号查询User表确定用户是否存在
    	ii)设备信息用本次请求提交的设备信息比对,确定前后是否是同一设备,决定是否对用户做安全提示(eg:短信邮箱提示异地登录)(同样的安全保障还可以为IP、登录地点等)
    	iii)过期时间与当前时间比对,该token是否在有效时间内
    4)签名(第三段)采用加密碰撞校验
    	i)将头、载荷加密字符串和数据库安全码形成json字典,转换成json字符串
    	ii)采用不可逆HS256加密形成加密字符串
    	iii)新的加密字符串与第三段签名碰撞比对,一致才能确保token是合法的
    	
    5)前方算法都通过后,载荷校验得到的User对象,就是该token代表的登录用户(Django项目一般都会把登录用户存放在request.user中)
    '''
    

    刷新算法

    '''
    刷新算法:
    1)要在签发token的载荷中,额外添加两个时间信息:第一次签发token的时间,最多往后刷新的有效时间
    2)每一请求携带token,不仅走校验算法验证token是否合法,还要额外请求刷新token的接口,完成token的刷新:校验规则与校验算法差不多,但是要将过期时间后移(没有超过有效时间,产生新token给客户端,如果超过了,刷新失败)
    3)所以服务器不仅要配置过期时间,还需要配置最长刷新时间
    '''
    

    自定义jwt配置

    '''
    #1)在settings中配置
        import datetime
        JWT_AUTH = {
            # 过期时间
            'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=300),
            # 是否允许刷新
            'JWT_ALLOW_REFRESH': True,
            # 最大刷新的过期时间
            'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),
        }
    '''
    

    登录接口,提供username 和password,签发token : ObtainJSONWebToken

    校验接口,提供token,返回token就代表校验通过 : VerifyJSONWebToken

    刷新接口,通过token,刷新新token返回 : RefreshJSONWebToken

    认证类的使用

    自定义认证类

    '''
    1.  如果使用session认证,drf默认提供了SessionAuthentication
    2.  如果使用drf-jwt认证框架,drf-jwt框架提供了JSONWebTokenAuthentication
    3.  如果是自定义签发与校验token,才需要将校验token的算法封装到自定义的认证类中
    '''
    
    #1)首先建立一个authentications的py文件,在该文件中自定义MyAuthentication认证组件类,该类继承BaseAuthentication
    
    from rest_framework.authentication import BaseAuthentication
    
    class MyAuthentication(BaseAuthentication):
        """
        1) 从请求头中拿到前台提交的token(一般从HTTP_AUTHORIZATION中拿,也可以与前台约定)
                  -- 如果设置了反爬等措施,校验一下反爬(头 token)
        2) 重写authenticate方法
        3)没有token,返回None,代表游客
        4)有token,进入校验
                  -- 不通过:抛AuthenticationFailed异常,代表非法用户
                  -- 通过:返回 (user, token),代表合法用户
        """
        def authenticate(self, request):
            print('来啦啦啦啦')
            pass
    #2)在BaseAuthentication类中规定了客户端携带token时的结构,已经校验token是否合法和正确
    #-----------------------------------------------------------------------------------
    class UserListViewSet(mixins.ListModelMixin, GenericViewSet):
        # 认证组件
        # 配置自定义认证类(需求小)
        authentication_classes = [authentications.MyAuthentication]
        # 配置drf-jwt框架的认证类(需求大) => 配置还可以在全局(认证组件只能决定request.user,不是断定权限的地方,所以一般配置全局)
        from rest_framework_jwt.authentication import JSONWebTokenAuthentication
        # authentication_classes = [JSONWebTokenAuthentication]
    

    使用drf封装的认证组件

    # 配置drf-jwt框架的认证类(需求大) => 配置还可以在全局(认证组件只能决定request.user,不是断定权限的地方,所以一般配置全局)
        from rest_framework_jwt.authentication import JSONWebTokenAuthentication
        authentication_classes = [JSONWebTokenAuthentication]
    
    #全局配置认证组件
    #	在settings中配置
    # drf框架自定义配置
    REST_FRAMEWORK = {
        # 认证组件
        'DEFAULT_AUTHENTICATION_CLASSES': [
            # 'rest_framework.authentication.SessionAuthentication',
            # 'rest_framework.authentication.BasicAuthentication'
            'rest_framework_jwt.authentication.JSONWebTokenAuthentication'
        ],
    

    权限组件

    self.check_permissions(request)

    # 权限组件
        # 配置自定义权限类(有需求)
        permission_classes = [permissions.MyPermission]
        # 配置drf自带的权限类(有需求)
        from rest_framework.permissions import IsAuthenticated, IsAdminUser, AllowAny, IsAuthenticatedOrReadOnly
        permission_classes = [IsAuthenticated]
        permission_classes = [IsAdminUser]
    

    原生drf权限组件

    1. AllowAny:游客和登录用户有全权限
    2. IsAuthenticated:只有登录用户有全权限
    3. IsAdminUser:只有后台用户(admin用户)有全权限
    4. IsAuthenticatedOrReadOnly:游客有读权限,登录用户有全权限

    自定义权限组件

    """
    如果有特殊需要,需要自定义权限类
        如:只有superuser有权限、只有vip用户有权限、只有某ip网段用户有权限、只有某个视图及其子类有权限
    """
    class MyPermission(BasePermission):
        def has_permission(self, request, view):
            """
            1) 根据需求,request和view的辅助,制定权限规则判断条件
            2)如果条件通过,返回True
            3)如果条件不通过,返回False
            """
            print(request.user, request.auth)
            return False
    
    #--------------------------------------------------------------------------------
    class UserListViewSet(mixins.ListModelMixin, GenericViewSet):
    	# 权限组件
        # 配置自定义权限类(有需求)
        permission_classes = [permissions.MyPermission]
    

    jwt签发token

    jwt签发token源码分析

    多方式登录签发token

    多方式登录,一定要自定义jwt-token的签发 --> 自己定义login视图

    1. token只能由 登录接口 签发
    2. 登录接口也是APIView的子类,使用一定会进行 认证、权限 组件的校验

    结论:

    ​ 不管系统默认、或是全局settings配置的是何认证与权限组件,登录接口不用参与任何认证与权限的校验

    ​ 所以,登录接口一定要进行 认证与权限 的局部禁用

    # 多方式登录
    from rest_framework.views import APIView
    class LoginAPIView(APIView):
    
        authentication_classes = []
        pagination_class = []
    
        def post(self, request, *args, **kwargs):
            serializer = serializers.LoginModelSerializer(data=request.data)
            serializer.is_valid(raise_exception=True)  # 内部在全局钩子中完成token的签发
            return APIResponse(results={
                'username': serializer.content.get('user').username,
                'token': serializer.content.get('token')
            })
        
    #---------在serializers中自定义登录时所要反序列化的字段,以及校验账号密码----
    from rest_framework_jwt.serializers import jwt_payload_handler, jwt_encode_handler
    import re
    class LoginModelSerializer(serializers.ModelSerializer):
        # post请求,序列化默认当做create动作进行校验,需要校验数据库,create动作username会抛用户已存在异常
        # 抛用户已存在异常是多余的,所以自定义系统校验规则即可
        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()  # type: models.User
            elif re.match(r'^1[3-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
    

    认证组件与权限组件绑定使用

    1. 每一个视图类都要进行认证校验,且认证规则一致,所以全局配置认证类即可
    2. 每一个视图类都要进行权限校验,默认配置的是不限制(AllowAny),但实际开发中,视图类的访问权限不尽相同,所以要在具体 的视图类,配置具体的权限规则
    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.email,
                'mobile': request.user.mobile,
                'data_joined': request.user.date_joined,
            })
        
    #----------自定义权限组件-------------------------------
    # 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
    

    频率组件

    self.check_throttles(request)

    原生drf封装的频率组件

    1. AnonRateThrottle : 登录客户无限制访问
    2. UserRateThrottle : 对所有用户设置频率限制

    自定义频率类

    from rest_framework.throttling import SimpleRateThrottle
    """
    如果有特殊需要,需要自定义频率类
        如:对ip进行限次、对电话进行限制、对视图某些信息进行限次
    """
    class MobileRateThrottle(SimpleRateThrottle):
        """
        1)设置scope字符串类属性,同时在settings中进行drf配置DEFAULT_THROTTLE_RATES
            eg: DEFAULT_THROTTLE_RATES = {'mobile': '1/min'}
        2)重写get_catch_key方法:
            返回与限制条件有关的字符串,表示限制
            返回None,表示不限制
        """
        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
            }
    #------------views视图类中--------------------------------------------------
    class UserListViewSet(mixins.ListModelMixin, GenericViewSet):
        # 频率组件
        # 配置drf自带的频率类(有需求)
        from rest_framework.throttling import AnonRateThrottle, UserRateThrottle
        # throttle_classes = [UserRateThrottle]
        # 配置自定义的频率类(需求大)
        throttle_classes = [throttles.MobileRateThrottle]
    

    三大认证流程图

  • 相关阅读:
    Java环境搭建
    Java语言的分支
    Java语言概述
    计算机语言
    人机交互
    计算机分类
    计算机的应用领域
    计算机的发展史
    Java8的一些新特性
    python国内三方库下载镜像
  • 原文地址:https://www.cnblogs.com/samoo/p/12152899.html
Copyright © 2020-2023  润新知