一、前言
restframework有自己很方便的一套认证、权限体系:官方文档(tokenauthentication)
官方文档的token 是基于数据库中的authtoken_token
表来做的
有时候在后续接口中需要使用的用户信息过多时,频繁、高并发下的查询数据库会带来比较大的性能消耗。这个时候我们就需要通过redis来做用户认证,并存储一些用户信息在其中。下面就为你讲解如何基于redis来使用DRF做用户认证。
二、详解
1. 前期准备
1.1 安装redis并启动
自行安装!这个都装不好后面的教程也不用看了!看了也理解不了!
1.2 安装django-redis库
pip install django-redis -i https://pypi.tuna.tsinghua.edu.cn/simple
2. 配置redis
2.1 配置redis连接
在settings.py
输入下面的代码 (我的redis没有设置密码,所以配置代码中无密码相关配置)
# redis缓存配置 CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379/", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", "COMPRESSOR": "django_redis.compressors.zlib.ZlibCompressor", "IGNORE_EXCEPTIONS": True, } } }
2.2 初始化redis连接
在项目初始化文件中(__init__.py
)加入下面代码进行redis的初始化
加入下列代码
from django_redis import get_redis_connection redis_connect = get_redis_connection()
3. 将token写入redis
在之前的登录接口是将token写入数据库的,现在需要重写它让其写入redis
3.1 原来的登录代码
@api_view(['POST']) def login(request): """ 登录接口 """ user = authenticate(username=request.data['username'], password=request.data['password']) if user: Token.objects.filter(user_id=user.id).delete() token = Token.objects.create(user=user) _dict = {'id': user.id, 'username': user.username, 'first_name': user.first_name, 'last_name': user.last_name, 'email': user.email} redis_connect.set(token.key, json.dumps(_dict), 259200) # 存redis 259200秒=72个小时 return Response(data={'status_code': 200, 'msg': '登录成功!', 'token': token.key}) return Response(data={'status_code': 403, 'msg': '密码错误!'})
3.2 重写后的登录代码
@api_view(['POST']) def login(request): """ 登录接口 """ user = authenticate(username=request.data['username'], password=request.data['password']) if user: token = binascii.hexlify(os.urandom(20)).decode() # 生成token 的方式 _dict = {'id': user.id, 'username': user.username, 'first_name': user.first_name, 'last_name': user.last_name, 'email': user.email} redis_connect.set(token, json.dumps(_dict), 259200) # 存redis 259200秒=72个小时 return Response(data={'status_code': 200, 'msg': '登录成功!', 'token': token}) return Response(data={'status_code': 403, 'msg': '密码错误!'})
3.3 登录后redis存储的用户记录
4. 重写认证token方法
4.1 源码分析
我们可以全局搜索TokenAuthentication
找到【restframework】源码中的Token认证类
这个类中我们只需要关注authenticate_credentials这个方法就可以了。
def authenticate_credentials(self, key): model = self.get_model() try: token = model.objects.select_related('user').get(key=key) except model.DoesNotExist: raise exceptions.AuthenticationFailed(_('Invalid token.')) if not token.user.is_active: raise exceptions.AuthenticationFailed(_('User inactive or deleted.')) return (token.user, token)
源码首先通过接口请求的token (源码中的key
) 去数据库中寻找是否有该对应的记录
如果有则认证成功返回user
和token
这两个模型对象
如果没有对应的记录,则抛出【invalid token】异常
try: token = model.objects.select_related('user').get(key=key) except model.DoesNotExist: raise exceptions.AuthenticationFailed(_('Invalid token.'))
如果有对应的记录,但用户是未激活的 (is_active=0) 则抛出【User inactive or deleted】异常
if not token.user.is_active: raise exceptions.AuthenticationFailed(_('User inactive or deleted.'))
然后restframework会在视图层的dispatch
方法中进行异常的封装并返回响应结果。
4.2 进行重写
经过源码分析,我们需要重写的有两部分:
1.验证token (源码中的key) 是否有效,之前是从数据库进行验证的现在需要通过redis去验证
2.重新封装user模型对象,但有个问题需要注意的是:
如果你重写了django的user对象,让它关联了其他表的属性,那么这里则不能将其封装进user这个对象的,因为redis不能存储一个对象!,当然如果非要这么做可以将外键id值在登录 (写入token) 的时候存入redis,然后在这里通过该外键id去查询关联的外键表获取属性,再封装到user模型对象中!
重写后的代码
class RedisTokenAuthentication(TokenAuthentication): def authenticate_credentials(self, key): user_data = redis_connect.get(key) if user_data: user_dict = json.loads(user_data) user_obj = User() for key_name in user_dict.keys(): setattr(user_obj, key_name, user_dict[key_name]) return user_obj, key raise exceptions.AuthenticationFailed(_('无效的token.'))
4.3 加入认证配置
在settings.py
配置文件中,加入如下配置
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( #如果有REST_FRAMEWORK配置项了单独加入该项即可 'Demo.RedisAuthentication.RedisTokenAuthentication', # 项目名称.重新认证类所在的文件.类名 ), }
4.4 效果展示
增加一个接口
path('test-token', views.test_token),
接口方法代码
@api_view(['GET']) @permission_classes((permissions.IsAuthenticated,)) def test_token(request): """ 测试token """ print('登录的用户名是:', request.user) res_data = {'data': {'status_code': 200}} return Response(**res_data)
输出结果
登录的用户名是: admin
三、总结
无论是django还是restframework,他们的源码和官方文档在我看来是非常清晰的 (在我看的那么多官方文档中算很清晰的了) 这里给DRF的团队点个赞!!!