DRF-Django rest framework
1. 视图继承示例
1. 项目需求
1. 自定义User表扩展auth的User
新增mobile唯一约束字段;新增icon图片字段
2. 在自定义User表基础上
用 GenericViewSet + CreateModelMixin + serializer 完成User表新增接口(就是注册接口)
(重要提示:序列化类要重写create方法,不然密码就是明文了)
3. 在自定义User表基础上
用 GenericViewSet + RetrieveModelMixin + serializer 完成User表单查(就是用户中心)
4. 在自定义User表基础上
用 GenericViewSet + UpdateModelMixin + serializer 完成用户头像的修改
2. 后台文件编写
1. settings.py
# settings 文件的配置
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR,'media')
AUTH_USER_MODEL = 'homework.UserInfo'
2. url.py
from django.contrib import admin
from django.urls import path,re_path
from homework import views
# 自动生成路由
from rest_framework.routers import SimpleRouter
router=SimpleRouter()
router.register('user',views.UserView)
router.register('userinfo',views.UserReadOnlyView)
urlpatterns = [
# path('user/',views.UserView.as_view({'post':'create'})),
# path('',include(router.urls)),
]
urlpatterns += router.urls
3. models.py
from django.db import models
# Create your models here.
'''扩写django自带的auth_user表模型'''
from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
mobile = models.CharField(max_length=32,unique=True)
icon = models.ImageField(upload_to='head',default='head/default.png')
4. serializer.py
from rest_framework import serializers
from homework.models import UserInfo
from rest_framework.exceptions import ValidationError
class UserModelSerializer(serializers.ModelSerializer):
# 注册功能 需要的字段
# username password re_password mobile
# re_password 字段在表中没有 需改写成 write_only=True
re_password = serializers.CharField(max_length=18,min_length=3,required=True,write_only=True)
class Meta:
model = UserInfo
fields = ['username','password','mobile','re_password','icon']
extra_kwargs = {
'username': {'max_length':12,'min_length':3},
'password': {'write_only': True},
'icon': {'read_only': True}
}
# mobile的局部钩子
def validate_mobile(self,data):
if len(data) == 11:
return data
else:
raise ValidationError('手机号不合法')
# 全局钩子校验两次密码是否一致
def validate(self, attrs):
password = attrs.get('password')
re_password = attrs.get('re_password')
if password == re_password:
# re_password 不存数据库 剔除
attrs.pop('re_password')
return attrs
else:
raise ValidationError('两次密码不一致')
# 重写create方法 实现密码的加密
def create(self,validated_data):
# re_password 字段也可以在这里移除
# UserInfo.objects.create(**validated_data) 此方法的密码是明文
user = UserInfo.objects.create_user(**validated_data)
return user
# 序列化的时候 一个模型类 不一定对着一个序列化类
# 只做序列化用
class UserReadOnlyModelSerializer(serializers.ModelSerializer):
class Meta:
model = UserInfo
fields = ['username','mobile','icon','email','date_joined']
# 只做修改头像用
class UserIconModelSerializer(serializers.ModelSerializer):
class Meta:
model = UserInfo
fields = ['icon',]
5. views.py
from django.shortcuts import render
from homework.models import UserInfo
from homework.serializer import UserModelSerializer,UserReadOnlyModelSerializer,UserIconModelSerializer
from rest_framework.mixins import CreateModelMixin,RetrieveModelMixin,UpdateModelMixin
from rest_framework.generics import GenericAPIView
from rest_framework.viewsets import GenericViewSet,ViewSetMixin
# ViewSetMixin: 自动生成路由
# GenericAPIView: 必须指定queryset和serializer
# class UserView(ViewSetMixin,GenericAPIView,CreateModelMixin):
class UserView(GenericViewSet,CreateModelMixin,RetrieveModelMixin,UpdateModelMixin):
queryset = UserInfo.objects.all()
serializer_class = UserModelSerializer
# 重写get_serializer_class方法 实现不同的请求 返回的序列化类不一样
def get_serializer_class(self):
# 可以根据请求方式
# 1. 用户注册
if self.action == 'create':
return UserModelSerializer
# 2. 查询用户
elif self.action == 'retrieve':
return UserReadOnlyModelSerializer
# 3. 修改头像
elif self.action == 'update':
return UserIconModelSerializer
# UserReadOnlyView
class UserReadOnlyView(GenericViewSet,RetrieveModelMixin):
queryset = UserInfo.objects.all()
serializer_class = UserReadOnlyModelSerializer
2. jwt认证
1. jwt认证介绍
在用户注册或登录后,我们想记录用户的登录状态,或者为用户创建身份认证的凭证。我们不再使用Session认证机制,而使用 Json Web Token(本质就是token)认证机制。
用户只要登录了,返回用户一个token串(随机字符串),每次用户发请求,需要携带这个串过来,验证通过,我们就认为用户登录了。
Json web token (JWT)
是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519)
该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景
JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息
该token也可直接被用于认证,也可被加密
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CcDsI3YD-1605370077872)(.\image-20201113204446643.png)] [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jDLjClfe-1605370077874)(.\image-20201113204552690.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YA2UXjdL-1605370077875)(.\image-20201113204632256.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2ckiDtXb-1605370077877)(.\image-20201113204709365.png)]
2. 构成和工作原理
JWT的构成
# 简介
JWT就是一段字符串,由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串
第一部分我们称它为头部 header
第二部分我们称其为载荷 payload
第三部分是签证 signature
# 示例
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
1. header
# jwt的头部承载两部分信息
1. 声明类型,这里是jwt
2. 声明加密的算法 通常直接使用 HMAC SHA256
# 完整的头部就像下面这样的JSON
{
'typ': 'JWT',
'alg': 'HS256'
}
# 然后将头部进行base64编码(该编码方式可以解码) 构成第一部分
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
2. payload
# 载荷就是存放有效信息的地方 这些有效信息包含三个部分
1. 标准中注册的声明
2. 公共的声明
3. 私有的声明
# 标准中注册的声明 (建议但不强制使用)
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避时序攻击
# 公共的声明
公共的声明可以添加任何的信息
一般添加用户的相关信息或其他业务需要的必要信息,但不建议添加敏感信息,因为该部分在客户端可解密
# 私有的声明
私有声明是提供者和消费者所共同定义的声明
一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息
# 定义一个payload 当前用户的信息(用户名,id,这个token的过期时间,手机号)
{
"sub": "1234567898",
"name": "egon",
"admin": true,
"userid":1,
'mobile':123444444
}
# 然后将其进行base64加密,得到JWT的第二部分
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
3. signature
# JWT的第三部分是一个签证信息,这个签证信息由三部分组成
1. header (base64编码后的)
2. payload (base64编码后的)
3. secret
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分
# 示例
// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
# 把前面两部分的内容通过加密算法+密钥加密后得到的一个字符串 用.连接成一个完整的字符串,构成了最终的jwt
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
# 注意
secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以它就是你服务端的私钥,在任何场景都不应该流露出去。
一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
关于签发和核验JWT,我们可以使用Django REST framework JWT扩展来完成。
# 文档网站
http://getblimp.github.io/django-rest-framework-jwt/
4. JWT认证原理
# jwt认证算法:签发与校验
1. jwt分三段式:头.体.签名 (head.payload.sgin)
2. 头和体是可逆加密,让服务器可以反解出user对象;签名是不可逆加密,保证整个token的安全性的
3. 头体签名三部分,都是采用json格式的字符串,进行加密
可逆加密一般采用base64算法
不可逆加密一般采用hash(md5)算法
4. 头中的内容是基本信息:公司信息、项目组信息、token采用的加密方式信息
{
"company": "公司信息",
...
}
5. 体中的内容是关键信息:用户主键、用户名、签发时客户端信息(设备号、地址)、过期时间
{
"user_id": 1,
...
}
6. 签名中的内容时安全信息:头的加密结果 + 体的加密结果 + 服务器不对外公开的安全码 进行md5加密
{
"head": "头的加密字符串",
"payload": "体的加密字符串",
"secret_key": "安全码"
}
# 签发
"""
根据登录请求提交来的 账号 + 密码 + 设备信息 签发 token
1. 用基本信息存储json字典,采用base64算法加密得到 头字符串
2. 用关键信息存储json字典,采用base64算法加密得到 体字符串
3. 用头、体加密字符串再加安全码信息存储json字典,采用hash md5算法加密得到 签名字符串
账号密码就能根据User表得到user对象,形成的三段字符串用 . 拼接成token返回给前台
"""
# 校验
"""
根据客户端带token的请求 反解出 user 对象
1. 将token按 . 拆分为三段字符串,第一段 头加密字符串 一般不需要做任何处理
2. 第二段 体加密字符串,要反解出用户主键,通过主键从User表中就能得到登录用户
过期时间和设备信息都是安全信息,确保token没过期,且时同一设备来的
3. 再用 第一段 + 第二段 + 服务器安全码 不可逆md5加密,与第三段 签名字符串 进行碰撞校验
通过后才能代表第二段校验得到的user对象就是合法的登录用户
"""
# drf项目的jwt认证开发流程
1. 用户登录
用户携带用户名,密码登录我的系统,登录接口逻辑中调用签发token ,生成一个token(三部分),返回给用户,客户端自己存到cookies中
2. 登录功能完成
访问需要登录的接口(用户中心),必须携带token过来,后端拿到token后,把header和payload截出来,再通过一样的加密方式和密码得到一个signature,和该token的signature比较,如果一样,表示是正常的token,就可以继续往后访问
# 注意
登录接口需要做 认证 + 权限 两个局部禁用
3. 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=
# 需要用bytes格式
# 编码
base64_str=base64.b64encode(dic_str.encode('utf-8'))
print(base64_str)
# 解码
res_bytes=base64.b64decode('eyJuYW1lIjogImxxeiIsICJhZ2UiOiAxOH0=')
print(res_bytes)
4. jwt基本使用
jwt内置,控制用户登录后能访问和未登陆能访问
# drf-jwt安装和简单使用
1. 官网
https://github.com/jpadilla/django-rest-framework-jwt
2. 安装
pip3 install djangorestframework-jwt
3. 快速使用(默认使用auth的user表)
1. 在默认auth的user表中创建一个用户
python3 manage.py createsuperuser
2. 配置路由urls.py
path('login/', obtain_jwt_token),
3. postman测试
用postman向这个地址发送post请求,携带用户名,密码,登陆成功就会返回token
4. obtain_jwt_token本质也是一个视图类,继承了APIView
通过前端传入的用户名密码,校验用户,如果校验通过,生成token,返回
如果校验失败,返回错误信息
5. setting.py中配置认证使用jwt提供的jsonwebtoken
postman发送访问请求(必须带jwt空格)
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
5. 使用Django auth的User表自动签发
1. 控制登录接口返回的数据格式
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. 编写序列化类ser.py
from rest_framework import serializers
from users import models
class UserReadOnlyModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserInfo
fields = ['username']
4. 在setting.py中配置
import datetime
JWT_AUTH = {
# 过期时间1天
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
# 自定义认证结果:见下方序列化user和自定义response
# 如果不自定义,返回的格式是固定的,只有token字段
'JWT_RESPONSE_PAYLOAD_HANDLER': 'homework.utils.jwt_response_payload_handler',
}
2. 自定义基于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())
if not token:
raise AuthenticationFailed('token 字段是必须的')
# 校验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, ]
# 局部禁用
authentication_classes = []
# 自定义Response
class CommonResponse(Response):
def __init__(self,status,msg,data='',*args,**kwargs):
dic={'status':status,'msg':msg,'data':data}
super().__init__(data=dic,*args,**kwargs)
# 测试订单接口
from users.app_auth import JSONWebTokenAuthentication
class OrderView(APIView):
# authentication_classes = [JSONWebTokenAuthentication]
authentication_classes = []
def get(self,request):
return CommonResponse('100', '成功',{'数据':'测试'})
4. 全局使用
# setting.py
REST_FRAMEWORK = {
# 认证模块
'DEFAULT_AUTHENTICATION_CLASSES': (
'users.app_auth.JSONWebTokenAuthentication',
),
}