rest-framework(七)

三大认证组件

认证组件

self.perform_authentication(request)

'''
用户权限关系 RBAC(Role-BasedAccessControl)
表:User、Group、Permission、UG关系表、UP关系表、GP关系表
传统的RBAC有两种:权限三表 => 权限五表(没有UP关系表)
Django中Auth组件采用的是 权限六表(在传统RBAC基础上增加UP关系表)

	==用户管理表,一定要在第一次数据库迁移时完成==
'''
#在admin中注册自定义的User表,并继承UserAdmin,使在后台注册的用户密码是密文
#通过重写UserAdmin,自定义某些属性
from django.contrib import admin
from . import models
from django.contrib.auth.admin import UserAdmin as AuthUserAdmin

class UserAdmin(AuthUserAdmin):
    # 添加用户页面可控制字段
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('username', 'password1', 'password2', 'is_staff', 'mobile'),
        }),
    )
    # 用户列表展示页面显示字段
    list_display = ('username', 'email', 'mobile', 'is_staff')

# 注册自定义User表,用admin管理,配置UserAdmin,定制化管理页面
admin.site.register(models.User, UserAdmin)

RBAC三表

RBAC六表

jwt认证

'''
1. 登录时,账号密码换成token,=>签发token算法

2. 处理需要登录后的请求,拿到前台传来的登录证明信息token
	解析出登录用户user=>校验token算法(代码)
'''

jwt认证集群图

2

jwt优点

"""
jwt: json web token
优点:
1)数据库不需要存储token,所以服务器的 IO 操作会减少(没有IO写操作)
2)客户端存Token,服务器只存储签发与校验算法,执行效率高
3)签发与校验算法在多个服务器上可以直接统一,所以jwt认证规则下,服务器做集群非常便捷

突破点:
1)token必须要有多个部分组成,有能反解的部分,也要有不能反解的部分 - jwt采用的都是三段式
2)token中必须包含过期时间,保证token的安全性与时效性
"""

jwt原理

'''
jwt原理:
1)jwt由 头.载荷.签名 三部分组成
2)每一部分数据都是一个json字典,头和载荷采用 base64 可逆加密算法加密,签名采用 HS256 不可逆加密

内容:
1)头(基本信息):可逆不可逆采用的加密算法、公司名称、项目组信息、开发者信息...
{
	"company": "小女孩",
	...
}
2)载荷(核心信息):用户主键、用户账号、客户端设备信息、过期时间...
{
	'pk': 1,
	...
}
3)签名(安全信息):头的加密结果、载荷的加密结果、服务器的安全码(盐)...
{
	"header": "..."
	...
}
'''

签发算法

'''
签发算法:
1)头内容写死(可以为空{}):公司、项目组信息都是固定不变的
	=> 将数据字典转化成json字符串,再将json字符串加密成base64字符串
	
2)载荷的内容:用户账号、客户端设备信息是由客户端提供,用户主键是客户端提供账号密码校验User表通过后才能确定,过期时间根据当前时间与配置的过期时间相结合产生
	=> 将数据字典转化成json字符串,再将json字符串加密成base64字符串
	
3)签名的内容,先将头的加密结果,载荷的加密结果作为成员,再从服务器上拿安全码(不能让任何客户端知道),也可以额外包含载荷的部分(用户信息,设备信息)
	=> 将数据字典转化成json字符串,再将json字符串不可逆加密成HS256字符串
	
4)将三个字符串用 . 连接产生三段式token

校验算法:
1)从客户端提交的请求中拿到token,用 . 分割成三段(如果不是三段,非法)
2)头(第一段)可以不用解密
3)载荷(第二段)一定需要解密,先base64解密成json字符串,再转换成json字典数据
	i)用户主键与用户账号查询User表确定用户是否存在
	ii)设备信息用本次请求提交的设备信息比对,确定前后是否是同一设备,决定是否对用户做安全提示(eg:短信邮箱提示异地登录)(同样的安全保障还可以为IP、登录地点等)
	iii)过期时间与当前时间比对,该token是否在有效时间内
4)签名(第三段)采用加密碰撞校验
	i)将头、载荷加密字符串和数据库安全码形成json字典,转换成json字符串
	ii)采用不可逆HS256加密形成加密字符串
	iii)新的加密字符串与第三段签名碰撞比对,一致才能确保token是合法的
	
5)前方算法都通过后,载荷校验得到的User对象,就是该token代表的登录用户(Django项目一般都会把登录用户存放在request.user中)
'''

刷新算法

'''
刷新算法:
1)要在签发token的载荷中,额外添加两个时间信息:第一次签发token的时间,最多往后刷新的有效时间
2)每一请求携带token,不仅走校验算法验证token是否合法,还要额外请求刷新token的接口,完成token的刷新:校验规则与校验算法差不多,但是要将过期时间后移(没有超过有效时间,产生新token给客户端,如果超过了,刷新失败)
3)所以服务器不仅要配置过期时间,还需要配置最长刷新时间
'''

自定义jwt配置

'''
#1)在settings中配置
    import datetime
    JWT_AUTH = {
        # 过期时间
        'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=300),
        # 是否允许刷新
        'JWT_ALLOW_REFRESH': True,
        # 最大刷新的过期时间
        'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),
    }
'''

登录接口,提供username 和password,签发token : ObtainJSONWebToken

校验接口,提供token,返回token就代表校验通过 : VerifyJSONWebToken

刷新接口,通过token,刷新新token返回 : RefreshJSONWebToken

认证类的使用

自定义认证类

'''
1.  如果使用session认证,drf默认提供了SessionAuthentication
2.  如果使用drf-jwt认证框架,drf-jwt框架提供了JSONWebTokenAuthentication
3.  如果是自定义签发与校验token,才需要将校验token的算法封装到自定义的认证类中
'''
#1)首先建立一个authentications的py文件,在该文件中自定义MyAuthentication认证组件类,该类继承BaseAuthentication

from rest_framework.authentication import BaseAuthentication

class MyAuthentication(BaseAuthentication):
    """
    1) 从请求头中拿到前台提交的token(一般从HTTP_AUTHORIZATION中拿,也可以与前台约定)
              -- 如果设置了反爬等措施,校验一下反爬(头 token)
    2) 重写authenticate方法
    3)没有token,返回None,代表游客
    4)有token,进入校验
              -- 不通过:抛AuthenticationFailed异常,代表非法用户
              -- 通过:返回 (user, token),代表合法用户
    """
    def authenticate(self, request):
        print('来啦啦啦啦')
        pass
#2)在BaseAuthentication类中规定了客户端携带token时的结构,已经校验token是否合法和正确
#-----------------------------------------------------------------------------------
class UserListViewSet(mixins.ListModelMixin, GenericViewSet):
    # 认证组件
    # 配置自定义认证类(需求小)
    authentication_classes = [authentications.MyAuthentication]
    # 配置drf-jwt框架的认证类(需求大) => 配置还可以在全局(认证组件只能决定request.user,不是断定权限的地方,所以一般配置全局)
    from rest_framework_jwt.authentication import JSONWebTokenAuthentication
    # authentication_classes = [JSONWebTokenAuthentication]

使用drf封装的认证组件

# 配置drf-jwt框架的认证类(需求大) => 配置还可以在全局(认证组件只能决定request.user,不是断定权限的地方,所以一般配置全局)
    from rest_framework_jwt.authentication import JSONWebTokenAuthentication
    authentication_classes = [JSONWebTokenAuthentication]
#全局配置认证组件
#	在settings中配置
# drf框架自定义配置
REST_FRAMEWORK = {
    # 认证组件
    'DEFAULT_AUTHENTICATION_CLASSES': [
        # 'rest_framework.authentication.SessionAuthentication',
        # 'rest_framework.authentication.BasicAuthentication'
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication'
    ],

权限组件

self.check_permissions(request)

# 权限组件
    # 配置自定义权限类(有需求)
    permission_classes = [permissions.MyPermission]
    # 配置drf自带的权限类(有需求)
    from rest_framework.permissions import IsAuthenticated, IsAdminUser, AllowAny, IsAuthenticatedOrReadOnly
    permission_classes = [IsAuthenticated]
    permission_classes = [IsAdminUser]

原生drf权限组件

  1. AllowAny:游客和登录用户有全权限
  2. IsAuthenticated:只有登录用户有全权限
  3. IsAdminUser:只有后台用户(admin用户)有全权限
  4. IsAuthenticatedOrReadOnly:游客有读权限,登录用户有全权限

自定义权限组件

"""
如果有特殊需要,需要自定义权限类
    如:只有superuser有权限、只有vip用户有权限、只有某ip网段用户有权限、只有某个视图及其子类有权限
"""
class MyPermission(BasePermission):
    def has_permission(self, request, view):
        """
        1) 根据需求,request和view的辅助,制定权限规则判断条件
        2)如果条件通过,返回True
        3)如果条件不通过,返回False
        """
        print(request.user, request.auth)
        return False

#--------------------------------------------------------------------------------
class UserListViewSet(mixins.ListModelMixin, GenericViewSet):
	# 权限组件
    # 配置自定义权限类(有需求)
    permission_classes = [permissions.MyPermission]

jwt签发token

jwt签发token源码分析

多方式登录签发token

多方式登录,一定要自定义jwt-token的签发 --> 自己定义login视图

  1. token只能由 登录接口 签发
  2. 登录接口也是APIView的子类,使用一定会进行 认证、权限 组件的校验

结论:

​ 不管系统默认、或是全局settings配置的是何认证与权限组件,登录接口不用参与任何认证与权限的校验

​ 所以,登录接口一定要进行 认证与权限 的局部禁用

# 多方式登录
from rest_framework.views import APIView
class LoginAPIView(APIView):

    authentication_classes = []
    pagination_class = []

    def post(self, request, *args, **kwargs):
        serializer = serializers.LoginModelSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)  # 内部在全局钩子中完成token的签发
        return APIResponse(results={
            'username': serializer.content.get('user').username,
            'token': serializer.content.get('token')
        })
    
#---------在serializers中自定义登录时所要反序列化的字段,以及校验账号密码----
from rest_framework_jwt.serializers import jwt_payload_handler, jwt_encode_handler
import re
class LoginModelSerializer(serializers.ModelSerializer):
    # post请求,序列化默认当做create动作进行校验,需要校验数据库,create动作username会抛用户已存在异常
    # 抛用户已存在异常是多余的,所以自定义系统校验规则即可
    username = serializers.CharField(min_length=3, max_length=16)
    password = serializers.CharField(min_length=3, max_length=16)
    class Meta:
        model = models.User
        fields = ('username', 'password')

    # 用全局钩子,完成token的签发
    def validate(self, attrs):
        # 1)通过 username 和 password 完成多方式登录校验,得到user对象
        user = self._validate_user(attrs)
        # 2)user对象包装payload载荷
        payload = jwt_payload_handler(user)
        # 3)payload载荷签发token
        token = jwt_encode_handler(payload)
        # 4)将user与token存储到serializer对象中,方便在视图类中使用
        self.content = {
            'user': user,
            'token': token
        }
        return attrs

    def _validate_user(self, attrs):
        username = attrs.get('username')
        password = attrs.get('password')

        if re.match(r'.*@.*', username):  # 邮箱
            user = models.User.objects.filter(email=username).first()  # type: models.User
        elif re.match(r'^1[3-9][0-9]{9}$', username):  # 电话
            user = models.User.objects.filter(mobile=username).first()
        else:  # 用户名
            user = models.User.objects.filter(username=username).first()

        if not user or not user.check_password(password):
            raise serializers.ValidationError({'message': '用户信息异常'})

        return user

认证组件与权限组件绑定使用

  1. 每一个视图类都要进行认证校验,且认证规则一致,所以全局配置认证类即可
  2. 每一个视图类都要进行权限校验,默认配置的是不限制(AllowAny),但实际开发中,视图类的访问权限不尽相同,所以要在具体 的视图类,配置具体的权限规则
from rest_framework.viewsets import ViewSet
class UserViewSet(ViewSet):
    # 权限:只有VIP用户可以查看个人详细详细
    permission_classes = [permissions.VIPUserPermission]

    def retrieve(self, request, *args, **kwargs):
        return APIResponse(results={
            'username': request.user.username,
            'email': request.user.email,
            'mobile': request.user.mobile,
            'data_joined': request.user.date_joined,
        })
    
#----------自定义权限组件-------------------------------
# VIP用户权限
class VIPUserPermission(BasePermission):
    def has_permission(self, request, view):
        for group in request.user.groups.all():
            if group.name.lower() == 'vip':
                return True
        return False

频率组件

self.check_throttles(request)

原生drf封装的频率组件

  1. AnonRateThrottle : 登录客户无限制访问
  2. UserRateThrottle : 对所有用户设置频率限制

自定义频率类

from rest_framework.throttling import SimpleRateThrottle
"""
如果有特殊需要,需要自定义频率类
    如:对ip进行限次、对电话进行限制、对视图某些信息进行限次
"""
class MobileRateThrottle(SimpleRateThrottle):
    """
    1)设置scope字符串类属性,同时在settings中进行drf配置DEFAULT_THROTTLE_RATES
        eg: DEFAULT_THROTTLE_RATES = {'mobile': '1/min'}
    2)重写get_catch_key方法:
        返回与限制条件有关的字符串,表示限制
        返回None,表示不限制
    """
    scope = 'mobile'
    def get_cache_key(self, request, view):
        if not request.user.is_authenticated or not request.user.mobile:
            return None  # 匿名用户 或 没有电话号的用户 都不限制

        # 只要有电话号的用户踩进行限制
        return self.cache_format % {
            'scope': self.scope,
            'ident': request.user.mobile
        }
#------------views视图类中--------------------------------------------------
class UserListViewSet(mixins.ListModelMixin, GenericViewSet):
    # 频率组件
    # 配置drf自带的频率类(有需求)
    from rest_framework.throttling import AnonRateThrottle, UserRateThrottle
    # throttle_classes = [UserRateThrottle]
    # 配置自定义的频率类(需求大)
    throttle_classes = [throttles.MobileRateThrottle]

三大认证流程图

posted @ 2020-01-05 16:57  simple123  阅读(216)  评论(0编辑  收藏  举报