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)
django使用User模型类来对应auth_user表,我们可以通过User类来对auth_user表进行操作
这里单独创建一个user应用
django-admin startapp user
在user应用下新建serializers.py文件
from rest_framework.serializers import ModelSerializer
# 导入django提供的 User 数据库models类
from django.contrib.auth.models import User
class auth_user_serializer(ModelSerializer):
class Meta:
model = User
# 根据需求选择一些必要的字段进行序列化。
fields = ["id","username","is_active","is_superuser","last_login"]
# 设置总路由:
urlpatterns = [
path('user/',include("user.urls"))
]
# 设置子路由:
from django.urls import path
from .views import *
urlpatterns = [
path('',userview.as_view())
]
编辑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_serializer
class 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.data
ser_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)
测试创建用户是否成功
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: 1
password: pbkdf2_sha256$260000$0AYS0xx7BRn7P9P46a3Q8Y$V9aVCZ6jOgVWK3e8ZF/PkPu3Wldoc6DzrXRB9c5tFfM=
last_login: NULL
is_superuser: 0
username: admin
first_name:
last_name:
email:
is_staff: 0
is_active: 1
date_joined: 2024-02-22 02:27:08.195161
1 row in set (0.00 sec)
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_serializer
class userview(ListAPIView):
queryset = User.objects.all()
serializer_class = auth_user_serializer
def post(self,request):
data = request.data
ser_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.data
field_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)
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_serializer
class userview(ListAPIView):
queryset = User.objects.all()
serializer_class = auth_user_serializer
def post(self,request):
data = request.data
ser_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.data
field_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.data
if "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 = 0
query_data.save()
return Response({
"message": "{}用户删除成功".format(data["username"])
},status=200)
else:
return Response({
"message": "字段错误,username为必填字段"
})
在命令创建管理员,注意一定要先迁移数据库,也就是说要在数据库中必须要生成auth_user表
python manage.py createsuperuser
在上边已经了解到了django的用户模型类(User).然后通过自己编写的功能实现用户的增删改查。那么用户访问的时候就可以通过User模型类创建好的用户访问django的其它接口。
那么用户携带的用户名和密码到底对不对呢?django是怎么判断的?,当用户携带的用户名和密码正确,后续的操作又该做什么?这就需要用到了 “DRF的认证”
DRF的认证一共有4种,如下:
认证方式 | 作用 |
---|---|
BasicAuthentication | 客户端用户名密码认证 |
SessionAuthentication | 采用sessionid的方式进行校验。 |
TokenAuthentication | 采用token值进行校验 |
RemoteUserAuthentication | 不经常用 |
在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认证
测试视图类如下:
from rest_framework.views import APIView,Response
from rest_framework.authentication import BasicAuthentication
from rest_framework.permissions import IsAuthenticated
class test(APIView):
# 选择使用基本认证类型,也就是用户名和密码验证
authentication_classes = [BasicAuthentication]
# 配置验证用户是否登录成功,如果陈成功,执行下边函数,不成功抛出异常
permission_classes = [IsAuthenticated]
def get(self,request):
return Response({
"message":"访问成功!"
},status=200)
浏览器访问对应路由,就会自动弹出认证弹窗。让用户输入用户名和密码。如果输入错误,会让用户重新输入用户名和密码。
此验证方式尽量不要用于生产环境。安全系数较低
简介:
JSON Web Token不再维护,所以在不使用JWT组件了。
官网建议使用simpleJWT组件
from django.urls import path,include
urlpatterns = [
path('app1/', include('app1.urls')),
]
from django.urls import path
from .views import *
urlpatterns = [
path('', test.as_view()),
]
from rest_framework.views import APIView,Response
class test(APIView):
def get(self,request):
return Response({
"message":"访问成功!!!"
},status=200)
GET http://127.0.0.1:8000/app1/
返回结果:
{
"message": "访问成功!!!"
}
前3步准备了一个最基础的DRF视图类和对应路由
pip install djangorestframework-simplejwt
在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'
],
}
这里配置了使用JWT验证后,在测试访问之前的接口
GET http://127.0.0.1:8000/app1/
返回结果:异常提示 “未提供身份验证凭据”
{
"detail": "Authentication credentials were not provided."
}
因为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())
]
在 “一” 中已经讲述了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"
}
上述结果中返回了两个token
refresh: 这个token是用来刷新access token的
access: 这个token是 用户访问业务接口需要携带的token.用户携带的这个token有效,才可以访问接口
GET http://127.0.0.1:8000/app1/
# 在header头部中增加
key: Authorization
值: Bearer ”上边获取的access对应的token值“
返回结果:
{
"message": "访问成功!!!"
}
依然是在settings.py文件中进行配置。
为了测试这两个配置的时间都比较短
# 注意,在配置文件中导入datetime模块
import datetime
SIMPLE_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),
}
POST http://127.0.0.1:8000/api/token/
POST数据为:
{
"username": "admin",
"password": "admin"
}
GET http://127.0.0.1:8000/app1/
返回接口:
{
"message": "访问成功!!!"
}
因为上边设置了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的路由中,一共配置了3个关于token的路由,上边只是使用了一个:api/token/。这里讲解剩下的两个
上述也讲过了refresh的值是专门用来刷新token的值的。
POST http://127.0.0.1:8000/api/refresh/
# POST数据为:
{
"refresh": "获取到的值"
}
返回结果:
{
"access": "值"
}
使用这个接口可以不断刷新access 的值
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': datetime.timedelta(seconds=20),
# 将这里的过期时间调整为30s
'REFRESH_TOKEN_LIFETIME': datetime.timedelta(seconds=30),
}
POST http://127.0.0.1:8000/api/token/
POST数据为:
{
"username": "admin",
"password": "admin"
}
POST http://127.0.0.1:8000/api/refresh/
# POST数据为:
{
"refresh": "刚刚获取到过期时间为30s的refresh值"
}
等待30s后,返回结果如下:
{
"detail": "Token is invalid or expired",
"code": "token_not_valid"
}
POST http://127.0.0.1:8000/api/token/verify/
# post数据为:
{
"token": "access的值"
}
如果验证成功,并且没有过期,返回空字典{}。
如果过期返回如下:
{
"detail": "Token is invalid or expired",
"code": "token_not_valid"
}