一、认证
这里只记录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,它会在需要重新生成密码时调用。 """