drf认证

  • 参考网址

https://www.cnblogs.com/yangyi215/p/15041975.html

  • 作用: 校验用户是否登录

  • 书写步骤


- 写一个类,继承BaseAuthentication,重写类中的重写authenticate方法,认证的逻辑写在其中

    - 如果认证通过,返回两个值,一个值最终给了Requet.user,另外一个值给了Requet.auth
    - 如果认证失败,抛出异常,APIException或者AuthenticationFailed

ps:从源码中看出,如果有多个认证,要将返回有两个值的放到最后(认证流程见参考网址)

  • 认证demo
### models.py

# 用户
class User(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=32)

# token
class UserToken(models.Model):
    token = models.CharField(max_length=64)
    user = models.OneToOneField(to='User')

......
## your_app.auth.py

# 用户登录之前,校验token是否正确(即请求到LoginView之前,先经过这个认证类校验,通过了请求才到LoginView)
class MyAuthentication(BaseAuthentication):
    # 须重写
    def authenticate(self, request):
        """
        如果认证通过,返回两个值 request.user和request.auth;
        如果认证失败,抛出AuthenticationFailed异常
        """
        # token = request.query_params.get('token')
        token = request.data.get('token')
        if token:
            user_token = models.UserToken.objects.filter(token=token).first()
            if user_token:
                return user_token.user, token # 正确就返回user和token
            else:
                raise AuthenticationFailed('认证失败')
        else:
            raise AuthenticationFailed('请求地址中需要携带token')

## views
# 如果请求能来到这边,说明自定义 MyAuthentication 校验无误
class LoginView(APIView):

    authentication_classes = [MyAuthentication,] # 局部配置
    # uthentication_classes = []

    def post(self, request):
        username = request.data.get('username')
        password = request.data.get('password')
        user_obj = User.objects.filter(username=username, password=password).first()
        if user_obj:
            token_num = str(uuid.uuid4())
            # 登录成功以后,更新token
            UserToken.objects.update_or_create(defaults={'token': token_num}, user=user_obj)
            return Response({'status': 100, 'msg': '登陆成功', 'token': token_num})
        else:
            return Response({'status': 101, 'msg': '用户名或密码错误'})
  • 全局配置如下
......
# ---------DRF配置--------------------#
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": ["apps.tests.auth.MyAuthentication"]
}
......
class LoginView(APIView):

    # authentication_classes = [MyAuthentication,] # 局部配置
    authentication_classes = [] # 或者 authentication_classes 不写

权限

  • 流程
- APIView ---> dispatch方法 ---> self.initial(request, *args, **kwargs) ---> self.check_permissions(request)

- 源码解析:重写 has_permission()即可,返回布尔
    
    ......
    def check_permissions(self, request):
    # 遍历权限对象列表得到一个个权限对象(权限器),进行权限认证
    for permission in self.get_permissions():
        # 权限类一定有一个has_permission权限方法,用来做权限认证的
        # 参数:权限对象self、请求对象request、视图类对象
        # 返回值:有权限返回True,无权限返回False
        if not permission.has_permission(request, self):
            self.permission_denied(
                request, message=getattr(permission, 'message', None)
            )

  • demo演示
from django.db import models

# 用户
class User(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=32)
    # 新增用户类型
    user_type = models.IntegerField(default=3, choices=(
        (1, '超级用户'),
        (2, '普通用户'),
        (3, '游客'),
    ))

# token
class UserToken(models.Model):
    token = models.CharField(max_length=64)
    user = models.OneToOneField(to='User',on_delete=models.CASCADE)

# 认证
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed

from apps.tests import models


class MyAuthentication(BaseAuthentication):

    def authenticate(self, request):
        # 不同的请求方法,响应相应的token
        if request.method == 'POST':
            token = request.data.get('token')
        else:
            token = request.query_params.get('token')
        """
        如果认证通过,返回两个值 request.user和request.auth;
        如果认证失败,抛出AuthenticationFailed异常
        """
        if token:
            user_token = models.UserToken.objects.filter(token=token).first()
            if user_token:
                return user_token.user, token
                print(user_token.user,token)
            else:
                raise AuthenticationFailed('token认证失败')
        else:
            raise AuthenticationFailed('请求地址中需要携带token')

# 权限
from rest_framework.permissions import BasePermission

class SuperUserPermission(BasePermission):

    def has_permission(self,request,view):
        # 意味view若引用这个权限类,则必须是超级用户才能访问这个view
        if request.user.user_type == 1:
            return True
        else:
            return False

# 视图
......
# 用户登录
class LoginView(APIView):

    # 如果是全局配置,最好就是不写
    authentication_classes = []

    def post(self, request):
        username = request.data.get('username')
        password = request.data.get('password')
        user_obj = User.objects.filter(username=username, password=password).first()
        if user_obj:
            token_num = str(uuid.uuid4())
            UserToken.objects.update_or_create(defaults={'token': token_num}, user=user_obj)
            return Response({'status': 100, 'msg': '登陆成功', 'token': token_num})
        else:
            return Response({'status': 101, 'msg': '用户名或密码错误'})

# 用户登录以后,校验权限
class AfterLoginView(APIView):
    # 不能写成空list,空list会导致生成匿名用户,而匿名用户是没有 user_type属性,会报错
    # authentication_classes = [] 
    permission_classes = [SuperUserPermission]

    def get(self, request):
        return Response({'msg':'欢迎超级用户...'})

- 测试接口: 如果权限不够,会提示权限不足,如何自定义权限不足的消息呢,看了源码,可以这么干

# 用户登录以后,校验权限
class AfterLoginView(APIView):

  ......

    def get(self, request):
        ......

    def permission_denied(self, request, message=None, code=None):
        """
        If request is not permitted, determine what kind of exception to raise.
        """
        message = '充值可以提升权限噢'
        super().permission_denied(request, message=message, code=None)
        ## 或者这么写,显然很麻烦
        # message = '充值可以提升权限噢'
        # if request.authenticators and not request.successful_authenticator:
        #     raise exceptions.NotAuthenticated()
        # raise exceptions.PermissionDenied(detail=message, code=code)

- 但是这么写,每一个视图难道都实现一次 permission_denied ? 显然不可接受,可以用继承类的方式来实现
  ??? 能不能写一个权限类,一次就搞定呢?

- 全局配置,一样的套路,注释掉原有的 permission_classes,settings配置即可
# ---------DRF配置--------------------#
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": ["apps.tests.auth.MyAuthentication"],
    # 权限配置
    "DEFAULT_PERMISSION_CLASSES": ["apps.tests.permissions.SuperUserPermission"]
}