DRF【jwt认证组件】
前后端分离项目都用token认证
前后端不分离的项目都用cookie和session认证
通用配置
总路由
from django.urls import path, include
import xadmin
xadmin.autodiscover()
# version模块自动注册需要版本控制的 Model
from xadmin.plugins import xversion
xversion.register_models()
urlpatterns = [
# path('admin/', admin.site.urls),
path(r'xadmin/', xadmin.site.urls),
path('books/', include("SerDemo.urls")),
]
模型
from django.db import models
__all__ = ["Book", "Publisher", "Author"]
class Book(models.Model):
title = models.CharField(max_length=32, verbose_name="图书名称")
CHOICES = ((1, "Python"), (2, "Go"), (3, "Linux"))
category = models.IntegerField(choices=CHOICES, verbose_name="图书的类别")
pub_time = models.DateField(verbose_name="图书的出版日期")
publisher = models.ForeignKey(to="Publisher", on_delete=None)
author = models.ManyToManyField(to="Author")
def __str__(self):
return self.title
class Meta:
db_table = "book"
verbose_name_plural = "图书表"
class Publisher(models.Model):
title = models.CharField(max_length=32, verbose_name="出版社的名称")
def __str__(self):
return self.title
class Meta:
db_table = "publisher"
verbose_name_plural = "出版社表"
class Author(models.Model):
name = models.CharField(max_length=32, verbose_name="作者的姓名")
def __str__(self):
return self.name
class Meta:
db_table = "author"
verbose_name_plural = "作者表"
认证准备工作
新建子应用user
python3 manage.py startapp user
注册子应用
INSTALLED_APPS = [
...
...
...
#子应用
'user',
]
创建用户模型类models.py
user/models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
"""用户模型类"""
# 如果手机设置 IntegerField的话,只有10位数字,并且有正有负
# 手机号不需要计算,所以不需要设置int类型,设置字符串即可
mobile = models.CharField(max_length=15, unique=True, verbose_name='手机号')
avatar = models.ImageField(upload_to="avator",null=True,blank=True,verbose_name="用户头像")
class Meta:
db_table = 'users'
verbose_name = '用户'
verbose_name_plural = verbose_name
注册用户模型类
自定义的用户模型类还不能直接被Django的认证系统所识别,需要在配置文件中告知Django认证系统使用自定义的模型类。
settings.py
#注册自定义user模型 --> '子应用名.models类名'
AUTH_USER_MODEL = 'user.User'
数据库迁移
python3 manage.py makemigrations
python3 manage.py migrate
若报错,解决方案
如果在第一次数据迁移以后,才设置 AUTH_USER_MODEL 自定义用户模型,则会报错。解决方案如下:
0. 先把现有的数据库导出备份,然后清掉数据库 中 所有的数据表,不删库,只删除下面所有的表。
1. 把开发者创建的所有子应用apps/下面的migrations目录下除了__init__.py以外的所有迁移文件,只要涉及到用户的,一律删除
2. 把django.contrib.admin.migrations目录下除了__init__.py以外的所有迁移文件,全部删除。
3. 把django.contrib.auth.migrations目录下除了__init__.py以外的所有迁移文件,全部删除。
4. 把reversion.migrations目录下除了__init__.py以外的所有迁移文件,全部删除。
5. 把xadmin.migrations目录下除了__init__.py以外的所有迁移文件,全部删除。
6. 接下来,重新执行数据迁移,回顾第0步中的数据,以后如果要修改用户相关数据,不需要重复本次操作,直接数据迁移即可。
python manage.py makemigrations
python manage.py migrate
7. 录入备份的数据
解决步骤0-6
录入备份数据
- 录入之前先迁移数据库,先新建表之后在录入数据
重新创建xadmin超级用户
python3 manage.py createsuperuser
用户名 (leave blank to use 'wangjunxiang'): root
电子邮件地址: root@root.com
Password: 123123
Password (again): 123123
密码长度太短。密码必须包含至少 8 个字符。
这个密码太常见了。
密码只包含数字。
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.
实现JWT认证
安装&配置 JWT
pip3 install djangorestframework-jwt -i https://pypi.tuna.tsinghua.edu.cn/simple
settings.py
REST_FRAMEWORK = {
#jwt登录认证
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
}
# 设置jwt token认证有效期
import datetime
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),#可以设置seconds minutes hours days weeks
}
总路由分发子路由
urls.py
urlpatterns = [
...
path('user/', include('user.urls')),
# include 的值必须是 模块名.urls 格式,字符串中间只能出现一个圆点
]
新建子路由
新建文件 user/urls.py
from rest_framework_jwt.views import obtain_jwt_token
from django.urls import path
urlpatterns = [
path(r'login/', obtain_jwt_token),
]
测试认证
如果什么都不带 直接发送 127.0.0.1:8000/user/login/ 的 post 请求,会返回

如果发送post请求 带如下参数 ,则返回token,需要前端将token存在浏览器中,这样下次继续访问,会带着这个token 和其他参数去访问,后端先接收到token验证成功后,就会转给对应的接口响应需要的数据
这个是之前创建超级用户的账号和密码
{
"username": "root",
"password": "123123"
}

设置jwt返回自定义数据
jwt返回自定义数据utils.py
新建 user/utils.py 作为 user通用的
- 当然如果公司有其他需求,需要返回用户头像等其他的数据
- 修改这个函数的返回值即可,比如返回 数据库中用户头像的url字段
- 返回值全都自定义
def jwt_response_payload_handler(token, user=None, request=None):
"""
自定义jwt认证成功返回数据
token:本次登录成功后,返回的jwt数据
user:本次登陆后,从数据库查询到的用户模型信息
request:本次客户端的请求对象,
"""
return {
# token 就是前段请求 生成 保存在客户端的的token数据
'token': token,
# user.id 和 user.username 都是从user用户数据库模型中的数据库.字段获取
'id': user.id,
'username': user.username
}
配置jwt返回自定义数据生效
settings/dev.py
在 JWT_AUTH 中新增
#JWT_AUTH = {
....
....
# 设置返回数据的格式
'JWT_RESPONSE_PAYLOAD_HANDLER': 'user.utils.jwt_response_payload_handler',
#}
测试返回数据
同样携带json的账户和密码信息 post 请求 127.0.0.1:8000/user/login/
{
"username": "root",
"password": "123123"
}
可以返回自定义数据

jwt的多条件登录
例如手机号登录
思路
-
JWT扩展的登录视图,在收到用户名与密码时,也是调用Django的认证系统中提供的authenticate()来检查用户名与密码是否正确。
-
可以通过修改Django认证系统的认证后端(主要是authenticate方法)来支持登录账号既可以是用户名也可以是手机号。
修改Django认证系统的认证后端需要继承django.contrib.auth.backends.ModelBackend,并重写authenticate方法
authenticate(self, request, username=None, password=None, **kwargs)
方法的参数说明:
- request 本次认证的请求对象
- username 本次认证提供的用户账号
- password 本次认证提供的密码
想让用户既可以 用户名登录,也可以 手机号登录,那么对于authenticate方法而言,前端既可以把用户名或者手机号传后端验证,但是都存在username变量中,也就是username有可能是用户名,也有可能是手机号
重写authenticate方法的思路
-
根据username参数查找用户User对象,username参数可能是用户名,也可能是手机号
-
若查找到User对象,调用User对象的check_password方法检查密码是否正确
新增自定义认证方法utils.py
users/utils.py中新增:
# def jwt_response_payload_handler(token, user=None, request=None):
# """
# 自定义jwt认证成功返回数据
# token:本次登录成功后,返回的jwt数据
# user:本次登陆后,从数据库查询到的用户模型信息
# request:本次客户端的请求对象,
# """
# return {
# # token 就是前段请求 生成 保存在客户端的的token数据
# 'token': token,
# # user.id 和 user.username 都是从user用户数据库模型中的数据库.字段获取
# 'id': user.id,
# 'username': user.username
# }
#
#如果还要加用邮箱登录吧mobile改为email就行
from .models import User
from django.db.models import Q #Q对象用来做多条件查询
from django.contrib.auth.backends import ModelBackend
def get_user_by_account(account):
"""
根据帐号获取user对象
:param account: 账号,可以是用户名username,也可以是手机号mobile,或者其他的数据
:return: User对象 或者 None
"""
try:
# Q对象多条件查询, '|' 代表或者
user = User.objects.filter( Q(username=account) | Q(mobile=account)).first()
except User.DoesNotExist:
return None
else:
return user
class UsernameMobileAuthBackend(ModelBackend): #继承ModelBackend
"""
自定义用户名或手机号认证
"""
def authenticate(self, request, username=None, password=None, **kwargs):
user = get_user_by_account(username)
# user模型下有check_password方法比较 ,user.is_authenticated用来验证权限
if user is not None and user.check_password(password) and user.is_authenticated:
return user
else:
return None
注册自定义认证方式生效
settings.py
AUTHENTICATION_BACKENDS = [
'user.utils.UsernameMobileAuthBackend',
]
测试多条件登录
之前是post请求 127.0.0.1:8000/user/login/ 接口
携带username 和 password,现在将username的root 换为了手机号,同样可以返回token

浙公网安备 33010602011771号