1, Foreword
restframework has its own convenient authentication and permission system: Official document (token authentication)
The token of the official document is based on the authToken in the database_ Token table
Sometimes, when too much user information needs to be used in subsequent interfaces, frequent, high and distributed query databases will bring relatively large performance consumption. At this time, we need to do user authentication through redis and store some user information in it. Here's how to use DRF for user authentication based on redis.
2, Explain in detail
1. Preliminary preparation
1.1 install redis and start it
Self installation! This can't be installed, and you don't have to read the later tutorials! I can't understand it!
1.2 installing Django redis Library
pip install django-redis -i https://pypi.tuna.tsinghua.edu.cn/simple
2. Configure redis
2.1 configure redis connection
Enter the following code in settings.py (my redis does not have a password, so there is no password related configuration in the configuration code)
# redis cache configuration 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 initialize redis connection
Add the following code to the project initialization file (_init_. Py) to initialize redis
Add the following code
from django_redis import get_redis_connection redis_connect = get_redis_connection()
3. Write the token to redis
In the previous login interface, the token was written to the database. Now you need to rewrite it to write it to redis
3.1 original login code
@api_view(['POST']) def login(request): """ Login interface """ 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) # Save redis 259200 seconds = 72 hours return Response(data={'status_code': 200, 'msg': 'Login succeeded!', 'token': token.key}) return Response(data={'status_code': 403, 'msg': 'Wrong password!'})
3.2 rewritten login code
@api_view(['POST']) def login(request): """ Login interface """ user = authenticate(username=request.data['username'], password=request.data['password']) if user: token = binascii.hexlify(os.urandom(20)).decode() # How to generate a 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) # Save redis 259200 seconds = 72 hours return Response(data={'status_code': 200, 'msg': 'Login succeeded!', 'token': token}) return Response(data={'status_code': 403, 'msg': 'Wrong password!'})
3.3 user records stored in redis after login
4. Rewrite the authentication token method
4.1 source code analysis
We can search TokenAuthentication globally to find the Token authentication class in the [restframework] source code
In this class, we only need to focus on authenticate_credentials is OK.
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)
The source code first looks for the corresponding record in the database through the token (key in the source code) requested by the interface
If yes, the authentication succeeds, and two model objects, user and token, are returned
If there is no corresponding record, an [invalid token] exception will be thrown
try: token = model.objects.select_related('user').get(key=key) except model.DoesNotExist: raise exceptions.AuthenticationFailed(_('Invalid token.'))
If there is a corresponding record but the user is inactive (is_active=0), the [User inactive or deleted] exception is thrown
if not token.user.is_active: raise exceptions.AuthenticationFailed(_('User inactive or deleted.'))
Then restframework will encapsulate the exception in the dispatch method of the view layer and return the response result.
4.2 rewrite
After source code analysis, we need to rewrite two parts:
1. Verify whether the token (key in the source code) is valid. Previously, it was verified from the database. Now it needs to be verified through redis
2. Reseal the user model object, but there is a problem to note:
If you rewrite the user object of django to associate it with the attributes of other tables, you can't encapsulate it into the user object here, because redis can't store an object!, Of course, if you have to do this, you can store the foreign key id value in redis when logging in (writing a token), and then query the associated foreign key table through the foreign key id to obtain the attributes, and then encapsulate them in the user model object!
Rewritten code
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(_('invalid token.'))
4.3 adding authentication configuration
In the settings.py configuration file, add the following configuration
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( #If there is rest_ The framework configuration item can be added separately 'Demo.RedisAuthentication.RedisTokenAuthentication', # Project name. File where the recertification class is located. Class name ), }
4.4 effect display
Add an interface
path('test-token', views.test_token),
Interface method code
@api_view(['GET']) @permission_classes((permissions.IsAuthenticated,)) def test_token(request): """ test token """ print('The login user name is:', request.user) res_data = {'data': {'status_code': 200}} return Response(**res_data)
Output results
The login user name is: admin
3, Summary
Whether django or restframework, their source code and official documents are very clear to me (they are very clear in so many official documents I read). Here's a praise for the DRF team!!!
In the future, I will share some DRF related technologies and function points. Welcome to pay attention! support!