测试平台开发 - 用户认证与权限

一、 用户注册

注册流程:

1. 注册序列化器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# serializers.py

# 注册序列化器
class RegisterSerializer(serializers.ModelSerializer):
# admin_code 不在user 模型字段中,需要单独定义
admin_code = serializers.CharField(default='') # 字符串类型

class Meta:
model = User
fields = ['username', 'password', 'email', 'phone', 'realname', 'admin_code']

# 额外的验证 --- 覆盖父类的方法
def validate(self, attrs): # attrs 为入参的字典形式
# 检查 admin_code 是否等于 sqpt
if attrs['admin_code'] and attrs['admin_code'] != 'sqpt':
raise ValidationError('错误的admin_code')
return attrs # 检查通过,返回入参数据

# 定义注册用户方法
def register(self):
in_param = self.data # 从序列化器的数据取入参
# 传递 admin_code
if in_param['admin_code']:
# 创建 用户数据不需要 admin_code
in_param.pop('admin_code')
user = User.objects.create_superuser(**in_param) # 解包 传递入参用于创建用户
else:
in_param.pop('admin_code')
user = User.objects.create_user(**in_param)
return user

2. 注册视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# views.py

# 注册视图
@api_view(['POST'])
def register(request):
# 序列化器,获取注册信息
serializer = RegisterSerializer(data=request.data)
if serializer.is_valid(): # 自动根据模型定义判断数据入参是否合法
# 创建用户
user = serializer.register()
# 实现用户登录
auth.login(request, user) # 将当前用户信息记录到请求中,并在服务器的 session中记录信息
return Response(status=status.HTTP_201_CREATED,data={'msg': 'reister success', 'retcode': 201,'is_admin': user.is_superuser})
return Response(status=400,data={'msg': 'error', 'error': serializer.errors,'retcode': 400}) # 如果数据校验不合法就返回错误信息

3. 注册 URL

1
path('register/', views.register),  # 注册

二、 用户登录

登录流程:

1. 登录序列化器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# serializers.py

# 登录
class LoginSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['username', 'password',]

def validate(self, attrs):
# 判断用户名和密码是否传递
position_params = ['username', 'password'] # 必填参数
for param in position_params:
if param not in attrs: # 如果入参中不存在必填参数,抛出异常
raise ValidationError(f'缺少参数:{param}')
# 验证用户信息
user = auth.authenticate(**attrs)
if not user:
raise ValidationError('用户名或密码错误')
return user

2. 登录视图

1
2
3
4
5
6
7
8
9
10
11
12
# views.py

# 登录视图
@api_view(['POST'])
def login(request):
serializer = LoginSerializer(data=request.data)
user = serializer.validate(request.data)
if user:
# 验证成功
auth.login(request, user)
return Response(status=302, data={'msg': 'success', 'to': 'index.html'}) # 登录成功,跳转主页
return Response(status=status.400,data={'msg': 'error', 'error': serializer.errors, 'retcode': status.400})

3. 登录 url

1
path('login/', views.login),  # 登录

4. 渲染器-异常捕获重写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# exception.py

# REST 框架异常处理器
from rest_framework.exceptions import ValidationError
from rest_framework.views import exception_handler

# 处理异常返回 过滤器
def my_exception_handler(exc, context):
# 获取标准的错误响应
response = exception_handler(exc, context)
if response:
# 成功捕获到异常
# 判断 exc 类型,如果是 APIException 类型
if isinstance(exc, ValidationError):
error_msg = exc.detail[0]
else:
error_msg = exc

response.data = {'msg': 'error', 'retcode': response.status_code, 'error': str(error_msg)}

return response

三、 用户登出

流程图:

1. 登出视图

1
2
3
4
5
6
7
8
# views.py

# 登出请求
@api_view(['GET'])
def logout(request):
if request.user.is_authenticated:
auth.logout(request) # 清除登录信息 --- session 中
return Response(status=302, data={'msg': 'logout success', 'retcode': 302, 'to': 'login.html'})

2. 登出 url

1
path('logout/', views.logout),

3. 获取当前用户信息视图

前后端分离的系统前端部分需要同步后端的访问状态,因此构造一个请求用于检查是否登录

1
2
3
4
5
6
7
8
9
10
11
12
# views.py

# 获取当前用户信息
@api_view(['GET'])
def current_user(request):
# 判断当前用户是否处于登录状态 request.user
if request.user.is_authenticated: # 验证用户是否登录
# 返回当前用户登录信息
serializer = UserSerializer(request.user)
return Response(serializer.data)
# 如果为登录就返回一个重定向地址
return Response(status=status.HTTP_403_FORBIDDEN,data={'retcode':403,'msg':'未登录', 'to': 'login.html'})

四、 用户状态效验

1. Django 默认方式

django默认的用户信息存储方案为session机制,有内置的装饰器可以验证当前请求是否携带用户认证信息,

1
2
3
4
5
6
from django.contrib.auth.decorators import login_required
@login_required
def list_user(request):
...

# 如果用户未登录就会被重定向到一个默认的URL(/accounts/login/) 状态码为403

2. DRF 设置认证方案

可以使用 DEFAULT_AUTHENTICATION_CLASSES 设置全局的默认身份验证方案

1
2
3
4
5
6
7
8
9
# settings.py

REST_FRAMEWORK = {
# 全局认证模块
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
),
}

①. 基于 APIView 类视图的方式 :

1
2
3
4
5
6
7
8
9
10
11
12
13
# views.py

from rest_framework.authentication import SessionAuthentication,BasicAuthentication
from rest_framework.permissions import IsAuthenticated

class ProjectViewSet(viewsets.ModelViewSet):
queryset = Project.objects.all()
serializer_class = ProjectSerializer
# 权限相关
authentication_classes = (SessionAuthentication, BasicAuthentication)
# IsProjectAdmin 为自定义的权限
permission_classes = (IsAuthenticated, IsProjectAdmin)

②. 基于函数的视图

1
2
3
4
5
6
7
8
9
10
11
12
13
# views.py

from rest_framework.authentication import SessionAuthentication,BasicAuthentication
from rest_framework.permissions import IsAuthenticated

# 查询所有用户
@api_view(['GET'])
@authentication_classes((SessionAuthentication, BasicAuthentication)) # 认证类
@permission_classes((IsAuthenticated,))
def user_lest(request):
query_set = User.objects.all()
serializer = UserSerializer(query_set, many=True)
return Response(serializer.data)

3. DRF权限方案

①. 定义权限

sqpt/permissions.py

1
2
3
4
5
6
7
8
9
10
11
12
# permissions.py

from rest_framework import permissions

class IsProjectAdmin(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
# obj 是当前数据对象,此时等于当前项目数据对象
# 如果请求是 GET、HEAD、OPTIONS 等安全类型的请求
if request.method in permissions.SAFE_METHODS:
return True
# 返回当前用户是否是项目(obj)管理员,只有当前项目管理员才具备操作项目权限
return obj.admin == request.user

②. 加入权限

1
2
3
4
5
6
7
8
9
10
11
12
# views.py

from rest_framework.authentication import SessionAuthentication,BasicAuthentication
from rest_framework.permissions import IsAuthenticated

class ProjectViewSet(viewsets.ModelViewSet):
queryset = Project.objects.all()
serializer_class = ProjectSerializer
# 权限相关
authentication_classes = (SessionAuthentication, BasicAuthentication)
# IsProjectAdmin 为自定义的权限
permission_classes = (IsAuthenticated, IsProjectAdmin)