本博客作为个人笔记使用 主要是 记录 APIView 类的 用户认证 权限校验 以及 节流的使用与一些 原理说明 水平十分有限
创建 用户 使用的是django自带的User。
序列化器 如下
from django.contrib.auth.models import User
from rest_framework import serializers
class MyUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'password', 'email']
如果不会用序列化器可以参考https://blog.csdn.net/dandanfengyun/article/details/84779459
所使用的 包
import uuid
from django.conf import settings
from django.contrib.auth import authenticate
from django.contrib.auth.models import User
from django.core.cache import caches
from django.shortcuts import render
from rest_framework import status
from rest_framework.generics import ListCreateAPIView, ListAPIView
from rest_framework.response import Response
cache = caches['user'] # 缓存
使用一个视图类完成 User 创建 主要是 密码的设置
class RegisterAPI(ListCreateAPIView):
serializer_class = MyUserSerializer
def list(self, request, *args, **kwargs):
res = {
'code': 0,
'msg': '注册界面',
}
return Response(res)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
user = User.objects.get(pk=serializer.data.get('id'))
user.set_password(user.password)
user.save()
new_serializer = self.get_serializer(user)
return Response(new_serializer.data, status=status.HTTP_201_CREATED, headers=headers)
User 类自带的方法 self.set_password()可以将设置保护密码
在 url 路由中 加入该视图函数
url(r'^register$', RegisterAPI.as_view()),
前端代码未写 可以直接使用 Postman 工具发起请求
图为注册成功 返回的 数据
登录 逻辑 则是 当用户登录时 验证通过后 生成一个 随机 token 值 可用 UUID生成。然后将token 值 作为 key 登录用户 id 作为value 保存在 缓存中,缓存可以设置过期时间 。然后将token值 返回给客户端。 当某个视图需要用户为登录状态才能访问时 。客户发起的请求中应该 带有 token 值。可以设置为请求附带的参数 ?token=‘asghjfugyig’ 之类。 如果根据该token可以在缓存中查找到 用户id 且根据该id能找到对应user. 则为 登录状态。 可以访问 , 否则 不可以访问。
这里 需要用到 缓存 可以使用 redis 缓存 。
setting 中设置
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
},
"user": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/10",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
}
# 用户登录缓存时间
USER_ALIVE = 60*60*24*15 # 15天
登录的视图类
class LoginAPI(ListAPIView):
def list(self, request, *args, **kwargs):
username = request.data.get('username')
password = request.data.get('password')
user_login = authenticate(username=username, password=password) # User自带的验证 返回值 为user
if user_login:
token = request.query_params.get('token', None)
user_id = cache.get(token, None)
if not user_id:
user_cache = User.objects.filter(pk=user_id).first()
if not (user_cache is user_login):
# 当前 缓存中 没有保存 用户
token = uuid.uuid4().hex
# 设置 token 到 缓存中
cache.set(token, user_login.id, settings.USER_ALIVE)
res = {
'code': 0,
'msg': '登陆成功',
'username': user_login.username,
'token': token,
}
else:
res = {
'code': 1,
'msg': '登录失败, 请检查用户名密码',
}
return Response(res)
主要是 缓存的 token 设置 。
cache.set(token, user_login.id, settings.USER_ALIVE)
设置了 token 对应 的 用户id 以及 到期时间。
主要逻辑是 当 用户登录且 验证通过 时。 判断 是否有 token 如果 没有 那么就随机生成 token 设置 缓存。 如果 已经 附带 token 且 根据 token 查询到的 用户 为 当前登录用户。那么就不重新生成 token 而是 根据原token 重新设置 过期事件 。 所以 前端设计时 如果 已经登录过的用户 一定 附带上token 。 如果 未附带 会使用 UUID 重新生成 新的 token 导致 缓存 浪费。
下图是 重新登录且token未过期是返回结果
笔记 的 重点
APIVIew继承了 django 的View。在路由中使用时 都是 类名.as_view()
as_view()方法 实际是 一个 闭包 方法内 调用 dispatch 方法 做路由 分发
View 中 的 dispatch 将请求 方法 转 小写 然后返回 给 对应的 已实现方法进行处理
def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
APIView 继承 View 且 继承了 父类 的 as_view()方法 。而且 重写 了 dispatch 方法
def dispatch(self, request, *args, **kwargs):
"""
`.dispatch()` is pretty much the same as Django's regular dispatch,
but with extra hooks for startup, finalize, and exception handling.
"""
self.args = args
self.kwargs = kwargs
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate?
try:
self.initial(request, *args, **kwargs)
# Get the appropriate handler method
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
response = handler(request, *args, **kwargs)
except Exception as exc:
response = self.handle_exception(exc)
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
从 原 码 可以看到 使用了self.initialize_request() 方法对 request 做了封装。
且在之后调用了 self.initial(request, *args, **kwargs)
查看 self.initial(request, *args, **kwargs) 方法 发现最后执行了
self.perform_authentication(request)
self.check_permissions(request)
self.check_throttles(request)
这 三个 方法 分别就是 用户认证 权限校验 以及 节流
首先 是 用户认证
def perform_authentication(self, request):
request.user
认证方法 只是 一句话 request.user 但 该 request此时应为
self.initialize_request(request, *args, **kwargs) 方法的 返回对象。
def initialize_request(self, request, *args, **kwargs):
"""
Returns the initial request object.
"""
parser_context = self.get_parser_context(request)
return Request(
request,
parsers=self.get_parsers(),
authenticators=self.get_authenticators(),
negotiator=self.get_content_negotiator(),
parser_context=parser_context
)
该方法的返回值 是 类 Request 的实例化 . 该类 中 设置 user 实际为一个方法
@property
def user(self):
"""
Returns the user associated with the current request, as authenticated
by the authentication classes provided to the request.
"""
if not hasattr(self, '_user'):
with wrap_attributeerrors():
self._authenticate()
return self._user
大概就是如果 没有 _user属性 就会 执行self._authenticate()方法
def _authenticate(self):
"""
Attempt to authenticate the request using each authentication instance
in turn.
"""
for authenticator in self.authenticators:
try:
user_auth_tuple = authenticator.authenticate(self)
except exceptions.APIException:
self._not_authenticated()
raise
if user_auth_tuple is not None:
self._authenticator = authenticator
self.user, self.auth = user_auth_tuple
return
self._not_authenticated()
内部 做了一个 循环 for authenticator in self.authenticators: 而authenticators的值也是在Request实例化时设置 authenticators=self.get_authenticators(),
def get_authenticators(self):
"""
Instantiates and returns the list of authenticators that this view can use.
"""
return [auth() for auth in self.authentication_classes]
def get_authenticators(self) 方法 返回的 一个 列表 。是self.authentication_classes中的类
如果我们做自定义验证的话 就 在 继承APIView 时 赋值authentication_classes
authentication_classes应为可迭代对象 如列表 元组 等 且其中元素 应为 类 。
user_auth_tuple = authenticator.authenticate(self)
且类中 有 authenticate方法。
注意 _authenticate(self): 是Request 的 方法
因此
if user_auth_tuple is not None:
self._authenticator = authenticator
self.user, self.auth = user_auth_tuple
return
实际上 是 使 我们的 request 对象 即请求 中 多了 属性 user, auth
关于authentication_classes中 类的 定义 我们可以参考 default
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication'
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.AllowAny',
),
'DEFAULT_THROTTLE_CLASSES': (),
并不在同一文件
class APIView(View):
# The following policies may be set at either globally, or per-view.
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
parser_classes = api_settings.DEFAULT_PARSER_CLASSES
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
metadata_class = api_settings.DEFAULT_METADATA_CLASS
versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
经过 寻找 我们 找到 方法继承的父类
class BaseAuthentication(object):
"""
All authentication classes should extend BaseAuthentication.
"""
def authenticate(self, request):
"""
Authenticate the request and return a two-tuple of (user, token).
"""
raise NotImplementedError(".authenticate() must be overridden.")
只要 继承 该 类。 然后 重写 authenticate 方法 即可 。 返回值 为 user token 。正好验证 登录。 验证通过 返回 值 就是 user, token
终于可以真正开始做了
定义一个类继承
from django.contrib.auth.models import User
from django.core.cache import caches
from rest_framework.authentication import BaseAuthentication
cache = caches['user']
class LoginAuth(BaseAuthentication):
def authenticate(self, request):
token = request.query_params.get('token')
user_id = cache.get(token)
user = User.objects.filter(pk=user_id).first()
if user:
return user, token
else:
return None
如果获取到 user 则验证通过 视图类
class AuthUserAPI(ListAPIView):
serializer_class = MyUserSerializer
authentication_classes = (LoginAuth, )
def list(self, request, *args, **kwargs):
user = request.user
if user:
queryset = user
serializer = self.get_serializer(queryset)
return Response(serializer.data)
else:
res = {
'code': 1,
'msg': '未登录或已过期 请重新登录'
}
视图类 中 设置 了 authentication_classes = (LoginAuth, )
如此 便可以 使得 request 对象中 包含 属性 user.
权限认证
权限认证 比 用户认证 更简单一点 APIView类中方法
def get_permissions(self):
"""
Instantiates and returns the list of permissions that this view requires.
"""
return [permission() for permission in self.permission_classes]
def check_permissions(self, request):
"""
Check if the request should be permitted.
Raises an appropriate exception if the request is not permitted.
"""
for permission in self.get_permissions():
if not permission.has_permission(request, self):
self.permission_denied(
request, message=getattr(permission, 'message', None)
)
同样是 设置permission_classes属性 。且 其中 类 get_permissions方法 返回值 只需为 True 或 False True则有该权限。否则没有。就会 执行
self.permission_denied(request, message=getattr(permission, ‘message’, None))
同auth一样 我们找到 该 方法默认父类
class BasePermission(object):
"""
A base class from which all permission classes should inherit.
"""
def has_permission(self, request, view):
"""
Return `True` if permission is granted, `False` otherwise.
"""
return True
只要 继承 该类 然后 判断 权限 有无 返回 True 或 False即可
判断是否是超级用户 的类
from rest_framework.permissions import BasePermission
class SuperPermission(BasePermission):
def has_permission(self, request, view):
user = request.user
return user.is_superuser
视图类
class PermissionUserAPI(ListAPIView):
queryset = User.objects.all()
serializer_class = MyUserSerializer
authentication_classes = (LoginAuth,)
permission_classes = (SuperPermission,)
节流Throttle
节流 也 类似于 权限验证 只有f not throttle.allow_request(request, self): 为真 即throttle.allow_request(request, self)返回值 为 False 是 才会执行节流设置
throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
def get_throttles(self):
"""
Instantiates and returns the list of throttles that this view uses.
"""
return [throttle() for throttle in self.throttle_classes]
def check_throttles(self, request):
"""
Check if request should be throttled.
Raises an appropriate exception if the request is throttled.
"""
for throttle in self.get_throttles():
if not throttle.allow_request(request, self):
self.throttled(request, throttle.wait())
默认的 throttle_classes是一个 空元组 。也就是 不做节流 设置。
定义节流类时 根据经验 该类 应有 allow_request 方法 。查找网上资料 。restframe_work有一个简单的 节流设置 我们 可以继承该类
class SimpleRateThrottle(BaseThrottle):
原码过长 大家可以 自己找找看
从节流类在 执行时 直接调用 的 allow_request与类初始化函数开始
def __init__(self):
if not getattr(self, 'rate', None):
self.rate = self.get_rate()
self.num_requests, self.duration = self.parse_rate(self.rate)
如果 未设置 直接设置rate 调用 self.rate = self.get_rate() 通过 get_rate()方法 为 rate属性赋值 。。。 为求 简单 直接 设置 rate属性。 现在还不知道应该设置为什么。继续向下
初始化函数执行了self.parse_rate(self.rate) 设置了 self.num_requests, self.duration
def parse_rate(self, rate):
"""
Given the request rate string, return a two tuple of:
,
"""
if rate is None:
return (None, None)
num, period = rate.split('/')
num_requests = int(num)
duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
return (num_requests, duration)
可以查找观察 得出 (查看网络资料更好 观察的不一定是对的) rate 应为 一个频率 字符串 格式 如 ‘5/min’ 意思就是 每分钟访问 5次 。
self.num_requests = 5 可访问次数
self.duration = 60 时间 间隔
然后 看 类 直接 调用的 allow_request 方法。假设设置了 rate=‘5/min’
def allow_request(self, request, view):
if self.rate is None:
return True
self.key = self.get_cache_key(request, view)
if self.key is None:
return True
self.history = self.cache.get(self.key, [])
self.now = self.timer()
# Drop any requests from the history which have now passed the
# throttle duration
while self.history and self.history[-1] <= self.now - self.duration:
self.history.pop()
if len(self.history) >= self.num_requests:
return self.throttle_failure()
return self.throttle_success()
执行到self.key = self.get_cache_key(request, view)
def get_cache_key(self, request, view):
"""
Should return a unique cache-key which can be used for throttling.
Must be overridden.
May return `None` if the request should not be throttled.
"""
raise NotImplementedError('.get_cache_key() must be overridden')
根据描述 我们 应该 重写 该 方法 返回一个 唯一的 cache-key 。作为 self.key的值
如果 返回的 是 None 那么就不会节流 。
这是 class SimpleRateThrottle(BaseThrottle):的属性
class SimpleRateThrottle(BaseThrottle):
cache = default_cache
timer = time.time
cache_format = 'throttle_%(scope)s_%(ident)s'
scope = None
THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES
...
由此 可知
self.history = self.cache.get(self.key, []) 根据key从默认缓存中取出数据 没有 则返回一个列表
self.now = self.timer() 当前时间戳
while self.history and self.history[-1] <= self.now - self.duration:
self.history.pop()
if len(self.history) >= self.num_requests:
return self.throttle_failure()
return self.throttle_success()
self.history应该是一个列表。但我们并不知道 里面存的什么数据。第一次判断时由于是空列表 while 循环不会执行 return self.throttle_failure() 也不会执行 会执行
return self.throttle_success()
def throttle_success(self):
"""
Inserts the current request's timestamp along with the key
into the cache.
"""
self.history.insert(0, self.now)
self.cache.set(self.key, self.history, self.duration)
return True
可以看出 向self.history中插入了当前时间戳。
即当前访问时间时间戳插入到列表中且占据第一个元素位置。
然后设置保存到了 了 默认缓存 。 key值为 self.key value值 为 self.history 过期时间 是 self.duration。
如果 我们设置的 rate = ‘5/min’ 那么 self.duration = 60也就是说 60s 后 该缓存过期。执行self.history = self.cache.get(self.key, [])时不能取出数据,再次为 空列表。
然后 如果据 上次 访问时间 1 分钟内 再次访问。 那么 self.history 中有数据 就会进行while循环 和判断。
while self.history and self.history[-1] <= self.now - self.duration:
self.history.pop()
self.history列表 的 最后 一位 是 记录中 最先 访问 的时间 戳。
如果 当前时间 - 规定时间 间隔 >= 该时间戳 那就说明 该访问 记录 是 时间间隔之外的 也就是 1 分钟之前的 。。。不应在 我们统计范围之内。。 从列表中 删除 即可
然后 列表中数据 即为 1 分钟内 访问 的 时间戳。
if len(self.history) >= self.num_requests:
return self.throttle_failure()
如果 该列表 长度 大于等于 规定访问 的次数 。 那就说明 在这个时间间隔内 访问 量超过了规定。 会进行节流 return self.throttle_failure()
回到 APIView 函数。 会执行 self.throttled(request, throttle.wait()) 这个方法就是 节流时执行的。 有兴趣可以看看 。。 我是看不懂。
下面开始 设置 节流 类
from rest_framework.throttling import SimpleRateThrottle
class MyThrottle(SimpleRateThrottle):
scope = 'test'
THROTTLE_RATES = {
'test': '5/min'
}
def get_cache_key(self, request, view):
user = request.user
if user:
ident = user
else:
ident = self.get_ident(request)
return self.cache_format % {
'scope': self.scope,
'ident': ident,
}
看到并没有 设置 rate 但设置 了 scope 且 设置了
THROTTLE_RATES = {
'test': '5/min'
}
这是因为 设置 rate 时 就会执行 根据 scope 从THROTTLE_RATES字典中中查找到相应值 作为 rate。
def get_rate(self):
"""
Determine the string representation of the allowed request rate.
"""
if not getattr(self, 'scope', None):
msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %
self.__class__.__name__)
raise ImproperlyConfigured(msg)
try:
return self.THROTTLE_RATES[self.scope]
except KeyError:
msg = "No default throttle rate set for '%s' scope" % self.scope
raise ImproperlyConfigured(msg)
然后 get_cache_key 方法 中返回的 cache-key值 应该 是 与 用户 或 浏览器相关的 。。。
视图类
class ThrottleAPI(ListAPIView):
queryset = User.objects.all()
serializer_class = MyUserSerializer
throttle_classes = (MyThrottle,)
诸多 不足 担待