zoukankan      html  css  js  c++  java
  • REST-Framework: 认证组件 | token的介绍和使用

    目录

    一 认证简介

    二 认证组件局部使用

    三 认证组件全局使用

    附:不存数据库的token验证

    四 源码分析


    一 认证简介

    Django 自带一个用户认证系统,这个系统处理用户帐户、组、权限和基于 cookie 的会话.
    只有认证通过的用户才能访问指定的url地址,比如:查询课程信息,需要登录之后才能查看,没有登录,就不能查看,这时候需要用到认证组件

    Rest-Framework中的认证

    在restframework中,认证即是通过继承BaseAuthentication重构认证的类,认证的逻辑在类的authenticate方法中实现,通过判断用户传递的信息,如果认证成功返回(用户,用户Token)元组,会将用户对象封装到request里,通过request.用户可以获得用户的相关信息。

    二 认证组件局部使用

    (1)models层:

    class User(models.Model):
    
    username=models.CharField(max_length=32)
    
    password=models.CharField(max_length=32)
    
    user_type=models.IntegerField(choices=((1,'超级用户'),(2,'普通用户'),(3,'二笔用户')))
    
    
    
    class UserToken(models.Model):
    
    user=models.OneToOneField(to='User')
    
    token=models.CharField(max_length=64)

    (2)新建认证类(验证通过需要返回return两个参数, 第一个参数实质上给了request.user,  第二个给了request.auth)

    from rest_framework.authentication import BaseAuthentication
    
    class TokenAuth(BaseAuthentication):
    
    def authenticate(self, request):
    
    token = request.GET.get('token')
    
    token_obj = models.UserToken.objects.filter(token=token).first()
    
    if token_obj:
    
    # 这里返回的两个值, 一个通过token对象获取到对应的user对象, 另一个可以任意返回
    
    return token_obj.user, token
    
    else:
    
    raise AuthenticationFailed('认证失败')
    
    def authenticate_header(self,request):
    
    pass

    (3)view层使用:(不要加括号)

    局部使用的时候, 哪个类需要加验证,就在哪个类中加上该属性:  authentication_classes = [TokenAuth, ]

    • 要注意列表中的类,必须是已经写好的用来验证的类,

    • 列表中有多个类的时候, 如果第一个类就有两个返回值, 那么后面的类将不会再继续进行, 是否进行的关键在于验证成功后返回的值是否为空, 为空则继续循环验证类, 反之则停止

    def get_random(name):
    
    import hashlib
    
    import time
    
    md=hashlib.md5()
    
    md.update(bytes(str(time.time()),encoding='utf-8'))
    
    md.update(bytes(name,encoding='utf-8'))
    
    return md.hexdigest()
    
    class Login(APIView):
    
    def post(self,reuquest):
    
    back_msg={'status':1001,'msg':None}
    
    try:
    
    name=reuquest.data.get('name')
    
    pwd=reuquest.data.get('pwd')
    
    user=models.User.objects.filter(username=name,password=pwd).first()
    
    if user:
    
    token=get_random(name)
    
    models.UserToken.objects.update_or_create(user=user,defaults={'token':token})
    
    back_msg['status']='1000'
    
    back_msg['msg']='登录成功'
    
    back_msg['token']=token
    
    else:
    
    back_msg['msg'] = '用户名或密码错误'
    
    except Exception as e:
    
    back_msg['msg']=str(e)
    
    return Response(back_msg)
    
    
    
    
    
    
    
    class Course(APIView):
    
    authentication_classes = [TokenAuth, ]
    
    
    
    def get(self, request):
    
    return HttpResponse('get')
    
    
    
    def post(self, request):
    
    return HttpResponse('post')

    三 认证组件全局使用

    • 全局使用的时候会导致一个尴尬的问题就是我们写的登录CBV也需要登录认证才行

    • 全局使用的时候,我们实现部分CBV禁用的方式是,在需要的CBV中写上:authentication_classes = [ ]

    • 当我们写了authentication_classes = [ ] 为空的时候,将不会有返回值,就会让我们进行正常登录

    REST_FRAMEWORK={
    
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",]
    
    }

    总结:局部使用,只需要在视图类里加入:

    authentication_classes = [TokenAuth, ]

    附:不存数据库的token验证

    我们面使用的验证方式算是现在比较主流的验证方式了, 那我们现在来思考一个问题, 上面的验证方式是将token放到数据库中,数量比较少的时候没有什么问题, 但是当并发比较高的时候, 每个用户过来访问服务器的时候,都需要服务器到数据库中去查询进行比对, 这样高强度的查询会给服务器带来很大压力, 那么我们有没有办法来缓解服务器的这种压力的, 在这里我们来介绍一种方法来供大家参考, 这个方法就是不存数据库的token验证

    原理: 将token存到客户端来缓解服务器的压力,高并发产生的压力由程序来优化

    • 在程序中首先对用户信息进行加密, 加密格式:(信息加密后的字符串 | 信息字典 ) 把asdfasdf | {name:allen,id:1}  当做token,发到客户端,存储起来

    • 以后客户端再发请求,会携带asdfasdf|{name:lqz,id:1}这个token过来, 服务端在程序中根据分隔符来截取{name:lqz,id:1}

    • 然后再使用相同的加密方式对这些信息进行加密, 加密后得到一串字符串, 然后将两个字符串进行对比, 

    • 两个字符串一样则认证成功, 不一样则认证失败

    第二种方式:

    我们也可以使用简单粗暴的方式,将信息进行加密后直接发送给客户端, 客户端再次发送请求的时候,使用用一种算法对字符串进行解密, 然后对解密后的信息进行操作

    def get_token(id,salt='123'):
    
    import hashlib
    
    md=hashlib.md5()
    
    md.update(bytes(str(id),encoding='utf-8'))
    
    md.update(bytes(salt,encoding='utf-8'))
    
    
    
    return md.hexdigest()+'|'+str(id)
    
    
    
    def check_token(token,salt='123'):
    
    ll=token.split('|')
    
    import hashlib
    
    md=hashlib.md5()
    
    md.update(bytes(ll[-1],encoding='utf-8'))
    
    md.update(bytes(salt,encoding='utf-8'))
    
    if ll[0]==md.hexdigest():
    
    return True
    
    else:
    
    return False
    
    
    
    class TokenAuth():
    
    def authenticate(self, request):
    
    token = request.GET.get('token')
    
    success=check_token(token)
    
    if success:
    
    return
    
    else:
    
    raise AuthenticationFailed('认证失败')
    
    def authenticate_header(self,request):
    
    pass
    
    class Login(APIView):
    
    def post(self,reuquest):
    
    back_msg={'status':1001,'msg':None}
    
    try:
    
    name=reuquest.data.get('name')
    
    pwd=reuquest.data.get('pwd')
    
    user=models.User.objects.filter(username=name,password=pwd).first()
    
    if user:
    
    token=get_token(user.pk)
    
    # models.UserToken.objects.update_or_create(user=user,defaults={'token':token})
    
    back_msg['status']='1000'
    
    back_msg['msg']='登录成功'
    
    back_msg['token']=token
    
    else:
    
    back_msg['msg'] = '用户名或密码错误'
    
    except Exception as e:
    
    back_msg['msg']=str(e)
    
    return Response(back_msg)
    
    from rest_framework.authentication import BaseAuthentication
    
    class TokenAuth():
    
    def authenticate(self, request):
    
    token = request.GET.get('token')
    
    token_obj = models.UserToken.objects.filter(token=token).first()
    
    if token_obj:
    
    return
    
    else:
    
    raise AuthenticationFailed('认证失败')
    
    def authenticate_header(self,request):
    
    pass
    
    
    
    class Course(APIView):
    
    authentication_classes = [TokenAuth, ]
    
    
    
    def get(self, request):
    
    return HttpResponse('get')
    
    
    
    def post(self, request):
    
    return HttpResponse('post')

     自定义认证类(重点)

    1 使用
        -定义一个类,继承BaseAuthentication
        class LoginAuth(BaseAuthentication):
            def authenticate(self, request):
                token = request.GET.get('token')
                res = models.UserToken.objects.filter(token=token).first()
                if res:
                    return 元组
                else:
                    raise AuthenticationFailed('您没有登录')
        -重写authenticate方法
        -局部使用和全局使用
            -局部:在视图类中配置(只要配置了,就是登录以后才能访问,没配置,不用登录就能访问)
                authentication_classes = [MyAuthen.LoginAuth, ]
            -全局
            REST_FRAMEWORK = {
            "DEFAULT_AUTHENTICATION_CLASSES": ["app01.MyAuthen.LoginAuth", ]
            }
            
       -注意:
        1 认证类,认证通过可以返回一个元组,有两个值,第一个值会给,request.user,第二个值会个request.auth
        2 认证类可以配置多个,按照从前向后的顺序执行,如果前面有返回值,认证就不再继续往下走了
    View Code

    四 源码分析

    #Request对象的user方法
    
    @property
    
    def user(self):
    
    the authentication classes provided to the request.
    
    if not hasattr(self, '_user'):
    
    with wrap_attributeerrors():
    
    self._authenticate()
    
    return self._user
    
    
    
    def _authenticate(self):
    
    for authenticator in self.authenticators:
    
    try:
    
    user_auth_tuple = authenticator.authenticate(self)
    
    except exceptions.APIException:
    
    self._not_authenticated()
    
    raise
    
    #认证成功,可以返回一个元组,但必须是最后一个验证类才能返回
    
    if user_auth_tuple is not None:
    
    self._authenticator = authenticator
    
    self.user, self.auth = user_auth_tuple
    
    return
    
    
    
    self._not_authenticated()

     认证功能源码分析

    1 APIView---》dispatch---》self.initial(request, *args, **kwargs)--》self.perform_authentication(request)---》Request.user--->self._authenticate(self):Request类的方法---》self.authenticators:Request类的属性---》在Request对象实例化的时候传入的----》Request在什么时候实例化的?dispatch的时候---》APIView:self.get_authenticators()--》return [auth() for auth in self.authentication_classes]----》如果在自己定义的视图类中写了authentication_classes=[类1,类2]----》Request的self.authenticators就变成了我们配置的一个个类的对象
            
            
    2 self._authenticate(self):Request类的方法
    def _authenticate(self):
         for authenticator in self.authenticators: # BookView中配置的一个个类的对象
                try:
                    user_auth_tuple = authenticator.authenticate(self)
                except exceptions.APIException:
                    self._not_authenticated()
                    raise
    
                if user_auth_tuple is not None:
                    self._authenticator = authenticator
                    self.user, self.auth = user_auth_tuple
                    return
    3 只要在视图类中配置authentication_classes = [MyAuthen.LoginAuth, ]
        就会执行上面的方法,执行认证
    View Code

    self.authenticators

    def get_authenticators(self):
    
    return [auth() for auth in self.authentication_classes]

    认证类使用顺序:先用视图类中的验证类,再用settings里配置的验证类,最后用默认的验证类

    五 jwt认证介绍

    1 不再使用Session认证机制,而使用Json Web Token(本质就是token)认证机制,用户登录认证
    2 用户只要登录了,返回用户一个token串(随机字符串),每次用户发请求,需要携带这个串过来,验证通过,我们认为用户登录了
    3 JWT的构成(字符串)
        -三部分(每一部分中间通过.分割):header   payload  signature
        -header:明类型,这里是jwt,声明加密算法,头里加入公司信息...,base64转码
            {
              'typ': 'JWT',
              'alg': 'HS256'
            }
        -payload:荷载(有用),当前用户的信息(用户名,id,这个token的过期时间,手机号),base64转码
            {
              "sub": "1234567898",
              "name": "egon",
              "admin": true,
              "userid":1,
              'mobile':123444444
            }
        -signature:签名
            -把前面两部分的内容通过加密算法+密钥加密后得到的一个字符串
            
            
        -jwt总的构成样子:
          eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
            
    4 JWT认证原理
        -用户携带用户名,密码登录我的系统,校验通过,生成一个token(三部分),返回给用户---》登录功能完成
        -访问需要登录的接口(用户中心),必须携带token过来,后端拿到token后,把header和payload截出来,再通过一样的加密方式和密码得到一个signature,和该token的signature比较,如果一样,表示是正常的token,就可以继续往后访问
    View Code

    base64的使用

    1 任何语言都有base64的加码和解码,转码方式(加密方式)
    2 python中base64的加密与解密
    
        import base64
    
        import json
        dic_info={
          "name": "lqz",
          "age": 18
        }
        # 转成json格式字符串
    
        dic_str=json.dumps(dic_info)
        print(dic_str)
        #eyJuYW1lIjogImxxeiIsICJhZ2UiOiAxOH0=
        #eyJuYW1lIjogImxxeiIsICJhZ2UiOiAxOH0=
        # 需要用bytes格式
        # 加密
        base64_str=base64.b64encode(dic_str.encode('utf-8'))
        print(base64_str)
    
    
        # 解密
    
        res_bytes=base64.b64decode('eyJuYW1lIjogImxxeiIsICJhZ2UiOiAxOH0=')
        print(res_bytes)
    View Code

    jwt基本使用(jwt内置,控制用户登录后能访问和未登陆能访问)

    1 drf中使用jwt,借助第三方https://github.com/jpadilla/django-rest-framework-jwt
    2 pip3 install djangorestframework-jwt
    3 快速使用(默认使用auth的user表)
        1 再默认auth的user表中创建一个用户
        2 在路由中配置
            path('login/', obtain_jwt_token),
        3 用postman向这个地址发送post请求,携带用户名,密码,登陆成功就会返回token
        
        4 obtain_jwt_token本质也是一个视图类,继承了APIView
            -通过前端传入的用户名密码,校验用户,如果校验通过,生成token,返回
            -如果校验失败,返回错误信息
        
    4 用户登录以后才能访问某个接口
        -jwt模块内置了认证类,拿过来局部配置就可以
        -class OrderView(APIView):
            # 只配它不行,不管是否登录,都能范围,需要搭配一个内置权限类
            authentication_classes = [JSONWebTokenAuthentication, ]
            permission_classes = [IsAuthenticated,]
            def get(self, request):
                print(request.user.username)
                return Response('订单的数据')
    
    5 用户未登录,不能访问
        -class OrderView(APIView):
            # 只配它不行,不管是否登录,都能范围,需要搭配一个内置权限类
            authentication_classes = [JSONWebTokenAuthentication, ]
            def get(self, request):
                print(request.user.username)
                return Response('订单的数据')
            
    6 如果用户携带了token,并且配置了JSONWebTokenAuthentication,从request.user就能拿到当前登录用户,如果没有携带,当前登录用户就是匿名用户
    
    7 前端要发送请求,携带jwt,格式必须如下
        -把token放到请求头中,key为:Authorization 
        -value必须为:jwt eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo1LCJ1c2VybmFtZSI6ImVnb24xIiwiZXhwIjoxNjA1MjQxMDQzLCJlbWFpbCI6IiJ9.7Y3PQM0imuSBc8CUe_h-Oj-2stdyzXb_U-TEw-F82WE

    控制登录接口返回的数据

    1 控制登录接口返回的数据格式如下
        {
        code:100
        msg:登录成功
        token:asdfasfd
        username:egon
        }
        
    2 写一个函数
        from homework.serializer import UserReadOnlyModelSerializer
        def jwt_response_payload_handler(token, user=None, request=None):
            return {'code': 100, 
                    'msg': '登录成功',
                    'token': token,
                    'user': UserReadOnlyModelSerializer(instance=user).data
                    }
    3 在setting.py中配置
        import datetime
        JWT_AUTH = {
            'JWT_RESPONSE_PAYLOAD_HANDLER': 'homework.utils.jwt_response_payload_handler',
        }
    View Code

    自定义基于jwt的认证类

    1 自己实现基于jwt的认证类,通过认证,才能继续访问,通不过认证就返回错误
    2 代码如下
        
        class JwtAuthentication(BaseJSONWebTokenAuthentication):
            def authenticate(self, request):
                # 认证逻辑()
                # token信息可以放在请求头中,请求地址中
                # key值可以随意叫
                # token=request.GET.get('token')
                token=request.META.get('HTTP_Authorization'.upper())
                # 校验token是否合法
                try:
                    payload = jwt_decode_handler(token)
                except jwt.ExpiredSignature:
                    raise AuthenticationFailed('过期了')
                except jwt.DecodeError:
                    raise AuthenticationFailed('解码错误')
                except jwt.InvalidTokenError:
                    raise AuthenticationFailed('不合法的token')
                user=self.authenticate_credentials(payload)
                return (user, token)
     3 在视图类中配置
        authentication_classes = [JwtAuthentication, ]
    View Code

    多种登录方式(手动签发token)

    1 手机号+密码   用户名+密码  邮箱+密码
    2 流程分析(post请求):
        -路由:自动生成
        -视图类:ViewSet(ViewSetMixin, views.APIView)
        -序列化类:重写validate方法,在这里面对用户名和密码进行校验
        
    3 代码实现

    路由

    path('login/', views.LoginViewSet.as_view({'post':'create'})),

    视图

    class LoginViewSet(ViewSet):
        def create(self, request, *args, **kwargs):
            # 实例化得到一个序列化类的对象
            # ser=LoginSerializer(data=request.data,context={'request':request})
            ser = LoginSerializer(data=request.data)
            # 序列化类的对象的校验方法
            ser.is_valid(raise_exception=True)  # 字段自己的校验,局部钩子校验,全局钩子校验
            # 如果通过,表示登录成功,返回手动签发的token
            token = ser.context.get('token')
            username = ser.context.get('username')
            return APIResponse(token=token, username=username)
            # 如果失败,不用管了

    序列化类

    from rest_framework import serializers
    from app01.models import UserInfo
    import re
    from rest_framework.exceptions import ValidationError
    from rest_framework_jwt.utils import jwt_encode_handler, jwt_payload_handler
    from rest_framework_jwt.views import obtain_jwt_token
    
    class LoginSerializer(serializers.ModelSerializer):
        username = serializers.CharField()
    
        class Meta:
            model = UserInfo
            fields = ['username', 'password']
    
        def validate(self, attrs):
            # username可能是邮箱,手机号,用户名
            username = attrs.get('username')
            password = attrs.get('password')
            # 如果是手机号
            if re.match('^1[3-9]d{9}$', username):
                # 以手机号登录
                user = UserInfo.objects.filter(phone=username).first()
            elif re.match('^.+@.+$', username):
                # 以邮箱登录
                user = UserInfo.objects.filter(email=username).first()
            else:
                # 以用户名登录
                user = UserInfo.objects.filter(username=username).first()
            # 如果user有值并且密码正确
            if user and user.check_password(password):
                # 登录成功,生成token
                # drf-jwt中有通过user对象生成token的方法
                payload = jwt_payload_handler(user)
                token = jwt_encode_handler(payload)
                # token是要在视图类中使用,现在我们在序列化类中
                # self.context.get('request')
                # 视图类和序列化类之间通过context这个字典来传递数据
                self.context['token'] = token
                self.context['username'] = user.username
                return attrs
    
            else:
                raise ValidationError('用户名或密码错误')
    每天逼着自己写点东西,终有一天会为自己的变化感动的。这是一个潜移默化的过程,每天坚持编编故事,自己不知不觉就会拥有故事人物的特质的。 Explicit is better than implicit.(清楚优于含糊)
  • 相关阅读:
    Python3基础 dict get 在查询不存在的键时,返回指定的内容
    MySql和Sql的单行注释和多行注释的区别
    MySql Server 5.7的下载及安装详细步骤
    sql Server中临时表与数据表的区别
    Sql Server中集合的操作(并集、差集、交集)学习
    sql server2008 如何获取上月、上周、昨天、今天、本周、本月的查询周期(通过存储过程)
    sql server中的大数据的批量操作(批量插入,批量删除)
    sql Server如何执行批量插入和批量删除
    vs2015 企业版、专业版如何破解(秘钥)
    Dos命令下目录操作
  • 原文地址:https://www.cnblogs.com/kylin5201314/p/13969729.html
Copyright © 2011-2022 走看看