Vai trò và quyền của Laravel Livewire

Theo mặc định, Tailwind được sử dụng để tạo giao diện quản lý quyền và vai trò nhưng bạn cũng có thể chọn chủ đề Bootstrap

<?php
return [
	/*
	 * Supported Theme: 'tailwind, bootstrap',
	 */
	'theme'  =>  'tailwind',
];

Định cấu hình mẫu

Bạn có 2 lựa chọn thay thế để sử dụng với gói này, sử dụng chỉ thị phiến hoặc sử dụng các thành phần laravel

<?php
return [
	'blade-template'  => [
		'type'  =>  'components', //Supported Type: 'components, directives'
		'component'  =>  'AppLayout', //type: components
		'directives'  => [ //type: directives
			'extends'  =>  'layouts.app',
			'section-content'  =>  'content',
		],
	],
];

Bao gồm thành phần chứa các tập lệnh và kiểu cần thiết cho giao diện đồ họa

<head>
    ....
       <x-permissions::styles />
    ....
</head>
 <body> 
    ...
    @livewireScripts
    
    <script src="//cdn.jsdelivr.net/npm/sweetalert2@10"></script>
    //INSERT COMPONENT
       <x-permissions::scripts />
  
 </body>

Để xóa một vai trò, SweetAlert được sử dụng, vì vậy bạn phải có nó trong thiết kế của mình và nó phải ở trước thành phần tập lệnh. Lưu ý rằng bạn phải nhập nó sau tập lệnh Livewire

Chọn một thiết kế Modal

Bạn sẽ có thể chọn trong số các kiểu cư xử mà gói sẽ có, hiện tại chỉ có kiểu kiểu danh sách, được cấu hình tại đây

<?php
return [
	'modals'  => [
		'role'  =>  'list'
	],
];

trang tùy chỉnh

Ở đây bạn xác định dãy số cho các tùy chọn phân trang

<?php
   'paginate' => [
        'perPages' => [
            10, 25, 50, 100, 200
        ]

    ]

Thay đổi tên bảng

Tại đây bạn gán tên các bảng để có thể chạy migration. Theo mặc định, nó sử dụng các bảng được liệt kê trong gói Quyền của Laravel

php artisan vendor:publish --provider="Tonystore\LivewirePermission\LivewirePermissionProvider" --tag=config-permission
0

Loại trừ vai trò

Tại đây, bạn xác định các vai trò mà bạn muốn loại trừ khỏi các truy vấn Livewire để hiển thị và sửa đổi. Theo mặc định, bạn sẽ có một mảng trống

php artisan vendor:publish --provider="Tonystore\LivewirePermission\LivewirePermissionProvider" --tag=config-permission
1

Tên cột dọc

Tại đây, bạn xác định tên của cột mới sẽ được tạo trong bảng vai trò và quyền được xác định, với điều này, bạn có thể thêm mô tả cho từng vai trò hoặc quyền được tạo, theo mặc định, tên của cột là mô tả. Theo mặc định tên

php artisan vendor:publish --provider="Tonystore\LivewirePermission\LivewirePermissionProvider" --tag=config-permission
2

Nếu bạn đánh dấu tùy chọn thêm cột mô tả là true, thì trước đó bạn phải tạo cột đó trong bảng cơ sở dữ liệu tương ứng

Tuyến đường

Đặt tiền tố và phần mềm trung gian của riêng bạn và đặt tên cho đường dẫn và quyền quản lý vai trò. Theo mặc định, bạn sẽ có những điều sau đây

Trong Laravel, vai trò và quyền là một trong những chủ đề khó hiểu nhất trong những năm qua. Hầu hết, bởi vì không có tài liệu về nó. những thứ tương tự "ẩn" dưới các thuật ngữ khác trong khuôn khổ, như "cổng", "chính sách", "lính canh", v.v. Trong bài viết này, tôi sẽ cố gắng giải thích tất cả bằng "ngôn ngữ của con người"

Cổng giống như Quyền

Theo tôi, một trong những sự nhầm lẫn lớn nhất là thuật ngữ "cổng". Tôi nghĩ rằng các nhà phát triển sẽ tránh được nhiều nhầm lẫn nếu họ được gọi là gì

Cổng là Quyền, chỉ được gọi bằng một từ khác

Các hành động điển hình chúng ta cần thực hiện với quyền là gì?

  • Xác định quyền, ví dụ. "manage_users"
  • Kiểm tra quyền trên giao diện người dùng, ví dụ. hiện/ẩn nút
  • Kiểm tra quyền trên back-end, ví dụ. có thể/không thể cập nhật dữ liệu

Đúng rồi, thay chữ "permission" bằng "gate" là hiểu hết

Một ví dụ Laravel đơn giản sẽ là thế này

ứng dụng/Nhà cung cấp/AppServiceProvider. php

________số 8

tài nguyên/lượt xem/điều hướng. lưỡi. php

<ul>

<li>

<a href="{{ route('projects.index') }}">Projects</a>

</li>

@can('manage_users')

<li>

<a href="{{ route('users.index') }}">Users</a>

</li>

@endcan

</ul>

tuyến đường/web. php

<?php
return [
	'blade-template'  => [
		'type'  =>  'components', //Supported Type: 'components, directives'
		'component'  =>  'AppLayout', //type: components
		'directives'  => [ //type: directives
			'extends'  =>  'layouts.app',
			'section-content'  =>  'content',
		],
	],
];
0

Bây giờ, tôi biết rằng, về mặt kỹ thuật, Cổng có thể có nhiều hơn một quyền. Vì vậy, thay vì "manage_users", bạn có thể xác định thứ gì đó như "admin_area". Nhưng trong hầu hết các ví dụ tôi đã thấy, Cổng là từ đồng nghĩa với Quyền

Ngoài ra, trong một số trường hợp, các quyền được gọi là "khả năng", như trong gói Bouncer. Nó cũng có nghĩa tương tự - khả năng/sự cho phép đối với một số hành động. Chúng ta sẽ đi đến các gói, ở phần sau của bài viết này


Nhiều cách khác nhau để kiểm tra quyền truy cập cổng

Một nguồn gây nhầm lẫn khác là làm thế nào/ở đâu để kiểm tra Cổng. Nó linh hoạt đến mức bạn có thể tìm thấy những ví dụ rất khác nhau. Hãy lướt qua chúng

lựa chọn 1. tuyến đường. phần mềm trung gian ('có thể. xxxxxx')

Đây là ví dụ từ trên. Trực tiếp trên tuyến/nhóm, bạn có thể chỉ định phần mềm trung gian

<?php
return [
	'blade-template'  => [
		'type'  =>  'components', //Supported Type: 'components, directives'
		'component'  =>  'AppLayout', //type: components
		'directives'  => [ //type: directives
			'extends'  =>  'layouts.app',
			'section-content'  =>  'content',
		],
	],
];
1

Lựa chọn 2. Bộ điều khiển. có thể không thể()

Trong những dòng đầu tiên của phương thức Controller, chúng ta có thể thấy như thế này, với các phương thức

<?php
return [
	'blade-template'  => [
		'type'  =>  'components', //Supported Type: 'components, directives'
		'component'  =>  'AppLayout', //type: components
		'directives'  => [ //type: directives
			'extends'  =>  'layouts.app',
			'section-content'  =>  'content',
		],
	],
];
03 hoặc
<?php
return [
	'blade-template'  => [
		'type'  =>  'components', //Supported Type: 'components, directives'
		'component'  =>  'AppLayout', //type: components
		'directives'  => [ //type: directives
			'extends'  =>  'layouts.app',
			'section-content'  =>  'content',
		],
	],
];
04, giống hệt với các chỉ thị của Blade

<?php
return [
	'blade-template'  => [
		'type'  =>  'components', //Supported Type: 'components, directives'
		'component'  =>  'AppLayout', //type: components
		'directives'  => [ //type: directives
			'extends'  =>  'layouts.app',
			'section-content'  =>  'content',
		],
	],
];
4

Ngược lại là

<?php
return [
	'blade-template'  => [
		'type'  =>  'components', //Supported Type: 'components, directives'
		'component'  =>  'AppLayout', //type: components
		'directives'  => [ //type: directives
			'extends'  =>  'layouts.app',
			'section-content'  =>  'content',
		],
	],
];
04

<?php
return [
	'blade-template'  => [
		'type'  =>  'components', //Supported Type: 'components, directives'
		'component'  =>  'AppLayout', //type: components
		'directives'  => [ //type: directives
			'extends'  =>  'layouts.app',
			'section-content'  =>  'content',
		],
	],
];
6

Hoặc, nếu bạn không có biến

<?php
return [
	'blade-template'  => [
		'type'  =>  'components', //Supported Type: 'components, directives'
		'component'  =>  'AppLayout', //type: components
		'directives'  => [ //type: directives
			'extends'  =>  'layouts.app',
			'section-content'  =>  'content',
		],
	],
];
06, bạn có thể sử dụng trình trợ giúp
<?php
return [
	'blade-template'  => [
		'type'  =>  'components', //Supported Type: 'components, directives'
		'component'  =>  'AppLayout', //type: components
		'directives'  => [ //type: directives
			'extends'  =>  'layouts.app',
			'section-content'  =>  'content',
		],
	],
];
07

<?php
return [
	'blade-template'  => [
		'type'  =>  'components', //Supported Type: 'components, directives'
		'component'  =>  'AppLayout', //type: components
		'directives'  => [ //type: directives
			'extends'  =>  'layouts.app',
			'section-content'  =>  'content',
		],
	],
];
9

Tùy chọn 3. Hẻm núi. cho phép () hoặc Cổng. từ chối()

Một cách khác là sử dụng mặt tiền Cổng

<head>
    ....
       <x-permissions::styles />
    ....
</head>
 <body> 
    ...
    @livewireScripts
    
    <script src="//cdn.jsdelivr.net/npm/sweetalert2@10"></script>
    //INSERT COMPONENT
       <x-permissions::scripts />
  
 </body>
0

Hoặc, cách ngược lại

<head>
    ....
       <x-permissions::styles />
    ....
</head>
 <body> 
    ...
    @livewireScripts
    
    <script src="//cdn.jsdelivr.net/npm/sweetalert2@10"></script>
    //INSERT COMPONENT
       <x-permissions::scripts />
  
 </body>
1

Hoặc, cách phá thai ngắn hơn, có người trợ giúp

<head>
    ....
       <x-permissions::styles />
    ....
</head>
 <body> 
    ...
    @livewireScripts
    
    <script src="//cdn.jsdelivr.net/npm/sweetalert2@10"></script>
    //INSERT COMPONENT
       <x-permissions::scripts />
  
 </body>
2

Tùy chọn 4. Bộ điều khiển. ủy quyền()

Tùy chọn thậm chí ngắn hơn và tùy chọn yêu thích của tôi là sử dụng

<?php
return [
	'blade-template'  => [
		'type'  =>  'components', //Supported Type: 'components, directives'
		'component'  =>  'AppLayout', //type: components
		'directives'  => [ //type: directives
			'extends'  =>  'layouts.app',
			'section-content'  =>  'content',
		],
	],
];
08 trong Bộ điều khiển. Trong trường hợp không thành công, nó sẽ tự động trả về trang 403

<ul>

<li>

<a href="{{ route('projects.index') }}">Projects</a>

</li>

@can('manage_users')

<li>

<a href="{{ route('users.index') }}">Users</a>

</li>

@endcan

</ul>

0

Tùy chọn 5. Mẫu yêu cầu lớp

Tôi nhận thấy rằng nhiều nhà phát triển tạo các lớp Yêu cầu biểu mẫu chỉ để xác định các quy tắc xác thực, hoàn toàn bỏ qua phương thức đầu tiên của lớp đó, đó là

<?php
return [
	'blade-template'  => [
		'type'  =>  'components', //Supported Type: 'components, directives'
		'component'  =>  'AppLayout', //type: components
		'directives'  => [ //type: directives
			'extends'  =>  'layouts.app',
			'section-content'  =>  'content',
		],
	],
];
08

Bạn cũng có thể sử dụng nó để kiểm tra các cổng. Bằng cách này, bạn đang đạt được sự tách biệt giữa các mối quan tâm, đây là một cách thực hành tốt cho mã vững chắc, vì vậy Bộ điều khiển không quan tâm đến việc xác thực, bởi vì nó được thực hiện trong lớp Yêu cầu biểu mẫu chuyên dụng của nó

<ul>

<li>

<a href="{{ route('projects.index') }}">Projects</a>

</li>

@can('manage_users')

<li>

<a href="{{ route('users.index') }}">Users</a>

</li>

@endcan

</ul>

1

Và sau đó, trong Yêu cầu biểu mẫu

<ul>

<li>

<a href="{{ route('projects.index') }}">Projects</a>

</li>

@can('manage_users')

<li>

<a href="{{ route('users.index') }}">Users</a>

</li>

@endcan

</ul>

2

Chính sách. Bộ quyền dựa trên mô hình

Nếu các quyền của bạn có thể được gán cho một mô hình Eloquent, trong một Trình điều khiển CRUD điển hình, bạn có thể xây dựng một lớp Chính sách xung quanh chúng

Nếu chúng ta chạy lệnh này

<ul>

<li>

<a href="{{ route('projects.index') }}">Projects</a>

</li>

@can('manage_users')

<li>

<a href="{{ route('users.index') }}">Users</a>

</li>

@endcan

</ul>

3

Nó sẽ tạo tệp app/Policies/UserPolicy. php, với các phương thức mặc định có chú thích để giải thích mục đích của chúng

<ul>

<li>

<a href="{{ route('projects.index') }}">Projects</a>

</li>

@can('manage_users')

<li>

<a href="{{ route('users.index') }}">Users</a>

</li>

@endcan

</ul>

4

Trong mỗi phương thức đó, bạn xác định điều kiện cho kết quả trả về đúng/sai. Vì vậy, nếu chúng ta làm theo các ví dụ tương tự như Gates trước đây, chúng ta có thể làm điều này

<ul>

<li>

<a href="{{ route('projects.index') }}">Projects</a>

</li>

@can('manage_users')

<li>

<a href="{{ route('users.index') }}">Users</a>

</li>

@endcan

</ul>

5

Sau đó, bạn có thể kiểm tra Chính sách theo cách rất giống với Gates

<ul>

<li>

<a href="{{ route('projects.index') }}">Projects</a>

</li>

@can('manage_users')

<li>

<a href="{{ route('users.index') }}">Users</a>

</li>

@endcan

</ul>

6

Vì vậy, bạn chỉ định tên phương thức và tên lớp của Chính sách

Nói cách khác, Chính sách chỉ là một cách khác để nhóm các quyền, thay vì Cổng. Nếu hành động của bạn chủ yếu xoay quanh CRUD của Mô hình, thì Chính sách có thể là tùy chọn thuận tiện hơn và có cấu trúc tốt hơn so với Cổng


Vai diễn. Bộ quyền chung

Hãy thảo luận về một sự nhầm lẫn khác. trong tài liệu của Laravel, bạn sẽ không tìm thấy bất kỳ phần nào về Vai trò người dùng. lý do rất đơn giản. thuật ngữ "vai trò" được tạo ra một cách giả tạo, để nhóm quyền dưới một số loại tên, như "quản trị viên" hoặc "biên tập viên"

Từ quan điểm khung, không có "vai trò", chỉ có các cổng/chính sách mà bạn có thể nhóm theo bất kỳ cách nào bạn muốn

Nói cách khác, vai trò là một thực thể BÊN NGOÀI Laravel framework, vì vậy chúng ta cần tự xây dựng cấu trúc vai trò. Nó có thể là một phần của sự nhầm lẫn xác thực tổng thể, nhưng nó hoàn toàn hợp lý vì chúng ta nên kiểm soát cách xác định vai trò

  • Đó là một vai trò hay nhiều vai trò?
  • Người dùng có thể có một hoặc nhiều vai trò không?
  • Ai có thể quản lý vai trò trong hệ thống?
  • vân vân

Vì vậy, chức năng Vai trò là một lớp khác của ứng dụng Laravel của bạn. Đây là nơi chúng tôi đến với các gói Laravel có thể hữu ích. Nhưng chúng tôi cũng có thể tạo các vai trò mà không cần bất kỳ gói nào

  1. Tạo bảng DB "vai trò" và Mô hình hùng biện vai trò
  2. Thêm mối quan hệ từ Người dùng đến Vai trò. một-nhiều hoặc nhiều-nhiều
  3. Chọn các Vai trò mặc định và gán chúng cho Người dùng hiện có
  4. Chỉ định Vai trò mặc định khi đăng ký
  5. Thay đổi Cổng/Chính sách để kiểm tra Vai trò thay thế

Bit cuối cùng là quan trọng nhất

Vì vậy, thay vì

<ul>

<li>

<a href="{{ route('projects.index') }}">Projects</a>

</li>

@can('manage_users')

<li>

<a href="{{ route('users.index') }}">Users</a>

</li>

@endcan

</ul>

5

Bạn sẽ làm một cái gì đó như

<ul>

<li>

<a href="{{ route('projects.index') }}">Projects</a>

</li>

@can('manage_users')

<li>

<a href="{{ route('users.index') }}">Users</a>

</li>

@endcan

</ul>

8

Một lần nữa, ở đây bạn có một số tùy chọn để kiểm tra vai trò. Trong ví dụ trên, chúng tôi giả sử có mối quan hệ

<?php
return [
	'blade-template'  => [
		'type'  =>  'components', //Supported Type: 'components, directives'
		'component'  =>  'AppLayout', //type: components
		'directives'  => [ //type: directives
			'extends'  =>  'layouts.app',
			'section-content'  =>  'content',
		],
	],
];
10 từ Người dùng đến Vai trò và cũng có các hằng số trong mô hình Vai trò như
<?php
return [
	'blade-template'  => [
		'type'  =>  'components', //Supported Type: 'components, directives'
		'component'  =>  'AppLayout', //type: components
		'directives'  => [ //type: directives
			'extends'  =>  'layouts.app',
			'section-content'  =>  'content',
		],
	],
];
11, như
<?php
return [
	'blade-template'  => [
		'type'  =>  'components', //Supported Type: 'components, directives'
		'component'  =>  'AppLayout', //type: components
		'directives'  => [ //type: directives
			'extends'  =>  'layouts.app',
			'section-content'  =>  'content',
		],
	],
];
12, chỉ để tránh truy vấn cơ sở dữ liệu quá nhiều

Nhưng nếu bạn muốn linh hoạt, bạn có thể truy vấn cơ sở dữ liệu mọi lúc

<ul>

<li>

<a href="{{ route('projects.index') }}">Projects</a>

</li>

@can('manage_users')

<li>

<a href="{{ route('users.index') }}">Users</a>

</li>

@endcan

</ul>

9

Nhưng hãy nhớ háo hức tải mối quan hệ "vai trò", nếu không, bạn có thể dễ dàng gặp phải vấn đề truy vấn N+1 tại đây


Làm cho nó linh hoạt. Quyền được lưu trong DB

Theo kinh nghiệm cá nhân của tôi, mô hình thông thường để xây dựng tất cả cùng nhau là thế này

  • Tất cả các quyền và vai trò được lưu trong cơ sở dữ liệu, được quản lý bằng một số bảng quản trị;
  • Các mối quan hệ. quyền nhiều-nhiều, Người dùng thuộc Vai trò (hoặc vai trò nhiều-nhiều);
  • Sau đó, trong AppServiceProvider, bạn tạo vòng lặp
    <?php
    return [
    	'blade-template'  => [
    		'type'  =>  'components', //Supported Type: 'components, directives'
    		'component'  =>  'AppLayout', //type: components
    		'directives'  => [ //type: directives
    			'extends'  =>  'layouts.app',
    			'section-content'  =>  'content',
    		],
    	],
    ];
    13 từ tất cả các quyền từ DB và chạy câu lệnh
    <?php
    return [
    	'blade-template'  => [
    		'type'  =>  'components', //Supported Type: 'components, directives'
    		'component'  =>  'AppLayout', //type: components
    		'directives'  => [ //type: directives
    			'extends'  =>  'layouts.app',
    			'section-content'  =>  'content',
    		],
    	],
    ];
    14 cho từng quyền, trả về giá trị đúng/sai dựa trên vai trò;
  • Và cuối cùng, bạn kiểm tra quyền với
    <?php
    return [
    	'blade-template'  => [
    		'type'  =>  'components', //Supported Type: 'components, directives'
    		'component'  =>  'AppLayout', //type: components
    		'directives'  => [ //type: directives
    			'extends'  =>  'layouts.app',
    			'section-content'  =>  'content',
    		],
    	],
    ];
    15 và
    <?php
    return [
    	'blade-template'  => [
    		'type'  =>  'components', //Supported Type: 'components, directives'
    		'component'  =>  'AppLayout', //type: components
    		'directives'  => [ //type: directives
    			'extends'  =>  'layouts.app',
    			'section-content'  =>  'content',
    		],
    	],
    ];
    16, như ví dụ trên
<?php
return [
	'blade-template'  => [
		'type'  =>  'components', //Supported Type: 'components, directives'
		'component'  =>  'AppLayout', //type: components
		'directives'  => [ //type: directives
			'extends'  =>  'layouts.app',
			'section-content'  =>  'content',
		],
	],
];
00

Nói cách khác, chúng tôi không kiểm tra bất kỳ quyền truy cập nào theo vai trò. Vai trò chỉ là một lớp "nhân tạo", một tập hợp các quyền được chuyển thành Cổng trong vòng đời của ứng dụng

Có vẻ phức tạp?


Các gói để quản lý vai trò/quyền

Các gói phổ biến nhất cho việc này là Spatie Laravel Permission và Bouncer, tôi có một bài viết dài riêng về chúng. Bài viết cũ lắm rồi nhưng những ông lớn dẫn đầu thị trường vẫn vậy, vì tính ổn định của chúng

Chức năng của các gói đó là giúp bạn trừu tượng hóa việc quản lý quyền thành một ngôn ngữ thân thiện với con người, với các phương pháp mà bạn có thể dễ dàng ghi nhớ và sử dụng

Nhìn vào cú pháp tuyệt đẹp này từ sự cho phép của Spatie

<?php
return [
	'blade-template'  => [
		'type'  =>  'components', //Supported Type: 'components, directives'
		'component'  =>  'AppLayout', //type: components
		'directives'  => [ //type: directives
			'extends'  =>  'layouts.app',
			'section-content'  =>  'content',
		],
	],
];
01

Bouncer có thể kém trực quan hơn một chút nhưng vẫn rất tốt

<?php
return [
	'blade-template'  => [
		'type'  =>  'components', //Supported Type: 'components, directives'
		'component'  =>  'AppLayout', //type: components
		'directives'  => [ //type: directives
			'extends'  =>  'layouts.app',
			'section-content'  =>  'content',
		],
	],
];
02

Bạn có thể đọc thêm về cách sử dụng các gói đó trong các liên kết đến Github của họ hoặc bài viết của tôi ở trên

Vì vậy, các gói này là "lớp" xác thực/ủy quyền cuối cùng mà chúng tôi trình bày ở đây trong bài viết này, tôi hy vọng bây giờ bạn đã có được bức tranh đầy đủ và sẽ có thể chọn chiến lược nào sẽ sử dụng


P. S. Đợi đã, còn lính canh thì sao?

Ồ, những. Họ gây ra rất nhiều nhầm lẫn trong những năm qua. Nhiều nhà phát triển nghĩ rằng Người bảo vệ là Vai trò và bắt đầu tạo các bảng DB riêng biệt như "quản trị viên", sau đó chỉ định những người đó làm Người bảo vệ. Một phần, bởi vì trong tài liệu, bạn có thể tìm thấy các đoạn mã như

<?php
return [
	'blade-template'  => [
		'type'  =>  'components', //Supported Type: 'components, directives'
		'component'  =>  'AppLayout', //type: components
		'directives'  => [ //type: directives
			'extends'  =>  'layouts.app',
			'section-content'  =>  'content',
		],
	],
];
17

Tôi thậm chí đã gửi Yêu cầu kéo tới các tài liệu kèm theo cảnh báo để tránh sự hiểu lầm này

Trong tài liệu chính thức, bạn có thể tìm thấy đoạn này

Về cốt lõi, các cơ sở xác thực của Laravel được tạo thành từ "người bảo vệ" và "nhà cung cấp". Bộ bảo vệ xác định cách người dùng được xác thực cho từng yêu cầu. Ví dụ: Laravel cung cấp một bộ bảo vệ phiên duy trì trạng thái bằng cách sử dụng lưu trữ phiên và cookie

Vì vậy, bảo vệ là một khái niệm toàn cầu hơn là vai trò. Một ví dụ về trình bảo vệ là "phiên", ở phần sau của tài liệu, bạn có thể thấy một ví dụ về trình bảo vệ JWT. Nói cách khác, một bộ bảo vệ là một cơ chế xác thực đầy đủ và đối với phần lớn các dự án Laravel, bạn sẽ không cần phải thay đổi bộ bảo vệ hoặc thậm chí không biết chúng hoạt động như thế nào. Bảo vệ nằm ngoài chủ đề vai trò/quyền này