[DRF] how does django restframework use redis for token authentication

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!

Keywords: Database Django Redis

Added by smileyriley21 on Fri, 15 Oct 2021 20:51:19 +0300