django的用户管理与token认证(四)
一、django的用户管理
1.用户认证介绍
django的用户认证主要是用于用户的登陆认证上,如果用户名和密码正确,然后做对应的操作
Django的认证组件使用的默认用户模型类(User)存储用户信息,包括用户名、密码和电子邮件地址。
在执行
python manage.py makemigrations
在数据库中会生成几张默认的表
mysql> show tables;
+----------------------------+
| Tables_in_mydatabase |
+----------------------------+
| auth_group |
| auth_group_permissions |
| auth_permission |
| auth_user |
| auth_user_groups |
| auth_user_user_permissions |
| django_admin_log |
| django_content_type |
| django_migrations |
| django_session |
+----------------------------+
其中auth_user就是存储用户的表,默认这张表中没有用户。表结构如下:
mysql> desc auth_user;
+--------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| password | varchar(128) | NO | | NULL | |
| last_login | datetime(6) | YES | | NULL | |
| is_superuser | tinyint(1) | NO | | NULL | |
| username | varchar(150) | NO | UNI | NULL | |
| first_name | varchar(150) | NO | | NULL | |
| last_name | varchar(150) | NO | | NULL | |
| email | varchar(254) | NO | | NULL | |
| is_staff | tinyint(1) | NO | | NULL | |
| is_active | tinyint(1) | NO | | NULL | |
| date_joined | datetime(6) | NO | | NULL | |
+--------------+--------------+------+-----+---------+----------------+
11 rows in set (0.00 sec)
2.用户的Models类(User)
django使用User模型类来对应auth_user表,我们可以通过User类来对auth_user表进行操作
这里单独创建一个user应用
django-admin startapp user
2.1 创建序列化器
在user应用下新建serializers.py文件
from rest_framework.serializers import ModelSerializer
# 导入django提供的 User 数据库models类
from django.contrib.auth.models import Userclass auth_user_serializer(ModelSerializer):class Meta:model = User# 根据需求选择一些必要的字段进行序列化。fields = ["id","username","is_active","is_superuser","last_login"]
2.2 设置路由
# 设置总路由:
urlpatterns = [path('user/',include("user.urls"))
]# 设置子路由:
from django.urls import path
from .views import *urlpatterns = [path('',userview.as_view())
]
2.3 新增用户和获取所有用户
编辑views.py文件,实现获取所有用户和创建用户的功能
from django.contrib.auth.models import User
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.generics import ListAPIView,RetrieveAPIView
from .serializers import auth_user_serializerclass userview(ListAPIView):# 前两行代码 主要是实现获取所有用户列表queryset = User.objects.all()serializer_class = auth_user_serializer# 实现添加用户的功能,之所以没有使用CreateAPIViews视图类。是因为User类提供了两个创建用户的方法:# (1)create_user() : 创建普通用户# (2)create_superuser(): 创建管理员用户def post(self,request):data = request.dataser_data = auth_user_serializer(data=data)if ser_data.is_valid():# 这里只是创建了普通用户,这里可以根据用户传递的参数决定创建普通用户还是管理员User.objects.create_user(**ser_data.validated_data)return Response("{}用户创建成功".format(data['username']),status=200)else:return Response(ser_data.errors,status=405)
2.4 测试
测试创建用户是否成功
POST http://127.0.0.1:8000/user/ 提交数据 {"username":"admin","passwowrd":"123"}
测试获取所有用户是否成功
http://127.0.0.1:8000/user/# 获取数据如下
[{"id": 1,"username": "admin","is_active": true,"is_superuser": false,"last_login": null}
]
查看数据库中是否有数据。可以看出确实User模型类对应的是auth_user表
mysql> select * from auth_user\G
*************************** 1. row ***************************id: 1password: pbkdf2_sha256$260000$0AYS0xx7BRn7P9P46a3Q8Y$V9aVCZ6jOgVWK3e8ZF/PkPu3Wldoc6DzrXRB9c5tFfM=last_login: NULL
is_superuser: 0username: adminfirst_name: last_name: email: is_staff: 0is_active: 1date_joined: 2024-02-22 02:27:08.195161
1 row in set (0.00 sec)
2.5 修改用户密码
from django.contrib.auth.models import User
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.generics import ListAPIView,RetrieveAPIView
from .serializers import auth_user_serializerclass userview(ListAPIView):queryset = User.objects.all()serializer_class = auth_user_serializerdef post(self,request):data = request.dataser_data = auth_user_serializer(data=data)if ser_data.is_valid():User.objects.create_user(**ser_data.validated_data)User.objects.create_superuser()return Response("{}用户创建成功".format(data['username']),status=200)else:return Response(ser_data.errors,status=405)# 修改密码功能def put(self,request):data = request.datafield_list = ["username","password"]# 判断是否确实必须字段for field in field_list:if not field in data:return Response({"message": "'{}是必填写字段'.format(field)"})try:# 这里之所以使用用户名过滤而没有使用ID,是因为用户名有唯一性约束query_res = User.objects.get(username=data["username"])except Exception:return Response({"message":"用户不存在,无法修改密码"},status=405)try:query_res.set_password(data["password"])query_res.save()except Exception:return Response({"message":"密码修改失败"},status=500)else:return Response({"message": "密码修改成功!"},status=200)
2.6 删除用户功能
from django.contrib.auth.models import User
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.generics import ListAPIView,RetrieveAPIView
from .serializers import auth_user_serializerclass userview(ListAPIView):queryset = User.objects.all()serializer_class = auth_user_serializerdef post(self,request):data = request.dataser_data = auth_user_serializer(data=data)if ser_data.is_valid():User.objects.create_user(**ser_data.validated_data)User.objects.create_superuser()return Response("{}用户创建成功".format(data['username']),status=200)else:return Response(ser_data.errors,status=405)# 修改密码功能def put(self,request):data = request.datafield_list = ["username","password"]# 判断是否确实必须字段for field in field_list:if not field in data:return Response({"message": "'{}是必填写字段'.format(field)"})try:# 这里之所以使用用户名过滤而没有使用ID,是因为用户名有唯一性约束query_res = User.objects.get(username=data["username"])except Exception:return Response({"message":"用户不存在,无法修改密码"},status=405)try:query_res.set_password(data["password"])query_res.save()except Exception:return Response({"message":"密码修改失败"},status=500)else:return Response({"message": "密码修改成功!"},status=200)# 删除用户功能def delete(self,request):data = request.dataif "username" in data:try:query_data = User.objects.get(username=data["username"])except Exception:return Response({"message": "{}用户不存在".format(data["username"])},status=500)else:query_data.is_active = 0query_data.save()return Response({"message": "{}用户删除成功".format(data["username"])},status=200)else:return Response({"message": "字段错误,username为必填字段"})
2.7 命令行操作
在命令创建管理员,注意一定要先迁移数据库,也就是说要在数据库中必须要生成auth_user表
python manage.py createsuperuser
二、用户认证
在上边已经了解到了django的用户模型类(User).然后通过自己编写的功能实现用户的增删改查。那么用户访问的时候就可以通过User模型类创建好的用户访问django的其它接口。
那么用户携带的用户名和密码到底对不对呢?django是怎么判断的?,当用户携带的用户名和密码正确,后续的操作又该做什么?这就需要用到了 “DRF的认证”
DRF的认证一共有4种,如下:
| 认证方式 | 作用 |
|---|---|
| BasicAuthentication | 客户端用户名密码认证 |
| SessionAuthentication | 采用sessionid的方式进行校验。 |
| TokenAuthentication | 采用token值进行校验 |
| RemoteUserAuthentication | 不经常用 |
1.源码解析
在DRF中使用APIView视图类,其中提供了一个认证配置.
打开APIView类,可以看到
class APIView(View):............# 这里可以看到,有一authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
查看api_settings,它是APISettings的实例化对象。在实力化的时候将DEFAULTS(这是一个字典)配置传递了进去。
api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)
DEFAULTS对象中有一个配置DEFAULT_AUTHENTICATION_CLASSES。点击DEFAULTS查看
'DEFAULT_AUTHENTICATION_CLASSES': ['rest_framework.authentication.SessionAuthentication','rest_framework.authentication.BasicAuthentication'],
DRF默认使用了session和Basic认证
2.基本认证(BasicAuthentication)
测试视图类如下:
from rest_framework.views import APIView,Response
from rest_framework.authentication import BasicAuthentication
from rest_framework.permissions import IsAuthenticatedclass test(APIView):# 选择使用基本认证类型,也就是用户名和密码验证authentication_classes = [BasicAuthentication]# 配置验证用户是否登录成功,如果陈成功,执行下边函数,不成功抛出异常permission_classes = [IsAuthenticated]def get(self,request):return Response({"message":"访问成功!"},status=200)
浏览器访问对应路由,就会自动弹出认证弹窗。让用户输入用户名和密码。如果输入错误,会让用户重新输入用户名和密码。
此验证方式尽量不要用于生产环境。安全系数较低
三.用户认证-token
简介:
JSON Web Token不再维护,所以在不使用JWT组件了。
官网建议使用simpleJWT组件
1.配置路由
1.1总路由urls.py配置
from django.urls import path,includeurlpatterns = [path('app1/', include('app1.urls')),
]
1.2 子路由urls.py配置
from django.urls import path
from .views import *urlpatterns = [path('', test.as_view()),
]
2.配置视图类
from rest_framework.views import APIView,Responseclass test(APIView):def get(self,request):return Response({"message":"访问成功!!!"},status=200)
3.访问测试
GET http://127.0.0.1:8000/app1/
返回结果:
{"message": "访问成功!!!"
}
前3步准备了一个最基础的DRF视图类和对应路由
4.安装JWT组件
pip install djangorestframework-simplejwt
5.配置使用JWT认证
在settings.py文件中,加入以下配置.
这里的配置是全局配置,对所有接口都生效。
DEFAULT_AUTHENTICATION_CLASSES:配置django的验证方式
DEFAULT_PERMISSION_CLASSES: 配置django的验证动作
REST_FRAMEWORK = {'DEFAULT_AUTHENTICATION_CLASSES': [# 使用rest_framework_simplejwt(token)方式做为验证方式。'rest_framework_simplejwt.authentication.JWTAuthentication',],'DEFAULT_PERMISSION_CLASSES': [# 用户访问所有接口的时候,都需要验证用户是否携带token,并且是否正确。正确才可以访问。'rest_framework.permissions.IsAuthenticated' ],
}
5.1 测试
这里配置了使用JWT验证后,在测试访问之前的接口
GET http://127.0.0.1:8000/app1/
返回结果:异常提示 “未提供身份验证凭据”
{"detail": "Authentication credentials were not provided."
}
6.配置token路由
因为token是全局性的,面向所有视图类进行验证,所以这里就配置在总路由的urls.py文件中了,
from django.urls import path,include# 导入simplejwt的视图类
from rest_framework_simplejwt.views import (TokenObtainPairView,TokenRefreshView,TokenVerifyView
)urlpatterns = [# 业务的接口路由path('app1/', include('app1.urls')),# 获取token的路由path('api/token/',TokenObtainPairView.as_view()),# 获取单独刷新access token的路由path('api/refresh/',TokenRefreshView.as_view()),# 验证token是否有效的路由path('api/token/verify/',TokenVerifyView.as_view())]
6.1 获取token
在 “一” 中已经讲述了User的使用,并且创建合法用户。在获取token时需要使用User模型类创建的用户作为验证。
POST http://127.0.0.1:8000/api/token/POST数据为:
{"username": "admin","password": "admin"
}
返回结果:
{"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTcwODkyODM4NCwiaWF0IjoxNzA4ODQxOTg0LCJqdGkiOiIyMTI5ZTdkZGIxZDY0YmVjYWY5NDU0YTY0MjkwNDM3NSIsInVzZXJfaWQiOjF9.sKp2TKv9CEH0xfKK5xLMiFq5eJvlZPFswIvxaK8eAxo","access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzA4ODQyMjg0LCJpYXQiOjE3MDg4NDE5ODQsImp0aSI6IjM0ZDc3NWVhMDI1NjQ2NzE4YWZmYzRjNjU4Y2QzZGU1IiwidXNlcl9pZCI6MX0.7xsMmaagvYxNVAbqapY_40stnXPtOHPIGoNLuJSpYIo"
}
6.2 token类型
上述结果中返回了两个token
refresh: 这个token是用来刷新access token的
access: 这个token是 用户访问业务接口需要携带的token.用户携带的这个token有效,才可以访问接口
6.3 携带token访问接口
GET http://127.0.0.1:8000/app1/# 在header头部中增加
key: Authorization
值: Bearer ”上边获取的access对应的token值“
返回结果:
{"message": "访问成功!!!"
}
7.配置JWT的过期时间
依然是在settings.py文件中进行配置。
为了测试这两个配置的时间都比较短
# 注意,在配置文件中导入datetime模块
import datetimeSIMPLE_JWT = {# token有效时长'ACCESS_TOKEN_LIFETIME': datetime.timedelta(seconds=20),# token刷新后的有效时间'REFRESH_TOKEN_LIFETIME': datetime.timedelta(seconds=3000),
}
一般生产环境token的配置是refresh的时间长一点,token的时间短一点,比如:
SIMPLE_JWT = {# token有效时长'ACCESS_TOKEN_LIFETIME': datetime.timedelta(minutes=30),# token刷新后的有效时间'REFRESH_TOKEN_LIFETIME': datetime.timedelta(days=1),
}
7.1 获取token
POST http://127.0.0.1:8000/api/token/POST数据为:
{"username": "admin","password": "admin"
}
7.2 携带token访问接口
GET http://127.0.0.1:8000/app1/
返回接口:
{"message": "访问成功!!!"
}
7.3 使用过期token访问
因为上边设置了token的有效期是20秒。等待20秒左右。依然使用刚才的access token访问接口,发现返回接口如下:
{"detail": "Given token not valid for any token type","code": "token_not_valid","messages": [{"token_class": "AccessToken","token_type": "access","message": "Token is invalid or expired"}]
}
token已经过期了
四、token的刷新与验证
在token的路由中,一共配置了3个关于token的路由,上边只是使用了一个:api/token/。这里讲解剩下的两个
1.刷新token
上述也讲过了refresh的值是专门用来刷新token的值的。
POST http://127.0.0.1:8000/api/refresh/# POST数据为:
{"refresh": "获取到的值"
}
返回结果:
{"access": "值"
}
使用这个接口可以不断刷新access 的值
2.refresh的过期时间
SIMPLE_JWT = {'ACCESS_TOKEN_LIFETIME': datetime.timedelta(seconds=20),# 将这里的过期时间调整为30s'REFRESH_TOKEN_LIFETIME': datetime.timedelta(seconds=30),
}
2.1 重新获取resfresh token
POST http://127.0.0.1:8000/api/token/POST数据为:
{"username": "admin","password": "admin"
}
2.2 携带refresh token刷新access
POST http://127.0.0.1:8000/api/refresh/# POST数据为:
{"refresh": "刚刚获取到过期时间为30s的refresh值"
}
等待30s后,返回结果如下:
{"detail": "Token is invalid or expired","code": "token_not_valid"
}
3.token的验证
POST http://127.0.0.1:8000/api/token/verify/# post数据为:
{"token": "access的值"
}
如果验证成功,并且没有过期,返回空字典{}。
如果过期返回如下:
{"detail": "Token is invalid or expired","code": "token_not_valid"
}