8.用户认证与权限
大约 4 分钟学习笔记测试平台开发
一、 用户注册
注册流程:

1. 注册序列化器
# 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 user2. 注册视图
# 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
path('register/', views.register),  # 注册二、 用户登录
登录流程:

1. 登录序列化器
# 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 user2. 登录视图
# 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
path('login/', views.login),  # 登录4. 渲染器-异常捕获重写
# 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. 登出视图
# 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
path('logout/', views.logout),3. 获取当前用户信息视图
前后端分离的系统前端部分需要同步后端的访问状态,因此构造一个请求用于检查是否登录
# 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 机制,有内置的装饰器可以验证当前请求是否携带用户认证信息,
from django.contrib.auth.decorators import login_required
@login_required
def list_user(request):
...
# 如果用户未登录就会被重定向到一个默认的URL(/accounts/login/) 状态码为4032. DRF 设置认证方案
可以使用 DEFAULT_AUTHENTICATION_CLASSES 设置全局的默认身份验证方案
# settings.py
REST_FRAMEWORK = {
    # 全局认证模块
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ),
}①. 基于 APIView 类视图的方式 :
# 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)②. 基于函数的视图
# 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
# 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②. 加入权限
# 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)