Xác minh đăng nhập trên Django theo một cách tự làm khó bản thân
Django
2
Python
59
Male avatar

truongvan viết ngày 03/12/2018

Xác minh đăng nhập trên Django theo một cách tự làm khó bản thân

Django trang bị đến tận răng nếu bạn muốn làm một trang web. Trong đó cơ chế đăng nhập và xác minh đăng nhập là một phần rất mạnh được xây dựng thành một gói đầy đủ từ Models người dùng, Session, Phân quyền, Nhóm, đến form đăng nhập, form đăng ký.

Vậy nếu muốn có thêm 1 model người dùng tách biệt để đăng nhập thì sao (muốn có nhiều hơn một Sinh Linh Giá)? Và trong trường hợp xấu, dự án Django trở thành dự án Franjango.

Trước hết, nói về cách làm: Đăng nhập --> Chứng thực --> Gán user đăng nhập vào Session -> Thoát --> Xoá Session. Để làm được như vậy mình cần dựng một middleware. Cấu trúc middleware của Django giống hình bên dưới:
alt text

Mình vào trực tiếp phần tạo model luôn, Model đầu tiên là AccountAnonymousAccount. Mình tạo một subclass từ AbstractBaseUser.

# models.py
from uuid import uuid4 as uuid_uuid4
from django.db import models
from django.contrib.auth.models import AbstractBaseUser


class Account(AbstractBaseUser):
    uuid = models.UUIDField("UUID", default=uuid_uuid4, editable=False)
    email = models.EmailField("Email", unique=True)
    is_active = models.BooleanField(default=False)

    is_authenticated = True

    USERNAME_FIELD = 'email'

    def has_perm(self, perm, obj=None):
        "Does the user have a specific permission?"
        # Simplest possible answer: Yes, always
        return True

    def has_module_perms(self, app_label):
        "Does the user have permissions to view the app `app_label`?"
        # Simplest possible answer: Yes, always
        return True

    def __str__(self):
        return self.email

    class Meta:
        indexes = [
            models.Index(fields=['uuid'])


class AnonymousAccount:
    id = None
    pk = None
    uuid= None
    email = ''
    is_authenticated = False

    def __str__(self):
        return 'AnonymousUser'

Trong cùng thư mục với models mình tạo file auth.py. Đoạn code này mình chôm từ mã nguồn của Django và một chút điều chỉnh:

# auth.py
from django.utils.translation import LANGUAGE_SESSION_KEY
from django.utils.crypto import constant_time_compare
from django.dispatch import Signal
from django.middleware.csrf import rotate_token


SESSION_KEY = '_auth_account_id'
HASH_SESSION_KEY = '_auth_account_hash'

account_logged_in = Signal(providing_args=['request', 'account'])
account_login_failed = Signal(providing_args=['credentials', 'request'])
account_logged_out = Signal(providing_args=['request', 'account'])


def login(request, account):
    """
    Hàm này sẽ gán Account vào Session
    """
    if hasattr(account, 'get_session_auth_hash'):
        session_auth_hash = account.get_session_auth_hash()
    if SESSION_KEY in request.session:
        if request.session[SESSION_KEY] != account.uuid or (
                session_auth_hash and
                not constant_time_compare(request.session.get(HASH_SESSION_KEY, ''), session_auth_hash)):
            # To avoid reusing another user's session, create a new, empty
            # session if the existing session corresponds to a different
            # authenticated user.
            request.session.flush()
    else:
        request.session.cycle_key()

    request.session[SESSION_KEY] = account.uuid.hex
    request.session[HASH_SESSION_KEY] = session_auth_hash
    if hasattr(request, 'account'):
        request.account = account
    rotate_token(request)
    account_logged_in.send(sender=account.__class__,
                        request=request, account=account)


def logout(request):
    """
    Remove the authenticated user's ID from the request and flush their session
    data
    """
    account = getattr(request, 'account', None)
    if not getattr(account, 'is_authenticated', True):
        account = None
    account_logged_out.send(sender=account.__class__,
                         request=request, account=account)

    # remember language choice saved to session
    language = request.session.get(LANGUAGE_SESSION_KEY)

    request.session.flush()

    if language is not None:
        request.session[LANGUAGE_SESSION_KEY] = language

    if hasattr(request, 'account'):
        from .models import AnonymousAccount
        request.user = AnonymousAccount()

Và phần cuối cùng nhưng không kém phần quan trọng là middleware.py, cũng lấy trong mã nguồn của django:

from django.utils.crypto import constant_time_compare
from django.utils.deprecation import MiddlewareMixin
from django.utils.functional import SimpleLazyObject
from uuid import UUID

from .models import Account


SESSION_KEY = '_auth_account_id'
HASH_SESSION_KEY = '_auth_account_hash'


class AccountAuthentication(MiddlewareMixin):
    """
    Find account via account id in session, check hash then return account
    """
    def process_request(self, request):
        assert hasattr(request, 'session'), (
            "The Account authentication middleware requires session middleware "
            "to be installed. Edit your MIDDLEWARE setting to insert "
            "'django.contrib.sessions.middleware.SessionMiddleware' before "
            "'django.contrib.auth.middleware.AuthenticationMiddleware'."
        )
        request.account = SimpleLazyObject(lambda: get_account_cache(request))


def get_account_session_key(request):
    uuid = request.session[SESSION_KEY]
    account = Account.objects.get(uuid=uuid)
    return account


def get_account_cache(request):
    print("middleware-------------------------------------------------------------->")
    print(hasattr(request, '_cached_account'))

    if not hasattr(request, '_cached_account'):
        request._cached_account = get_account(request)
    return request._cached_account


def get_account(request):
    from .models import AnonymousAccount
    account = None
    try:
        user_uuid = UUID(request.session[SESSION_KEY])
    except KeyError:
        pass
    else:
        account = Account.objects.get(uuid=user_uuid)
        # Verify the session
        if hasattr(account, 'get_session_auth_hash'):
            session_hash = request.session.get(HASH_SESSION_KEY)
            session_hash_verified = session_hash and constant_time_compare(
                session_hash,
                account.get_session_auth_hash()
            )
            if not session_hash_verified:
                request.session.flush()
                user = None

    return account or AnonymousAccount()

Và từ đây chỉ cần add thêm form và view là có thể đăng nhập bên cạnh cơ chế đăng nhập của Django.

truongvan 03-12-2018

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

Male avatar

truongvan

1 bài viết.
0 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Bài viết liên quan
White
7 3
Description Mình là một thằng thích đọc sách. Nhưng lúc nào cũng bận (lười) nên cũng mấy tháng rồi chưa hoàn thành được quyển sách nào. Mình đa số...
Rice viết 9 tháng trước
7 3
White
4 2
Requirement Hôm bữa đọc được bài https://kipalog.com/posts/Hocvacaithienkienthuctucacduancanhanpetproject thấy hay quá. Tính cũng định viết con ap...
Rice viết 9 tháng trước
4 2
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


Male avatar
{{userFollowed ? 'Following' : 'Follow'}}
1 bài viết.
0 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!