0
0
Lập trình
Sơn Tùng Lê
Sơn Tùng Lê103931498422911686980

Quản lý quyền truy cập đối tượng trong Elasticsearch với Django

Đăng vào 1 tuần trước

• 10 phút đọc

Giới thiệu

Elasticsearch đã trở thành một công cụ mạnh mẽ để cải thiện hiệu suất danh sách có thể lọc và tìm kiếm, giảm thời gian tải từ vài giây xuống chỉ còn khoảng nửa giây. Trong hệ thống dựa trên Django mà tôi đang làm việc, chúng tôi sử dụng django-guardian để thiết lập quyền truy cập theo từng đối tượng cho người dùng hoặc vai trò quản lý các mục khác nhau. Điều này có nghĩa là bạn không chỉ cần kiểm tra xem người dùng có quyền tổng quát để xem, thay đổi hoặc xóa một loại đối tượng hay không, mà còn cần kiểm tra xem họ có quyền truy cập vào các đối tượng cụ thể hay không.

Một thách thức lớn xuất hiện khi sử dụng Elasticsearch cho các cái nhìn có thẩm quyền dựa trên quyền của người dùng – làm thế nào chúng ta có thể kiểm tra quyền mà không làm chậm quá trình liệt kê?

Thách thức

Elasticsearch có thể làm tăng hiệu suất của các danh sách có thể tìm kiếm, nhưng việc kiểm tra quyền cho từng đối tượng có thể làm giảm đáng kể tốc độ. Dưới đây là một số phương án mà tôi đã xem xét:

  1. Kiểm tra UUID của tất cả các đối tượng mà người dùng có thể truy cập thông qua django-guardian, sau đó truyền các UUID đó vào truy vấn tìm kiếm Elasticsearch. Phương pháp này có thể hoạt động với ít hơn 100 mục, nhưng không mở rộng được.
  2. Lọc danh sách Elasticsearch trước, và sau đó kiểm tra UUID của từng mục với quyền của người dùng. Với hàng nghìn kết quả tìm kiếm, việc kiểm tra quyền trở nên quá chậm. Nếu chỉ kiểm tra quyền cho trang đầu tiên, dữ liệu phân trang sẽ không chính xác.
  3. Tạo một chỉ mục Elasticsearch cho quyền người dùng với tất cả UUID của các mục mà người dùng có thể truy cập, và lọc danh sách bằng cách tra cứu các UUID đó. Điều này làm cho việc cập nhật chỉ mục trở nên khó khăn, đặc biệt đối với quản trị viên và siêu người dùng.
  4. Lưu trữ danh sách ID người dùng và ID nhóm có thể xem từng mục, sau đó kiểm tra người dùng hiện tại với các ID trong danh sách. Đây là phương pháp tôi đã chọn, vì thường chỉ có một số ít người dùng và nhóm cần truy cập vào bất kỳ mục nào.

Giải pháp đã chọn

Chúng tôi sử dụng django-elasticsearch-dsl để lập chỉ mục các mô hình Django trong Elasticsearch. Tài liệu chỉ mục Elasticsearch cho một mục với ID người dùng và ID nhóm có thể trông như sau:

python Copy
# items/documents.py
from django.conf import settings
from django_elasticsearch_dsl.registries import registry
from django_elasticsearch_dsl import Document, fields
from guardian.shortcuts import get_users_with_perms, get_groups_with_perms
from .models import Item

@registry.register_document
class ItemDocument(Document):
    users_can_view = fields.KeywordField(multi=True)
    users_can_change = fields.KeywordField(multi=True)
    users_can_delete = fields.KeywordField(multi=True)
    groups_can_view = fields.KeywordField(multi=True)
    groups_can_change = fields.KeywordField(multi=True)
    groups_can_delete = fields.KeywordField(multi=True)

    class Index:
        name = "items"
        settings = {
            "number_of_shards": 1,
            "number_of_replicas": 0,
        }

    class Django:
        model = Item
        fields = [
            "uuid",
            "title",
            "intro",
            "created_at",
            "updated_at",
        ]
        queryset_pagination = 5000

    def prepare(self, instance):
        data = super().prepare(instance)
        data["users_can_view"] = []
        data["users_can_change"] = []
        data["users_can_delete"] = []
        for user, permissions in get_users_with_perms(
            item,
            attach_perms=True,
            with_superusers=True,
            with_group_users=False,
            only_with_perms_in=["view_item", "change_item", "delete_item"],
        ).items():
            if "view_item" in permissions:
                data["users_can_view"].append(user.pk)
            if "change_item" in permissions:
                data["users_can_change"].append(user.pk)
            if "delete_item" in permissions:
                data["users_can_delete"].append(user.pk)

        data["groups_can_view"] = []
        data["groups_can_change"] = []
        data["groups_can_delete"] = []
        for group, permissions in get_groups_with_perms(
            item, attach_perms=True
        ).items():
            for perm in permissions:
                if perm == "view_item":
                    data["groups_can_view"].append(group.pk)
                elif perm == "change_item":
                    data["groups_can_change"].append(group.pk)
                elif perm == "delete_item":
                    data["groups_can_delete"].append(group.pk)

        return data

Tiếp theo, chúng ta cần một lớp tiện ích để phân trang các chỉ mục Elasticsearch một cách tương thích với phân trang queryset mặc định của Django:

python Copy
# items/utils.py
class ElasticsearchPage:
    """
    Giao diện tương thích với Paginator của Django cho kết quả tìm kiếm Elasticsearch.
    """
    def __init__(self, results, total_count, page_number, items_per_page):
        self.object_list = results
        self.total_count = total_count
        self.number = page_number
        self.paginator = type(
            "Paginator",
            (),
            {
                "count": total_count,
                "num_pages": (total_count + items_per_page - 1) // items_per_page,
                "per_page": items_per_page,
            },
        )()

    def has_previous(self):
        return self.number > 1

    def has_next(self):
        return self.number < self.paginator.num_pages

    def has_other_pages(self):
        return self.paginator.num_pages > 1

    def previous_page_number(self):
        return self.number - 1 if self.has_previous() else None

    def next_page_number(self):
        return self.number + 1 if self.has_next() else None

Cuối cùng, chế độ xem danh sách kiểm tra ID người dùng và ID nhóm trong chỉ mục so với ID và thành viên nhóm của người dùng hiện tại:

python Copy
# items/views.py
from .utils import ElasticsearchPage
from .documents import ItemDocument
from django.contrib.auth.decorators import login_required

@login_required
def item_list(request):
    user_group_pks = list(request.user.groups.values_list("pk", flat=True))

    search_obj = ItemDocument.search()

    perm_filter = Q(
        "bool",
        should=[
            Q("term", users_can_view=request.user.pk),
            Q("terms", groups_can_view=user_group_pks),
        ],
        minimum_should_match=1,
    )

    search_obj = search_obj.query("bool", must=[perm_filter])

    # thêm tìm kiếm và lọc ở đây...

    items_per_page = int(request.GET.get("items_per_page", 24))
    page_number = int(request.GET.get("page", 1))
    offset = (page_number - 1) * items_per_page

    total_count = search_obj.count()

    search_obj = search_obj[offset : offset + items_per_page]
    search_results = search_obj.execute()

    page = ElasticsearchPage(
        results=search_results,
        total_count=total_count,
        page_number=page_number,
        items_per_page=items_per_page,
    )
    context = {
        "page": page,
        "items_per_page": items_per_page,
    }
    return render(request, "items/item_list.html", context)

Tại thời điểm này, điều quan trọng là phải cập nhật chỉ mục không chỉ khi chi tiết mục thay đổi, mà còn khi quyền thay đổi. Điều này có thể được thực hiện bằng cách gọi phương thức sau trong các chế độ xem hoặc phương thức lưu biểu mẫu liên quan:

python Copy
from django_elasticsearch_dsl.registries import registry

registry.update(item)

Thực hành tốt nhất

Để tối ưu hóa hiệu suất và đảm bảo quyền truy cập chính xác, hãy xem xét những điều sau:

  • Sử dụng chỉ mục Elasticsearch một cách hợp lý: Đảm bảo rằng quyền truy cập được lưu trữ trực tiếp trong chỉ mục để giảm thiểu thời gian kiểm tra quyền.
  • Cập nhật chỉ mục ngay khi có thay đổi: Đảm bảo rằng mỗi khi có thay đổi về quyền hoặc chi tiết mục, chỉ mục cũng được cập nhật kịp thời.
  • Kiểm tra hiệu suất: Thực hiện các bài kiểm tra hiệu suất để đảm bảo rằng tốc độ truy vấn không bị ảnh hưởng khi số lượng đối tượng tăng lên.

Những cạm bẫy thường gặp

  • Quên cập nhật chỉ mục: Nếu không cập nhật chỉ mục khi có thay đổi về quyền, điều này có thể dẫn đến việc người dùng không truy cập được các mục mà họ đáng lẽ phải có quyền.
  • Kiểm tra quyền không hiệu quả: Nếu phương pháp kiểm tra quyền không được tối ưu hóa, thời gian phản hồi của ứng dụng có thể bị ảnh hưởng nghiêm trọng.

Mẹo tối ưu hóa hiệu suất

  • Giảm số lượng truy vấn: Cố gắng giảm số lượng truy vấn cần thiết để kiểm tra quyền. Thay vì kiểm tra cho từng mục, hãy nhóm các quyền lại với nhau.
  • Sử dụng bộ nhớ đệm: Xem xét việc sử dụng bộ nhớ đệm cho các kết quả truy vấn để cải thiện hiệu suất.

Kết luận

Việc sử dụng django-guardian để xử lý các danh sách đã lọc từ Elasticsearch không hiệu quả. Thay vào đó, các quyền nên tồn tại trực tiếp trong chỉ mục Elasticsearch. Việc lưu trữ ID người dùng và ID nhóm trong các mục là cách thực tế hơn. Hãy đảm bảo rằng Elasticsearch được bảo mật đúng cách với SSL/TLS và xác thực (tên người dùng và mật khẩu) để bảo vệ dữ liệu khỏi việc can thiệp.

Câu hỏi thường gặp (FAQ)

1. Django Guardian là gì?

Django Guardian là một thư viện cho phép bạn quản lý quyền truy cập theo từng đối tượng trong Django, cho phép kiểm soát chi tiết hơn về quyền của người dùng.

2. Elasticsearch có an toàn không?

Có, Elasticsearch có thể được cấu hình với các biện pháp bảo mật như SSL/TLS và xác thực để bảo vệ dữ liệu của bạn.

3. Làm thế nào để cải thiện hiệu suất Elasticsearch?

Bạn có thể cải thiện hiệu suất bằng cách tối ưu hóa các truy vấn, sử dụng bộ nhớ đệm và giảm số lượng các truy vấn cần thiết.

4. Làm thế nào để kiểm tra quyền trong Elasticsearch?

Bạn có thể kiểm tra quyền bằng cách sử dụng các trường trong chỉ mục đã lưu trữ ID người dùng và ID nhóm có thể truy cập mỗi mục.

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào