• 基于自定义表编写认证类、django-jwt源码分析、权限介绍、simpleui的使用


    扩写auth的user表

    1 基于自定义表编写认证类
    1.1 认证类
    1.2 登录接口
    1.3 路由
    1.4 视图类

    2 django-jwt源码分析
    2.1 签发
    2.2 认证

    3 权限介绍

    4 simpleui的使用
    4.1 安装simpleui
    4.2 修改django后台模块默认的模板
    4.3 字符集及时区设置

    扩写auth的user表范

    # 1 基于自定义的用户表,签发token
    	-1 前端(postman,web,app,小程序)---》发送http请求,携带用户的用户名密码---》到后端
        -2 后端request.data 取出前端传入的数据--》字典---》取出用户名,密码
        -3 拿着用户名密码去数据库查询,有没有这个人
        -4 如果有,说明 登录成功
        -5 签发token:1 通过当前用户得到payload(自己生成荷载)  2 通过荷载得到token
        -6 返回给前端{code,msg,token,username,icon...}
        -7 如果查不到,就返回用户名密码错误
        
        
    # 2 基于auth的user表,签发token,多方式登录
    	-1 扩写auth的user表
        	-自己写一个表,继承AbstractUser:username,password,is_superuser
            	-password:加密的---》如何加密的
                -同样的明文---》加密得到的密文是不一样的
                -AuthUser.objects.create_user(username=xx,password=123)
                -AuthUser.objects.create_superuser(username=xx,password=123)
                -pbkdf2_sha256$260000$e3ZRrKYrR0kSEMNAKPOJl4$2dzB06dDZEe4ZSTy7DtWGqvCwY
                加密方式        有效时间  盐                     加密后的结果
                
                -自定义密码加密---》同样的密码,加密结果不一样
            -项目一开始,就要处理完,再迁移,如果先迁移,就会出问题
            -配置文件中配置
            
        -2 多方式登录
        	-前端接收 用户名,手机号,邮箱的这个字段是同一个
            	{username:用户名/手机号/邮箱,password:密码}
            -正则匹配---》如果是手机号,就查 手机号+密码,如果是邮箱 邮箱+密码
            -check_password
            -签发token
            ------所有逻辑都写在视图类中了-----
            
            
            -逻辑写到序列化类中
            	-如果继承了ModelSerializer,必须重写username
                -重写了全局钩子:在全局钩子中完成校验,如果校验不过,抛异常
            	-写了获取用户方法
                -写了签发token方法
                -把token和用户名放到了self.context中了
          
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    1 基于自定义表编写认证类

    ### 补充翻译函数,只要做了国际化,就会显示当前国家的语言
    from django.utils.translation import gettext_lazy as _
    msg = _('Signature has expired.') # _是个函数的别名,这个函数是翻译函数,只要做了国际化,它就是中文
    
    • 1
    • 2
    • 3

    1.1 认证类

    from rest_framework_jwt.authentication import JSONWebTokenAuthentication
    
    from rest_framework.authentication import BaseAuthentication
    from rest_framework.exceptions import AuthenticationFailed
    import jwt
    from rest_framework_jwt.settings import api_settings
    from .models import User
    
    jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
    from django.utils.translation import ugettext as _
    
    class JWTLoginAuthentication(BaseAuthentication):
        def authenticate(self, request):
            # 1 取出用户传入的token----》带在哪里?后端规定的---》规定放在请求头 token=asfas.asfdas.asdfa
            token = request.META.get('HTTP_TOKEN')
            # 2 验证token-->django-jwt 提供了一个校验的方法---》 jwt_decode_handler 通过传入token传,校验,校验通过返回payload,校验失败抛异常
            try:
                payload = jwt_decode_handler(token)
                # 通过payload 得到当前用户
                # 认证类,配好后,只要有认证的,都会走这里---》每次走这里都会查询一次数据库
                # 做个优化?
                # 1 自己根据payload数据,创建一个用户,有的参数没传,会使用默认值,跟我真实用户还是有区别的
                # user=User(id=payload.get('user_id'),username=payload.get('username'))
                # 2 直接返回用户id,后续 的request.user 都是用户id,如果真正要用的时候,再查
                user = User.objects.get(pk=payload.get('user_id'))
            except jwt.ExpiredSignature as e:
                # msg = _('Signature has expired.') # _是个函数的别名,这个函数是翻译函数,只要做了国际化,它就是中文
                msg = '签名过期'
                raise AuthenticationFailed(msg)
            except jwt.DecodeError:
                msg = 'token错误'
                raise AuthenticationFailed(msg)
            except jwt.InvalidTokenError:
                raise AuthenticationFailed('不合法的token')
            except Exception:
                raise AuthenticationFailed('未知错误,请联系系统管理员')
    
            return (user, token)  # 后续的request.user 都是用户id,如果真正要用的时候,再查
    
        
        
     '''
     每次访问需要登录的接口,都会去查数据库
     所以咱们可以做优化
     	1 自己生成一个user对象
     	2 直接返回user_id,以后用的时候,再查数据库
     
     '''
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    1.2 登录接口

    jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
    from rest_framework.viewsets import ViewSet
    from rest_framework.decorators import action
    # ### ### ### ### ##1 自己定义的用户表,签发token# ### ### ### ### ### ##
    class UserView(ViewSet):
        @action(methods=['POST'],detail=False)
        def login(self, request):
            username = request.data.get('username')
            password = request.data.get('password')
            user = User.objects.filter(username=username, password=password).first()
            if user:
                # 签发token
                # 1 通过user生成payload---》jwt 提供一个方法(username),传入user,返回payload
                payload = jwt_payload_handler(user)
                # payload={'username':'asdfasdf','exp':1694401763}
                # 2 生成token---》jwt提供了方法,把payload放入--》token
                token = jwt_encode_handler(payload)
                return Response({'code': 101, 'msg': '登录成功', 'token': token, 'username': user.username})
            else:
                return Response({'code': 101, 'msg': '用户名或密码错误'})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    1.3 路由

    router.register('user', UserView, 'user') # 127.0.0.1:8000/user/login  的post请求
    router.register('books', BooView, 'books') # 127.0.0.1:8000/user/login  的post请求
    
    • 1
    • 2

    1.4 视图类

    from rest_framework.viewsets import GenericViewSet
    
    # 登录后才能访问
    from .auth import JWTLoginAuthentication
    class BooView(GenericViewSet):
        authentication_classes = [JWTLoginAuthentication]
        def list(self, request):
            return Response("你看到我了")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2 django-jwt源码分析

    2.1 签发

    # 1 入口是:obtain_jwt_token---》from rest_framework_jwt.views import obtain_jwt_token
    # 2 obtain_jwt_token本质是:ObtainJSONWebToken.as_view()
    # 3 看ObtainJSONWebToken视图类
    	-前端post请求,提交了用户名密码,就能签发token
        -去ObtainJSONWebToken中找---》post
        
    # 4 发现没有
        class ObtainJSONWebToken(JSONWebTokenAPIView):
            serializer_class = JSONWebTokenSerializer  #就是它
            
    # 5 去父类中找JSONWebTokenAPIView
    	
    class JSONWebTokenAPIView(APIView):
        def post(self, request, *args, **kwargs):
            #serializer = self.get_serializer(data=request.data) # 配置的序列化类
            serializer =JSONWebTokenSerializer(data=request.data)
            if serializer.is_valid():  # 字段自己,局部,全局
                user = serializer.object.get('user')
                token = serializer.object.get('token')
                # 定制返回格式--》如果咱们写了,走自己的
                response_data = jwt_response_payload_handler(token, user, request)
                response = Response(response_data)
                return response
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
        
        
     # 6 校验用户名密码和生成token,写在了序列化类的--》全局钩子validate中--》JSONWebTokenSerializer
    
    class JSONWebTokenSerializer(Serializer):
        def validate(self, attrs):
            credentials = {
                'username': attrs.get("username"),
                'password': attrs.get('password')
            }
            if all(credentials.values()): # credentials字典的value必须不能为空
                # authenticate django的auth,通过用户名和明文密码校验用户是否存的函数:authenticate
                # User.objects.filter(username='lqz',password='123').first()
                # user = authenticate(username='lqz',password='123')
                user = authenticate(**credentials)
                if user: # 登录成功,前token
                    if not user.is_active: # 判断用户是否锁定
                        msg = _('User account is disabled.')
                        raise serializers.ValidationError(msg)
    
                    payload = jwt_payload_handler(user)
    
                    return {
                        'token': jwt_encode_handler(payload),
                        'user': user
                    }
                else:
                    msg = _('Unable to log in with provided credentials.')
                    raise serializers.ValidationError(msg)
            else:
                msg = _('Must include "{username_field}" and "password".')
                msg = msg.format(username_field=self.username_field)
                raise serializers.ValidationError(msg)
                
                
                
     # 总结
    	1 前端携带用户名密码到后端---》执行后端你的post方法---》后端生成一个序列化类的对象---》执行校验---》走了全局钩子--》全局钩子中通过用户名密码获取用户(如果获取不到抛异常)---》获取到后签发token---》签发完返回----》在视图类中---》取出来,返回给前端
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    2.2 认证

    # 1 从哪开始看---》认证类JSONWebTokenAuthentication
    # 2 JSONWebTokenAuthentication 的 
    class JSONWebTokenAuthentication(BaseJSONWebTokenAuthentication):
        def get_jwt_value(self, request):
            auth = get_authorization_header(request).split()
            auth_header_prefix = api_settings.JWT_AUTH_HEADER_PREFIX.lower()
    
            if not auth:
                if api_settings.JWT_AUTH_COOKIE:
                    return request.COOKIES.get(api_settings.JWT_AUTH_COOKIE)
                return None
    
            if smart_text(auth[0].lower()) != auth_header_prefix:
                return None
    
            if len(auth) == 1:
                msg = _('Invalid Authorization header. No credentials provided.')
                raise exceptions.AuthenticationFailed(msg)
            elif len(auth) > 2:
                msg = _('Invalid Authorization header. Credentials string '
                        'should not contain spaces.')
                raise exceptions.AuthenticationFailed(msg)
    
            return auth[1]
        
        
    # 2 父类中BaseJSONWebTokenAuthentication的authenticate
        def authenticate(self, request):
            # 拿出前端传入的token,可能前端没传,就是None
            jwt_value = self.get_jwt_value(request)
            if jwt_value is None: # None就不管了,认证类继续往后走,相当于没有认证,于是咱们才需要权限类
                return None
    
            try:
                payload = jwt_decode_handler(jwt_value)
            except jwt.ExpiredSignature:
                msg = _('Signature has expired.')
                raise exceptions.AuthenticationFailed(msg)
            except jwt.DecodeError:
                msg = _('Error decoding signature.')
                raise exceptions.AuthenticationFailed(msg)
            except jwt.InvalidTokenError:
                raise exceptions.AuthenticationFailed()
    
            user = self.authenticate_credentials(payload)
    
            return (user, jwt_value)
        
        
        
     # 总结:逻辑,跟咱们自定义的一模一样
    	-获取token复杂一点,有个前端
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    3 权限介绍

    # 所有项目都会有权限控制
    # https://www.cnblogs.com/liuqingzheng/articles/16972464.html
    
    
    #ACL(Access Control List,访问控制列表)---》针对互联网用户,多半是这个
    	将用户与权限对接(多对多)
           张三   [发视频,点赞,评论]
           李四   [看视频]
        
        
        
    # RBAC(Role-Based Access Control,基于角色的访问控制)--》公司内部项目
    	将用户与角色对接,然后角色与对象的权限对接。
        django的admin+auth就是使用了这套认证机制
        
    # ABAC(Attribute-Based Access Control,基于属性的访问控制)
        ABAC(Attribute-Based Access Control,基于属性的访问控制),又称为PBAC(Policy-Based Access Control,基于策略的访问控制),CBAC(Claims-Based Access Control,基于声明的访问控制)。
    
        传统的ACL、RBAC的架构是{subject,action,object},
        而ABAC的架构是{subject,action,object,contextual}且为他们添加了parameter(参数)。
    
        subject属性:比如用户的年龄、部门、角色、威望、积分等主题属性。
    
        action属性:比如查看、读取、编辑、删除等行为属性。
    
        object属性:比如银行账户、文章、评论等对象或资源属性。
    
        contextual属性:比如时段、IP位置、天气等环境属性
        
        
        
    # python写公司内部项目比较多,使用rbac控制居多
    	RBAC  是基于角色的访问控制(Role-Based Access Control )在 RBAC  中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便。
        
        
        
    # rbac如何设计
    	-用户表---》一堆用户:张三,李四
        -角色表(group组)---》用户的角色:后勤部,开发部,总裁办。。。
        -权限表-----》放了一堆权限:发电脑,提交代码,删除代码,发工资
        -用户和角色多对多:一个用户属于多个角色,一个角色属于多个用户
        -角色 和权限多对多:一个角色有多个权限,一个权限属于多个角色
        --------rabc----通过5张表可以实现
        -django为了更细粒度划分---》多了一张表,用户和权限多对多
        
    
        
    #演示django的rbac权限控制
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    4 simpleui的使用

    4.1安装simpleui

    # 指定清华源安装simpleui
    pip install django-simpleui -i https://pypi.tuna.tsinghua.edu.cn/simple
    
    # 公司内部,做公司内的项目需要使用这套权限控制
    ## 方案一:使用django-admin写
    # 有的公司,不怎么写前端,直接使用django的admin,快速写出一套具有权限管理的系统
    # django admin的界面不好看:第三方美化--》simpleui
    
    ## 方案二:自己写,前端使用vue,后端使用django,做公司内部的项目
    	-第三方开源的权限控制 项目
        	-python界:django-vue-admin   7期学长写的
            -java界:若依
            -go界:gin-vue-admin
    
            
    #  django admin的美化: simpleui
    	
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    4.2 修改django后台模块默认的模板

    # 修改project的setting文件,在INSTALLED_APPS 首行引入simple应用
      INSTALLED_APPS = [
          'simpleui',
      ]
    
    • 1
    • 2
    • 3
    • 4

    4.3 字符集及时区设置

    # 修改project的setting文件
    LANGUAGE_CODE = 'zh-hans'
    TIME_ZONE = 'Asia/Shanghai'
    USE_TZ = False # 这里务必调整为False,否则时区设置无效
    
    • 1
    • 2
    • 3
    • 4
  • 相关阅读:
    初识 kubernetes
    java计算机毕业设计个人事务管理系统源码+mysql数据库+系统+lw文档+部署
    SAP-写了一个FUNCTION,用于读取订单中,指定工序的状态。
    洗衣液行业调研:预计2028年将达到439亿美元
    RocketMQ相关面试题简单整理
    kotlin-高级特性一
    python --监听鼠标事件
    尚硅谷Git学习笔记
    Rust变量与数据类型
    【计算机网络】网络编程接口 Socket API 解读(9)
  • 原文地址:https://blog.csdn.net/weixin_44145338/article/details/132839688