Welcome to kimi's blog

drf之jwt

接口文档

前后端分离项目,后端需要写接口,前端根据接口下app/pc/小程序

eg: 编写登录接口
 /api/v1/login/---->post---->username,password,编码方式json---->返回的格式{'code',100,'msg':'登录成功'}

编写往登录接口,一定要编写接口文档。

如何编写接口文档?

  1. 使用word/md编写接口文档

  2. 使用第三方平台,编写我们的接口文档(非常多)----收费

    https://www.showdoc.com.cn
    

image

image

  1. 公司自己使用第三方开源的搭建的---->Yapi--->可以自行搭建

    https://zhuanlan.zhihu.com/p/366025001 
    

    老刘建的开源jwt,自行在虚拟机上测试image-20230209154959824

  2. 使用drf编写的接口,可以自动生成接口文档

    swagger---》drf-yasg---》官方推荐使用
    coreapi----》下面
    

自动生成接口文档---coreapi

REST framework可以自动帮助我们生成接口文档。接口文档以网页的方式呈现。自动接口文档能生成的是继承自APIView及其子类的视图。

  1. 安装

    pip install coreapi
    
  2. 配置路由

    from rest_framework.documentation import include_docs_urls
    path('docs/',include_docs_urls(title='xx项目接口文档'))
      ## title 为接口文档网站的标题
    
  3. 在视图类,方法上写注释即可

    1.在类上加注释---单一方法的视图
    2.在类的方法上加注释---多个方法的视图
    3.在序列化类或者表模型的字段上加---help_text,required.....
    
  4. 在配置文件配置

    REST_FRAMEWORK = {
        'DEFAULT_SCHEMA_CLASS':'rest_framework.schemas.coreapi.AutoSchema'
    }
    
  5. 访问地址

http://127.0.0.1:8000/docs

接口文档的内容

1.描述
2.地址
3.请求方式
4.请求编码格式
5.请求数据详解(必填)
6.返回格式案列
7.返回数据字段解释
8.错误码
。。。。。

jwt介绍和原理

cookie/session/token发展史

jwt的介绍

在用户注册或登录后,想记录用户的登录状态,或者为用户创建身份认证的凭证。我们不再使用Session认证机制,而使用Json Web Token(本质就是token)认证机制。

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。JWT由三部分构成,每部分用.分割

JWT就是一段字符串,由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串。如下
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

1.第一个部分:头部/header
    声明类型,这里是jwt
    声明加密的算法,通常直接使用HMAC SHA256
    公司信息...
    eg:
        {
              'typ': 'JWT',
              'alg': 'HS256'
            }
        然后将头部进行base64加密
2.第二部分:荷载/payload     存放有效信息的地方
    过期时间
    签发时间:jat
    签发者:iss
    用户id
    用户名字....
        eg:
            {
              "sub": "1234567890",
              "name": "John Doe",
              "admin": true
            }
        然后进行base64加密
3.第三部分:签证/signature  
	JWT的第三部分是一个签证信息,这个签证信息由三部分组成:
            header (base64后的)
            payload (base64后的)
            secret  加盐
            
     这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
	 
	 
    注意:
	cookie:存在于客户端浏览器的键值对
	session:存在于服务端的键值对,可以存文件/内存/数据库等
	token:由头部、荷载和签名组成。
   		 -签发:根据登录请求提交来的 账号 + 密码 + 设备信息 签发 token。
    		 -认证:根据客户端带token的请求 反解出 user 对象
    
    """
    1.头和体是可逆加密,让服务器可以反解出user对象;签名是不可逆加密,保证整个token的安全性的
    2.头体签名三部分,都是采用json格式的字符串,进行加密,可逆加密一般采用base64算法,不可逆加密一般采用hash(md5)算法"""

jwt开发的重点

登录接口-----签发token
认证类------jwt认证

image

drf-jwt快速使用

django+drf平台开发jwt这套,主要由两个模块

djangorestframework-jwt     ----一直可以用
djangorestframeword-simplejwt------公司用的多---自行测试
自己封装jwt签发和认证

使用步骤

1. 安装

pip3.8 install djangorestframework-jwt

2. 快速签发token,登录接口,路由中配置

from rest_framework_jwt.views import obtain_jwt_token
path('login/',abtain_jwt_token),

# 需要自行创建一个新用户

基于djangorestframework-jwt 签发token就是jwt在django上封装好了一个登录接口,直接导入,配置abtain_jwt_token,自行形成了登录的接口,直接访问http://127.0.0.1:8000/login/就能签发用户的token,
需要后台创建一个新用户,签发token时直接携带用户和密码登录

3. postman测试工具测试

http://127.0.0.1:8000/login/ 发送post请求,携带username和password

image

4. 设置签发`token的过期时间

在settings中配置,可以设置秒/分钟/天数这三种【days=,minutes=,seconds=】


JWT_AUTH = {
 # 定制返回格式
  'JWT_RESPONSE_PAYLOAD_HANDLER': 'app01.utils.jwt_response_payload_handler',
  #  设置token的过期时间
  'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
}

秘钥使用的是setting中的SECRET_KEY = '^ko2og9a+d81_^a2#4v!po&9vy*d(^n16+qc&x@_wcuk!karx)'

djangorestframeword-simplejwt快速使用

jwt的认证类

以后接口要登录之后才能访问

  1. 在视图类加一个认证类,一个权限类

    class BookView(viweSetMixin,RetrieveAPIView):
        authentication_classes = [JSONWebTokenAuthentication]
        permission_classes=[IsAuthenticated] # 登录用户有权限,不登录用户没有权限
    
  2. postman测试

    请求头中key叫Authorization
    请求头的value值是:jwt 有效的token值-----中间一定要空格
    

    image

定制返回格式

基于django后天管理authUser表签发token,就可以不用写用户登录接口了,但是登录接口返回的格式,只有token,不符合公司规范。

使用步骤

image

  1. 写一个函数:jwt_response_payload_handler

    def jwt_response_payload_handler(token, user=None, request=None):
        return {
            'code': 999,
            'msg': '登录成功',
            'username': user.username,
            'token': token,
            # 'icon':user.icon
        }
    
  2. 配置一下项目配置文件

    JWT_AUTH={
        'JWT_RESPONSE_PAYLOAD_HANDLER':'app01.utils.jwt_response_payload_handler'
    }
    
    
     了解知识:
         'JWT_AUTH_HEADER_PREFIX': 'JWT',
         'JWT_SECRET_KEY': settings.SECRET_KEY,  # 秘钥配置
    
    
  3. 使用postman工具测试,就能看见返回的格式

image

drf-jwt源码执行流程(了解)

jwt的签发(登录)源码分析

# 1. 登录接口,路由匹配成功,执行obtain_jwt_token---》post请求---》ObtainJSONWebToken的post方法,但ObtainJSONWebToken没有post方法,就找父类的JSONWebTokenAPIView的post方法,发现父类继承了APIView
	path('login/', obtain_jwt_token),
    
    
#2. ObtainJSONWebToken的post方法 继承APIView
    def post(self, request, *args, **kwargs):
        # 实例化得到序列化类
        serializer = self.get_serializer(data=request.data)
        # 做校验:字段自己,局部钩子,全局钩子
        if serializer.is_valid():
            # user:当前登录用户
            user = serializer.object.get('user') or request.user
            # 签发的token
            token = serializer.object.get('token')
            # 构造返回格式,咱们可以自定制---》讲过了
            response_data = jwt_response_payload_handler(token, user, request)
            response = Response(response_data)
            if api_settings.JWT_AUTH_COOKIE:
                expiration = (datetime.utcnow() +
                              api_settings.JWT_EXPIRATION_DELTA)
                response.set_cookie(api_settings.JWT_AUTH_COOKIE,
                                    token,
                                    expires=expiration,
                                    httponly=True)
            #最终返回了咱们定制的返回格式
            return response

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    
    
    
    
 #3. 如何得到user,如何签发的token----》在序列化类的全局钩子中得到的user和签发的token
	-JSONWebTokenSerializer---全局钩子---validate
    	#前端传入,校验过后的数据---》{"username":"lqz","password":"lqz1e2345"}
        def validate(self, attrs):
        credentials = {
            # self.username_field: attrs.get(self.username_field),
            'username':attrs.get('username')
            'password': attrs.get('password')
        }

        if all(credentials.values()):
            # auth 模块,authenticate 可以传入用户名,密码如果用户存在,就返回用户对象,如果不存就是None
            # 正确的用户
            user = authenticate(**credentials)

            if user:
                # 校验用户是否是活跃用户,如果禁用了,不能登录成功
                if not user.is_active:
                    msg = _('User account is disabled.')
                    raise serializers.ValidationError(msg)
				# 荷载----》通过user得到荷载   {id,name,email,exp}
                payload = jwt_payload_handler(user)
				
                return {
                    # jwt_encode_handler通过荷载得到token串
                    '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. 通过user得到荷载:payload = jwt_payload_handler(user)
  2. 通过荷载签发token:jwt_encode_handler(payload)

了解

翻译函数,只需要输入国际化的英文,我们只需要在配置settings配置成某个语言,就会输出相应的语言。

	from django.utils.translation import ugettext as _
	msg = _('Unable to log in with provided credentials.')

image-20230210171411269

jwt的认证(认证类)源码分析

#1. JSONWebTokenAuthentication---->父类BaseJSONWebTokenAuthentication----》authenticate方法
    def authenticate(self, request):
        # 前端带在请求头中的token 值
        jwt_value = self.get_jwt_value(request)
        # 如果没有携带token,就不校验了
        if jwt_value is None:
            return None

        try:
            # jwt_value就是token
            # 通过token,得到荷载,中途会出错
            # 出错的原因:
            	-篡改token
                -过期了
                -未知错误
            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()

        # 如果能顺利解开,没有被异常捕获,说明token是可以信任的
        # payload就可以使用,通过payload得到当前登录用户
        user = self.authenticate_credentials(payload)
		# 返回当前登录用户,token
        return (user, jwt_value)
    
#2.解析 jwt_value = self.get_jwt_value(request)
    def get_jwt_value(self, request):
        # 拿到了前端请求头中传入的 jwt dasdfasdfasdfa
        # auth=[jwt,asdfasdfasdf]
        auth = get_authorization_header(request).split()
        # 'jwt'
        auth_header_prefix = api_settings.JWT_AUTH_HEADER_PREFIX.lower()

        if not auth:
            # 请求头中如果没带,去cookie中取
            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]
    

注意

认证类配置了,如果不传jwt,不会校验一定要配合权限类的使用

base64模块

base64python的内置模块,直接使用即可

  1. base64编码和解码

import base64
import json
dic = {'user_id':1,'username':'kiki'}

dic_str = json.dumps(dic)  # 转成json字符串
# 把字符串使用base64编码
res = base64.b64encode(dic_str.encode('utf-8'))

print(res) #b'eyJ1c2VyX2lkIjogMSwgInVzZXJuYW1lIjogImtpa2kifQ=='

# 注意:base64编码后,字符长度一定是4的倍数,如果不是,使用  =  补齐,  = 不表示数据
res1 = base64.b64decode(b'eyJ1c2VyX2lkIjogMSwgInVzZXJuYW1lIjogImtpa2kifQ==')
print(res1) # b'{"user_id": 1, "username": "kiki"}'
  1. 写入文章
    s=''
    res=base64.b64decode(s)
    with open('a.png','wb') as f:
        f.write(res)
  1. 应用场景
    1 jwt 使用了base64
    2 网络中传输数据,也会经常使用 base64编码
    3 网络传输中,有的图片使用base64编码

自定义用户表签发token

1. 签发

# 路由
from django.urls import path,include
from app01.views import UserView
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('login',UserView,'login')

urlpatterns = [
    path('api/v1/',include(router.urls))
]

# 视图
from rest_framework.response import Response
from rest_framework.decorators import action
from rest_framework.viewsets import ViewSet
from app01.models import UserInfo
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework_jwt.settings import api_settings
# 生成荷载的方法,我们直接调用drf的
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
# 生成token的方法,我们也调用drf的
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER


class UserView(ViewSet):
    @action(methods=['POST'], detail=False)
    def login(self, request, *args, **kwargs):
        username = request.data.get('username')
        password = request.data.get('password')
        user = UserInfo.objects.filter(username=username, password=password).first()
        if user:
            # 登录成功,签发token
            # 通过user得到payload
            payload = jwt_payload_handler(user)
            # 通过payload得到token
            token = jwt_encode_handler(payload)
            return Response({'code': 1000, 'msg': '登录成功', 'token': token})
        else:
            return Response({'code': 1001, 'msg': '用户名或密码错误'})
        
# 查看

image

2. 认证

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
import jwt
from .models import UserInfo
from rest_framework_jwt.settings import api_settings

jwt_decode_handler = api_settings.JWT_DECODE_HANDLER


class JsonWebTokenAuthentication(BaseAuthentication):
    def authenticate(self, request):
        # 取出token----》请求头中,就叫token
        token = request.META.get('HTTP_TOKEN')
        if token:
            try:
                payload = jwt_decode_handler(token)
                # 得到当前登录用户----》
                user = UserInfo.objects.get(pk=payload.get('user_id'))
                # 只要访问一次需要登录的接口,就会去UserInfo表中查一次用户---》优化
                # user=UserInfo(id=payload.get('user_id'),username=payload.get('username'))
                # user={'id':payload.get('user_id')}

                return user, token
            except jwt.ExpiredSignature:
                raise AuthenticationFailed('token过期')
            except jwt.DecodeError:
                raise AuthenticationFailed('token认证失败')

            except jwt.InvalidTokenError:
                raise AuthenticationFailed('token无效')
            except Exception as e:
                raise AuthenticationFailed('未知异常')

        raise AuthenticationFailed('token没有传,认证失败')
        
        
 # 设置token的过期时间
在settings设置
import datetime
JWT_AUTH={
    # 过期时间7天
    'JWT_EXPIRATION_DELTA':datetime.timedelta(days=7),
}

image

simpleui的使用

公司做项目---》要使用权限--》快速搭建后台管理--》使用django的admin直接搭建,但是django的admin界面不好,我可以借助第三方的软件美化。

# 第三方美化的软件
	xadmin:作者弃坑了,bootstrap+jq
    simpleui:vue 界面更好看
    
    
# 现阶段,一般前后端分离比较多:django+vue
    1.带权限的前后端分离的快速开发框架
    2.django-vue-admin
    3.自己写
  1. 使用步骤

    1.安装
    	pip install django-simpleui
        
    2。在app中注册simpleui
    3.建模型表BooK,Publish,Author,AuthorDetail
    4.admin.py管理注册
    	from .models import Author,Book,Publish,AuthorDetail
        admin.site.register(Book)
        admin.site.register(Publish)
        admin.site.register(Author)
        admin.site.register(AuthorDetail)
    
  2. 美化

    1.美化图书标题----》在appps.py指定app名字
    	class App01Config(AppConfig):
        name = 'app01'
        verbose_name='图书管理系统'
        
    2.将项目英文名改为中文名----models.py
    	    def __str__(self):
                return self.name
    
            class Meta:
                verbose_name ='图书表'  # 在simpleui后台会加图书表s
                verbose_name_plural=verbose_name
                
    # 3 调整左侧导航栏----》
    	-menu_display对应menus name
        -如果是项目的app,就menus写app
        -菜单可以多级,一般咱们内部app,都是一级
        -可以增加除咱们app外的其它链接---》如果是外部链接,直接写地址,如果是内部链接,跟之前前后端混合项目一样的写法:咱们的案例---》show 的路由
    SIMPLEUI_CONFIG = {
        'system_keep': False,
        'menu_display': ['图书管理', '权限认证', '张红测试'],  # 开启排序和过滤功能, 不填此字段为默认排序和全部显示, 空列表[] 为全部不显示.
        'dynamic': True,  # 设置是否开启动态菜单, 默认为False. 如果开启, 则会在每次用户登陆时动态展示菜单内容
        'menus': [
            {
                'name': '图书管理',
                'app': 'app01',
                'icon': 'fas fa-code',
                'models': [
                    {
                        'name': '图书',
                        'icon': 'fa fa-user',
                        'url': 'app01/book/'
                    },
                    {
                        'name': '出版社',
                        'icon': 'fa fa-user',
                        'url': 'app01/publisssh/'
                    },
                    {
                        'name': '作者',
                        'icon': 'fa fa-user',
                        'url': 'app01/author/'
                    },
                    {
                        'name': '作者详情',
                        'icon': 'fa fa-user',
                        'url': 'app01/authordetail/'
                    },
                ]
            },
            {
                'app': 'auth',
                'name': '权限认证',
                'icon': 'fas fa-user-shield',
                'models': [
                    {
                        'name': '用户',
                        'icon': 'fa fa-user',
                        'url': 'auth/user/'
                    },
                    {
                        'name': '组',
                        'icon': 'fa fa-user',
                        'url': 'auth/group/'
                    },
                ]
            },
            {
    
                'name': '张红测试',
                'icon': 'fa fa-file',
                'models': [
                    {
                        'name': 'Baidu',
                        'icon': 'far fa-surprise',
                        # 第三级菜单 ,
                        'models': [
                            {
                                'name': '爱奇艺',
                                'url': 'https://www.iqiyi.com/dianshiju/'
                                # 第四级就不支持了,element只支持了3级
                            }, {
                                'name': '百度问答',
                                'icon': 'far fa-surprise',
                                'url': 'https://zhidao.baidu.com/'
                            }
                        ]
                    },
                    {
                        'name': '大屏展示',
                        'url': '/show/',
                        'icon': 'fab fa-github'
                    }]
            }
        ]
    } 
        
    # 4 内部app,图书管理系统某个链接要展示的字段---》在admin.py 中----》自定义按钮
    @admin.register(Book)
    class BookAdmin(admin.ModelAdmin):
        list_display = ('nid', 'name', 'price', 'publish_date', 'publish')
    
        # 增加自定义按钮
        actions = ['custom_button']
    
        def custom_button(self, request, queryset):
            print(queryset)
    
        custom_button.confirm = '你是否执意要点击这个按钮?'
        # 显示的文本,与django admin一致
        custom_button.short_description = '测试按钮'
        # icon,参考element-ui icon与https://fontawesome.com
        # custom_button.icon = 'fas fa-audio-description'
        # # 指定element-ui的按钮类型,参考https://element.eleme.cn/#/zh-CN/component/button
        custom_button.type = 'danger'
        # # 给按钮追加自定义的颜色
        # custom_button.style = 'color:black;'
         
    # 5 app名字显示中文,字段名字显示中文
    	-新增,查看修改展示中文,在表模型的字段上加:verbose_name='图书名字',help_text='这里填图书名'
    	-app名字中文:apps.py---》verbose_name = '图书管理系统'
    
        
    # 6 其它配置项
    	SIMPLEUI_LOGIN_PARTICLES = False  #登录页面动态效果
        SIMPLEUI_LOGO = 'https://avatars2.githubusercontent.com/u/13655483?s=60&v=4'#图标替换
        SIMPLEUI_HOME_INFO = False  #首页右侧github提示
        SIMPLEUI_HOME_QUICK = False #快捷操作
        SIMPLEUI_HOME_ACTION = False # 动作
    
  3. 大屏展示

    # 监控大屏展示
    	-https://search.gitee.com/?skin=rec&type=repository&q=%E5%B1%95%E7%A4%BA%E5%A4%A7%E5%B1%8F
            
        -就是前后端混合项目,js,css,图片对应好,就可以了
    

权限控制--acl/rbac

公司内部项目

rbac:是基于角色的访问控制(Role-Based Access Control )在 RBAC 中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便

    -用户表:用户和角色多对多关系,
    -角色表
    	-一个角色可能有多个权限----》
        	-开发角色:拉取代码,上传代码
        	-财务角色:开工资,招人,开除人
            -
    -权限表:角色和权限是多多多
    	-拉取代码
        -上传代码
        -部署项目
        -开工资
        -招人
        -开除人
        -
    -通过5张表完成rbac控制:用户表,角色表,权限表, 用户角色中间表, 角色权限中间表
    -如果某个人,属于财务角色,单只想要拉取代码权限,不要上传代码权限
    -通过6张表:django的admin----》后台管理就是使用这套权限认证
    	用户表,
        角色表,
        权限表, 
        用户角色中间表, 
        角色权限中间表
        用户和权限中间表
        
        
        
 # 演示了 django-admin 的权限控制
	-授予lqz 某个组
    -单独授予权限
    
    
# django -auth--6张表
	auth_user   用户表
	auth_group  角色表,组表
    auth_permission  权限表
    -----------
    auth_user_groups   用户和角色中间表
    auth_group_permissions  角色和权限中间表
	-------------
    auth_user_user_permissions  用户和权限中间表
    
    
    
# java:若依
# go :gin-vue-admin
# python :django-vue-admin

互联网项目

	-acl:Access Control List 访问控制列表,权限放在列表中
    -权限:权限表----》 发视频,评论,开直播
    -用户表:用户和权限是一对多
    
    张三:[发视频,]
    李四:[发视频,评论,开直播]

补充知识

#1 django 的auth  user表,密码是加密的,即便的同样的密码,密文都不一样
	-每次加密,都随机生成一个盐,把盐拼在加密后的串中
    
    # 比如
    pbkdf2_sha256$260000$B9ZRmPFpWb3H4kdDDmgYA9$CM3Q/ZfYyXzxwvjZ+HOcdovaJS7681kDsW77hr5fo5o=

    明文:lqz12345 
    盐:B9ZRmPFpWb3H4kdDDmgYA9

    后期来了明文lqz12345


#2 自定义用户表,生成密码用密文
from django.contrib.auth.hashers import make_password


#3  用户表的密码忘了怎么办
	-新增一个用户,把它的密码复制过去
 
# 4 双token认证
posted @ 2023-02-09 21:30  魔女宅急便  阅读(66)  评论(0)    收藏  举报
Title