我在之前的文章给大家说过,身份认证的组件authentication_classes
并且做了一个简单的登录验证生成token,然后在authentication_classes中校验token,
返回用户对象和token值
我再把这段代码贴出来,给大家回顾一下,并做了一点小改动,添加了创建时间
class Login(APIView):
def post(self,request):
fields = {'username','password'}
data_set = set(request.data.keys())
info = {}
if fields.issubset(data_set):
for k in fields:
info[k] = request.data[k]
user_instance = User.objects.filter(**info).first()
response = {}
if user_instance:
token = UserToken.objects.update_or_create(user=user_instance,defaults={
'token':set_token(),
'created_at':datetime.now()
})
response['token'] = token[0].token
else:
response['error'] = '错了错了'
return JsonResponse(response)
后面的返回JSON数据,你用django的JsonResponse或者rest中的Response都行
token生成了,接下来要做的就是在获取接口数据的时候进行身份认证了,我们之前进行身份验证的代码也在之前的文章有写到过
这里我为了对比再贴出来一遍
class UserAuth(BaseAuthentication):
def authenticate(self,request):
token = request.query_params.get('token')
try:
token = UserToken.objects.get(token=token)
return token.user,token.token
except Exception:
raise APIException('认证不通过')
之前我们就是简单的做了一下验证访问用户是否携带token以及携带的token是否正确
但是当我们的用户表存在千万级别数量的用户时
我们对用户做认证时,每次都需要对数据库进行一次查询,这样的效率其实是非常低下的,而且我们也应该对token设置一个过期时间,为了安全,否则一旦token值被恶意获取,那我们的数据就永久的面临着危险
于是,今天我就简单的优化一下我们的用户身份认证类的写法
下面就给大家贴代码:
先是model
class User(models.Model):
user_types = ((1,'NOMAL'),
(2,'SVIP'),
(3,'VVIP'))
username = models.CharField('用户名',max_length=22)
password = models.CharField('密码',max_length=32)
user_type = models.SmallIntegerField('用户级别',choices=user_types,default=1)
class UserToken(models.Model):
user = models.OneToOneField(User,related_name='user_token')
token = models.CharField('用户token',max_length=200)
created_at = models.DateTimeField('创建时间',auto_now_add=True)
再来序列化类:
class BookSerialize(serializers.ModelSerializer):
class Meta:
model = Book
fields = ['title','price','publish','author','publish_company','author_list']
extra_kwargs = {
'publish':{'write_only':True},
'author': {'write_only': True},
}
publish_company = serializers.CharField(read_only=True,source='publish.company',max_length=32)
author_list = serializers.SerializerMethodField()
def get_author_list(self,book_obj):
author_list = list()
for author in book_obj.author.all():
author_list.append(author.name)
return author_list
然后认证类:
认证类较之前做了比较大的改动
我将token的位置放置在了请求头中,
较cookie来讲相对安全
较session来讲对服务器没有压力
from datetime import datetime, timedelta
from django.core.cache import cache
class UserAuth(BaseAuthentication):
def authenticate(self,request):
token = request.META.get('HTTP_AUTHENTICATION') #将token放入请求头中
print(token)
user = cache.get(token)
if user:
print('这是缓存')
return user,token
token_obj = UserToken.objects.filter(token=token).first()
if not token_obj:
raise APIException('认证失败')
created_time = token_obj.created_at
print('创建时间',created_time)
now_time = datetime.now()
print('现在的时间',now_time)
delta = now_time - created_time
print('过去多少天时间',delta)
if delta < timedelta(seconds=5):
remain_time = timedelta(weeks=2) - delta
print('剩余时间',remain_time)
cache.set(token_obj.token,token_obj.user,min(remain_time.total_seconds(),3600*24*7))
print('这是数据库')
return token_obj.user,token
else:
raise APIException('认证超时')
最后视图接口:
class BookView(viewsets.ModelViewSet):
authentication_classes = [UserAuth]
queryset = Book.objects.all()
serializer_class = BookSerialize
好了,我之前登录访问login接口生成了token,现在在请求头中携带token访问数据
我在认证类中,设置的过期时间为5秒,因为token的值我肯定是在5秒前就生成好了,
所以在请求数据的时候,会告诉我认证超时,我们看看结果:
现在,我更改一下token的过期时间,设置为两周
if delta < timedelta(weeks=2):
我们再看一下结果:
成功请求数据
name缓存呢?我想设置7天的缓存时间,但是我的缓存时间为什么不直接写
cache.set(token_obj.token,token_obj.user,3600*24*7)
而是要写成:
cache.set(token_obj.token,token_obj.user,min(remain_time.total_seconds(),3600*24*7))
试想一下,当一个用户第一天登录,我们设置了7天的缓存,这7天内,用户的每次数据获取的身份验证都是从缓存中取验证信息
但7天过了之后,用户没有再登录,而是在第十天登录,我们再次从数据库查询,又设置了一次7天的缓存,而token的过期时间为两周14天,这样就导致token过期了,但用户依旧可以通过认证获取数据
所以,我们需要进行这方面的处理
来看一下设置了缓存后的运行效果
当第一次请求的数据进行验证的时候,我们经过的是数据库
但第二次看是,我们经过的就是缓存了
如此一来,认证效率会大大提高
当然,我这里也只是简单的做了一下认证
有许多功能已经非常完善的认证工具库我们拿过来直接用就可以了,比如jsonwebtoken
写这些是为了大家理解token认证的这个过程
好了,关于认证优化的分享目前就到这里了