Hướng dẫn sử dụng lớp phân quyền, lớp giới hạn tần suất và phân tích mã nguồn xác thực

1. Sử dụng lớp phân quyền =======

  • Bước đầu tiên: Viết một lớp kế thừa BasePermission
  • Bước thứ hai: Ghi đè phương thức has_permission
  • Bước thứ ba: Trong phương thức kiểm tra xem người dùng có quyền hay không (request.user là người dùng hiện tại)
  • Bước thứ tư: Nếu có quyền, trả về True, nếu không có quyền, trả về False
  • Bước thứ năm: self.message là thông báo hiển thị cho phía client
  • Bước thứ sáu: Sử dụng cục bộ, sử dụng toàn hệ thống, vô hiệu hóa cục bộ

Sử dụng số làm loại để xác định, ví dụ 1 là quản trị viên cấp cao...

from rest_framework.permissions import BasePermission

class UserTypeValidation(BasePermission):
    def has_permission(self, request, view):
        if request.user.user_type == '1':
            return True
        else:
            return False

2. Sử dụng lớp giới hạn tần suất =======

  • Bước đầu tiên: Viết một lớp kế thừa SimpleRateThrottle
  • Bước thứ hai: Ghi đè get_cache_key, trả về chuỗi duy nhất sẽ được sử dụng để giới hạn tần suất
  • Bước thứ ba: Định nghĩa thuộc tính scope='tùy ý', phải khớp với cấu hình trong file thiết lập
  • Bước thứ tư: Trong file cấu hình viết: 'DEFAULT_THROTTLE_RATES': { 'scope tùy ý': '3/m' # 3/h 3/s 3/d }
  • Bước thứ năm: Cấu hình cục bộ, cấu hình toàn hệ thống, vô hiệu hóa cục bộ
class CustomRateLimit(SimpleRateThrottle):
    scope = 'custom_scope'

    def get_cache_key(self, request, view):
        return request.META.get('REMOTE_ADDR')


class UserAccessLimit(BaseThrottle):
    access_records = {}

    def __init__(self):
        self.access_history = None

    def allow_request(self, request, view):
        client_ip = request.META.get('REMOTE_ADDR')
        import time
        current_time = time.time()
        print(current_time)
        if client_ip not in self.access_records:
            self.access_records[client_ip] = [current_time, ]
            return True
        self.access_history = self.access_records.get(client_ip)
        print(self.access_history)
        while self.access_history and current_time - self.access_history[-1] > 60:
            self.access_history.pop()
        if len(self.access_history) < 3:
            self.access_history.insert(0, current_time)
            return True
        else:
            return False

3. Phân tích mã nguồn xác thực ========

# Viết lớp xác thực, ghi đè phương thức cụ thể, cấu hình trong lớp view để có xác thực
# Khi lớp xác thực được thêm vào, trong phương thức của lớp view, request.user sẽ là người dùng đăng nhập hiện tại
# Dự đoán lớp xác thực được thực thi trước phương thức của lớp view

# Phân tích mã nguồn:
    - Trước đây chúng ta đã đọc quy trình thực thi của APIView
    - Tạo request mới, thực hiện 3 xác thực chính, thực thi phương thức của lớp view, xử lý ngoại lệ toàn cục
    - Điểm vào: dispatch của APIView
    - Dòng 496 của dispatch của APIView: self.initial(request, *args, **kwargs)
    - initial của APIView
    - Dòng 413: Có ba câu lệnh tương ứng với xác thực, phân quyền, giới hạn tần suất
        self.perform_authentication(request)
        self.check_permissions(request)
        self.check_throttles(request)

    # Đọc mã nguồn lớp xác thực -> perform_authentication(request) của APIView, dòng 315
        def perform_authentication(self, request):
            request.user  # request mới

    # request là request mới -> Tìm thuộc tính user trong lớp Request, là phương thức được đóng gói thành thuộc tính dữ liệu
    # Đến lớp Request tìm: dòng 220
       def user(self):
            if not hasattr(self, '_user'): # Lấy _user bằng phản chiếu trong đối tượng Request
                with wrap_attributeerrors():
                    self._authenticate()  # Lần đầu tiên sẽ chạy đoạn mã này
            return self._user

    # self._authenticate() của Request -> dòng 373
        def _authenticate(self):
            for authenticator in self.authenticators: # Danh sách đối tượng lớp xác thực được cấu hình trong lớp view
                try:
                    # (user_token.user, token)
                    auth_result = authenticator.authenticate(self) # Gọi authenticate của đối tượng lớp xác thực
                except exceptions.APIException:
                    self._not_authenticated()
                    raise

                if auth_result is not None:
                    self._authenticator = authenticator # Bỏ qua
                    self.user, self.auth = auth_result # Giải nén gán giá trị
                    return
              # Có thể cấu hình nhiều lớp xác thực, nhưng nếu một lớp trả về hai giá trị thì các lớp sau sẽ không thực thi
            self._not_authenticated()
            
    # Tổng kết: Lớp xác thực cần ghi đè phương thức authenticate, xác thực thành công trả về hai giá trị hoặc None, xác thực thất bại ném ngoại lệ AuthenticationFailed (kế thừa từ APIException)

4. Phân tích mã nguồn phân quyền ========

- Đọc quy trình thực thi đơn giản nhất của phân quyền -> check_permissions(request) của APIView, dòng 325
    def check_permissions(self, request):
        for permission in self.get_permissions():
            # permission là đối tượng lớp phân quyền được cấu hình trong lớp view, gọi phương thức ràng buộc has_permission của nó
            # Gọi phương thức ràng buộc của đối tượng sẽ truyền chính nó vào (đối tượng lớp phân quyền, request, đối tượng lớp view)
            if not permission.has_permission(request, self):
                self.permission_denied(
                    request,
                    message=getattr(permission, 'message', None),
                    code=getattr(permission, 'code', None)
                )
                
   - self.get_permissions() của APIVIew, dòng 273
    return [permission() for permission in self.permission_classes]
   - self.permission_classes là danh sách lớp phân quyền được cấu hình trong lớp view
   - Vì vậy get_permissions trả về danh sách đối tượng lớp phân quyền được cấu hình trong lớp view [UserTypePermission(),]

    # Tổng kết: Mã nguồn lớp phân quyền
        - Tại sao phải viết một lớp, ghi đè phương thức has_permission, có ba tham số, tại sao phải trả về True hoặc False, message có thể dùng để làm gì

5. Kiểu dáng vịt (Duck Typing) ======

# Đi như vịt, kêu như vịt, thì đó là vịt

# Trong lập trình hướng đối tượng, lớp con không cần kế thừa rõ ràng một lớp nào đó, chỉ cần có phương thức và thuộc tính cụ thể thì được coi là thuộc loại đó

# Giả sử có lớp Duck với hai phương thức run, speak
# Giả sử có lớp thường Duck, nếu cũng là vịt, nó cần kế thừa lớp Duck, sau khi kế thừa không cần viết gì cả, đối tượng lớp thường Duck là kiểu vịt; nếu không kế thừa, đối tượng lớp thường Duck không phải là kiểu vịt
# Giả sử có lớp Donald Duck, nếu cũng là vịt, nó cần kế thừa lớp Duck, sau khi kế thừa không cần viết gì cả, đối tượng lớp Donald Duck là kiểu vịt; nếu không kế thừa, đối tượng lớp Donald Duck không phải là kiểu vịt

# Python không khuyến khích điều này, mà khuyến khích kiểu dáng vịt, nghĩa là
không cần kế thừa rõ ràng một lớp, chỉ cần lớp của tôi có phương thức run và speak, tôi là lớp vịt

# Có vấn đề nhỏ: nếu sử dụng cách viết kiểu dáng vịt của Python, nếu phương thức bị viết sai, nó sẽ không còn là kiểu đó nữa, sẽ có vấn đề
# Để giải quyết vấn đề này:
    - Cách một: module abc, sau khi trang trí bắt buộc phải ghi đè phương thức, nếu không ghi đè sẽ báo lỗi
    - Cách hai: được sử dụng trong mã nguồn DRF: viết phương thức trong lớp cha nhưng không có triển khai cụ thể, trực tiếp ném ngoại lệ

Thẻ: django-rest-framework authentication authorization rate-limiting duck-typing

Đăng vào ngày 4 tháng 6 lúc 16:35