DRF认证、权限和限流

一、认证

这里只记录JWT的认证方式:

1. 首先针对用户需要创建JWT的token,方式如下:

# jwt_auth.py
import
jwt,datetime from django.conf import settings def create_token(payload,timeout=30): # JWT包含header、payload和hash签名,其中header可以自己写,也可以用默认的。有这三个元素就可以生成token。 header = { "alg": "HS256", "typ": "JWT" } payload['exp'] = datetime.datetime.utcnow() + datetime.timedelta(minutes=timeout) salt = settings.SECRET_KEY token = jwt.encode(payload=payload, key= salt, headers=header, algorithm='HS256').decode('utf-8') return token

2. 然后用户请求的时候,需要将这个token传递给对方:

# views.py
class
Login(APIView): # 登陆的试图不启用认证,这里改写认证类 authentication_classes = [] def post(self, request): # 获取用户的账号和密码 user = request.data.get('username') pwd = request.data.get('password') #校验用户的账号和密码,验证通过就构造一个payload,这里将IP地址也作为一个认证项 if user == 'zhangsan' and pwd == '123456': payload = { 'user_id': 1, 'username': 'zhangsan', 'user_ip': request.META.get('REMOTE_ADDR'), # 获取客户端ip地址 } # 获取token token = create_token(payload) # 给终端返还token return JsonResponse({'code':0, 'data':token}) else: return JsonResponse({'code':1, 'data':'login failed.'})

3. 用户获取到Token之后,后续访问需要携带Token,我们需要验证Token是否非法,所以需要写一个验证函数:

# jwt_auth.py
from
rest_framework.authentication import BaseAuthentication import jwt from django.conf import settings from rest_framework.exceptions import AuthenticationFailed class JwtQueryParamsBaseAuthentication(BaseAuthentication): def authenticate(self, request): salt = settings.SECRET_KEY token = request.META.get('HTTP_TOKEN') # 若抛出异常,后续代码将不执行 try: payload = jwt.decode(token,salt,True) if payload['user_ip'] != request.META.get('REMOTE_ADDR'): # 判断token里面的ip地址和客户端IP是否相同 raise AuthenticationFailed({'code':1, 'error': "IP address invalid."}) except jwt.exceptions.ExpiredSignatureError: raise AuthenticationFailed({'code':1, 'error':'The token has expired.'}) except jwt.DecodeError: raise AuthenticationFailed({'code':1, 'error':'Authentication failed.'}) except jwt.InvalidTokenError: raise AuthenticationFailed({'code':1, 'error':'token invalid.'}) return (payload['username'], token) # 抛出异常,后续不再执行 # return一个元组(1,2),认证通过,在试图中如果调用request.user就是元组的第一个值,request.auth就是元组的第二值

4. 用户发起访问,为了让用户使用认证,这里将所有视图缺省使用认证(在setting里面加),单个不使用认证的就单个视图中改写认证函数:

# sittings.py
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'netapps.extensions.auth.JwtQueryParamsBaseAuthentication', # 调用我们自己写的认证验证 ], }

5. 这样用户发起访问的时候,需要走认证流程。

# views.py
class
Logs(APIView): def get(self,request): print(request.user) # 'zhangsan' return HttpResponse("订单列表")

 

二、权限控制

1. 为了控制用户访问权限,要求只有张三能访问,其他人不能访问。这里写一个权限函数:

#permission.py
from rest_framework.permissions import BasePermission

class MyPermission(BasePermission):

    def has_permission(self,request,view):
        # 这个函数返回True或者False,True认证通过,False不通过
        if request.user != 'zhangsan':
            return False
        return True

2. 用户发起访问,为了让用户使用权限,这里将所有视图缺省使用权限控制(在setting里面加),单个不使用权限控制的就单个视图中改写权限函数:

# sittings.py
REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': [ 'netapps.extensions.permission.MyPermission' # 调用我们自己写的权限验证 ], }

3. 用户获取Token的时候,需要取消权限控制,所以在login这个函数里面需要改写权限函数:

# views.py
class
Login(APIView): authentication_classes = [] # 改写认证函数 permission_classes = [] # 改写权限函数 def post(self, request): # 获取用户的账号和密码 user = request.data.get('username') pwd = request.data.get('password') #校验用户的账号和密码,验证通过就构造一个payload,这里将IP地址也作为一个认证项 if user == 'zhangsan' and pwd == '123456': payload = { 'user_id': 1, 'username': 'zhangsan', 'user_ip': request.META.get('REMOTE_ADDR'), # 获取客户端ip地址 } # 获取token token = create_token(payload) # 给终端返还token return JsonResponse({'code':0, 'data':token}) else: return JsonResponse({'code':1, 'data':'login failed.'})

 

三、限流

1. 这里我们针对限流写两个限流函数,一个是限制用户获取Token的频率为每分钟10次,一个是限制用户访问业务的频率为每秒5次。

# throttle.py
from rest_framework.throttling import SimpleRateThrottle

class AllThrottle(SimpleRateThrottle):
    # 全局限流函数,基于用户限流,每秒5次
    scope = 'throttle'  # 该字段用来获取限流的频率,例如'5/s'表示每秒5次,在sitting里面设置

    def get_cache_key(self, request, view):
        return request.user  # 该字段用来定义基于什么做限流,这里基于用户做限流

class TokenThrottle(SimpleRateThrottle):
    scope = 'Token_throttle'  # 该字段是设置获取Token的频率

    def get_cache_key(self, request, view):
        return self.get_ident(request)  # 基于IP地址做限流

2. 在设置里面去配置限流:

# settings.py
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'netapps.extensions.permission.MyPermission'  # 调用我们自己写的权限验证
        
    ],
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'netapps.extensions.auth.JwtQueryParamsBaseAuthentication',  # 调用我们自己写的认证验证
    ],
    'DEFAULT_THROTTLE_CLASSES':[
        'netapps.extensions.throttle.AllThrottle'  # 设置全局使用的限流策略
    ],
    'DEFAULT_THROTTLE_RATES':{
        'throttle': '5/s',  # 跟throttle.py中的scope对应,一秒5次
        'Token_throttle': '10/m'  # 一分钟10次
    }
}

3. 由于全局使用的是AllThrottle这个函数,都是一秒5次,针对获取Token的频率我们需要改写限流函数:

# views.py
from netapps.extensions.throttle import TokenThrottle  # 引入Token限流函数
# Create your views here.

class Login(APIView):
    authentication_classes = []
    permission_classes = []
    throttle_classes = [TokenThrottle,]  # 调用
    def post(self, request):
        # 获取用户的账号和密码
        user = request.data.get('username')
        pwd = request.data.get('password')
        #校验用户的账号和密码,验证通过就构造一个payload,这里将IP地址也作为一个认证项
        if user == 'zhangsan' and pwd == '123456':
            payload = {
                'user_id': 1,
                'username': 'zhangsan',
                'user_ip': request.META.get('REMOTE_ADDR'),  # 获取客户端ip地址
            }
            # 获取token
            token = create_token(payload)
            # 给终端返还token
            return JsonResponse({'code':0, 'data':token})  
        else:
            return JsonResponse({'code':1, 'data':'login failed.'})  

这样就完成了,认证、权限控制和限流了。

 四、使用数据库用户进行认证授权

可以看到上面的认证都是张三这个人,可以同步数据库。在数据库中,有一张系统自带的名为auth_user的表,用于存储用户信息。该表通过密文方式存储密码,创建用户的两种方式:

1. 创建超级用户

[root@localhost myproject]# python manage.py createsuperuser
Username (leave blank to use 'root'): lisi
Email address: lisi@qq.com
Password: 
Password (again): 
Superuser created successfully.

2. 创建普通用户

python manage.py shell  # 进入python bash视图

from django.contrib.auth.models import User  # 导入用户表

User.objects.create_user('wangwu', 'wangwu@qq.com', 'password123')  # 顺序为用户名、邮箱、密码

接下来我们将通过数据库来完成用户的认证:

# views.py
from
django.http.response import JsonResponsefrom rest_framework.views import APIView from netapps.extensions.jwt_auth import create_token from netapps.extensions.throttle import TokenThrottle from django.contrib import auth from django.contrib.auth.models import User # Create your views here. class Login(APIView): authentication_classes = [] permission_classes = [] throttle_classes = [TokenThrottle,] def post(self, request): # 获取用户的账号和密码 user = request.data.get('username') pwd = request.data.get('password') #校验用户的账号和密码,验证通过就构造一个payload,这里将IP地址也作为一个认证项 user = auth.authenticate(username=user,password = pwd) # 将用户名和密码跟数据库比较,若账号密码正确则返回用户名,若不正确则返回None if user: payload = { 'user_id': 1, 'username': str(user), # 将用户传递给payload,由于用户是<class 'django.contrib.auth.models.User'>类型,需要强转str 'user_ip': request.META.get('REMOTE_ADDR'), # 获取客户端ip地址 } # 获取token token = create_token(payload) # 给终端返还token return JsonResponse({'code':0, 'Token':token}) else: return JsonResponse({'code':1, 'msg':'login failed.'})

然后在权限控制里面,也可以拉取数据库的用户。

# permission.py
from rest_framework.permissions import BasePermission
from django.contrib.auth.models import User

class MyPermission(BasePermission):

    def has_permission(self,request,view):
        # 这里只要用户在数据库能查询到,就返回True,也可以基于实际场景对不同用户做授权。
        if User.objects.filter(username = request.user['username']):
            return True
        return False

 

五、为什么要使用auth_user来做用户认证

auth_user是一个系统自带的表,该表实现了密码加密的功能,也就是存储在数据库的密码是一个hash值,这样更安全。

这个密码加密的实现方式是通过两个函数来实现的:

from django.contrib.auth.hashers import make_password, check_password
    def make_password(password, salt=None, hasher='default'):
        """
        将纯文本密码转换为用于数据库存储的哈希值。需要三个参数:
        password: 密码
        salt: 加盐(若不传值,将会使用随机值)
        hasher: hash算法
        若密码为空,将返回UNUSABLE_PASSWORD_PREFIX 和随机字符串的hash值,这将禁止用户登陆。
        """
        pass
    
    def check_password(password, encoded, setter=None, preferred='default'):
        """
        返回一个bool类型,判断密码是否正确。
        至少传2个参数,一个是密码,一个是hash值
        如果指定了setter,它会在需要重新生成密码时调用。
        """

 

posted on 2021-09-04 20:44  torotoise512  阅读(80)  评论(0编辑  收藏  举报