Có nghĩa là gì, người dùng không nên quyết định xem đó có phải là Quản trị viên hay không. Các đặc quyền hoặc hệ thống bảo mật nên.


55

Ví dụ được sử dụng trong câu hỏi chuyển dữ liệu tối thiểu để một hàm chạm vào cách tốt nhất để xác định xem người dùng có phải là quản trị viên hay không. Một câu trả lời phổ biến là:

user.isAdmin()

Điều này đã gợi ý một bình luận được lặp đi lặp lại nhiều lần và được bình chọn nhiều lần:

Người dùng không nên quyết định liệu đó có phải là Quản trị viên hay không. Các đặc quyền hoặc hệ thống bảo mật nên. Một cái gì đó được liên kết chặt chẽ với một lớp không có nghĩa là nên biến nó thành một phần của lớp đó.

Tôi đã trả lời,

Người dùng không quyết định bất cứ điều gì. Đối tượng / bảng người dùng lưu trữ dữ liệu về mỗi người dùng. Người dùng thực tế không được thay đổi mọi thứ về bản thân họ.

Nhưng điều này không hiệu quả. Rõ ràng có một sự khác biệt cơ bản của quan điểm đang làm cho việc giao tiếp trở nên khó khăn. Ai đó có thể giải thích cho tôi tại sao user.isAdmin () xấu và vẽ một bản phác thảo ngắn gọn về những gì nó trông giống như được thực hiện "đúng"?

Thực sự, tôi không thấy lợi thế của việc tách bảo mật khỏi hệ thống mà nó bảo vệ. Bất kỳ văn bản bảo mật nào cũng sẽ nói rằng bảo mật cần được thiết kế thành một hệ thống ngay từ đầu và được xem xét ở mọi giai đoạn phát triển, triển khai, bảo trì và thậm chí là cuối đời. Nó không phải là một cái gì đó có thể được chốt ở bên. Nhưng 17 phiếu bầu cho đến nay về nhận xét này nói rằng tôi đang thiếu một cái gì đó quan trọng.


8
Tôi không nghĩ nó tệ, đặc biệt nếu đó chỉ là một cờ trong bảng db người dùng. Nếu có thay đổi trong tương lai, hãy thay đổi nó trong tương lai. Bạn cũng có thể gọi hệ thống bảo mật từ đối tượng người dùng. Tôi đã từng đọc về mẫu thiết kế mà mọi truy cập db / tài nguyên phải đi qua đối tượng người dùng hiện tại, để đảm bảo bạn không bao giờ thử làm điều gì đó không được phép. Có thể hơi nhiều, nhưng người dùng có thể cung cấp một đối tượng "đặc quyền" bao gồm tất cả những thứ liên quan đến bảo mật.
Cephalepad

Tôi nghĩ rằng đã có một sự hiểu lầm. Đối với bạn, "Người dùng" nghĩa là người sử dụng phần mềm. Đối với họ, "Người dùng" có nghĩa là mã sử dụng chức năng của bạn.
Philipp

2
Không phải là bạn hỏi, nhưng điều tôi sẽ cẩn thận nhất trong trường hợp này là phương thức IsAdmin (), bất cứ nơi nào bạn đặt nó. Tôi thường khuyên bạn nên có một "Quản trị viên" không được mã hóa cứng, nhưng chỉ cần có một tập hợp các quyền có thể và một "Quản trị viên" sẽ có toàn bộ. Bằng cách đó khi bạn cần phân chia vai trò Quản trị viên sau này, mọi việc sẽ dễ dàng hơn nhiều . Và nó cắt giảm logic trường hợp đặc biệt.
psr

1
@Philipp Tôi không nghĩ vậy ... Cả hai trang web đều nói rõ ràng về một Userđối tượng bằng một isAdmin()phương thức (bất cứ điều gì nó làm đằng sau hậu trường)
Izkata

Quá muộn để chỉnh sửa; Tôi có nghĩa là "Cả hai bên", không phải "Cả hai trang web" ... = P
Izkata

Câu trả lời:


12

Hãy suy nghĩ về Người dùng như một khóa và các quyền như một giá trị.

Các bối cảnh khác nhau có thể có các quyền khác nhau cho mỗi người dùng. Vì các quyền có liên quan đến ngữ cảnh, bạn sẽ phải thay đổi người dùng cho từng bối cảnh. Vì vậy, tốt hơn là bạn đặt logic đó ở một nơi khác (ví dụ: lớp ngữ cảnh) và chỉ cần tìm kiếm các quyền khi cần thiết.

Một tác dụng phụ là nó có thể làm cho hệ thống của bạn an toàn hơn, vì bạn không thể quên xóa cờ quản trị viên cho những nơi không liên quan.


40

Nhận xét đó đã bị nói xấu.

Vấn đề không phải là liệu đối tượng người dùng có "quyết định" cho dù đó là quản trị viên, người dùng thử, siêu người dùng hay bất cứ điều gì khác thường. Rõ ràng là nó không quyết định bất kỳ điều gì trong số đó, hệ thống phần mềm nói chung, như bạn đã thực hiện. Vấn đề là liệu người dùng "" lớp nên đại diện cho vai trò của hiệu trưởng mà đối tượng của nó đại diện, hoặc cho dù đây là một trách nhiệm của lớp khác.


6
Ouch (đó là nhận xét của tôi), nhưng bạn làm nó tốt hơn tôi nhiều.
Marjan Venema

Bạn đang nói đến mô hình vai trò trong một lớp Vai trò riêng biệt, không phải trên lớp Người dùng? Bạn không đề xuất một hệ thống riêng như LDAP? Điều gì xảy ra nếu mỗi quyết định duy nhất mà lớp Vai trò đưa ra yêu cầu truy vấn hồ sơ người dùng phù hợp và không yêu cầu thông tin nào khác? Nó vẫn nên tách biệt với Người dùng và nếu vậy, tại sao?
GlenPeterson

2
@GlenPeterson: không nhất thiết, chỉ là các lớp <> bảng và thường có rất nhiều lớp được phân biệt hơn bảng. Việc định hướng dữ liệu sẽ dẫn đến việc xác định danh sách con trên các lớp, khi nhiều lần các danh sách này phải được xác định trên một "sổ cái" (đơn đặt hàng) hoặc một số lớp như vậy đi qua người dùng (hoặc đơn hàng) để lấy ... Đây là một vấn đề về trách nhiệm hơn là động từ và danh từ.
Marjan Venema

9
Hãy suy nghĩ về Người dùng như một khóa và các quyền như một giá trị. Các bối cảnh khác nhau có thể có các quyền khác nhau cho mỗi người dùng. Vì các quyền có liên quan đến ngữ cảnh, bạn sẽ phải thay đổi người dùng cho từng bối cảnh. Vì vậy, tốt hơn là bạn đặt logic đó ở một nơi khác (ví dụ: lớp ngữ cảnh).
Wilbert

2
@Wilbert: Bối cảnh! Đó là đặc biệt giác ngộ! Có, nếu bạn có nhiều hơn một ngữ cảnh cho các quyền đó thì mọi thứ đều có ý nghĩa với tôi. Thông thường tôi thấy các quyền này trong một ngữ cảnh duy nhất và gắn chúng vào người dùng là giải pháp đơn giản nhất trong trường hợp đó. Rõ ràng họ không thuộc về nơi đó nếu họ áp dụng khác nhau trong bối cảnh thứ hai. Nếu bạn viết nó như một câu trả lời, tôi có khả năng chấp nhận nó. Cảm ơn bạn.
GlenPeterson

30

Tôi đã không chọn từ ngữ nhận xét ban đầu theo cách nó được diễn đạt, nhưng nó xác định một vấn đề có thể hợp pháp.

Cụ thể, mối quan tâm rằng bảo đảm tách là xác thực so với ủy quyền .

Xác thực đề cập đến quá trình đăng nhập và nhận dạng. Đó là cách các hệ thống biết bạn là ai và được sử dụng cho những việc như cá nhân hóa, quyền sở hữu đối tượng, v.v.

Ủy quyền đề cập đến những gì bạn được phép làm và điều này (nói chung) không được xác định bởi bạn là ai . Thay vào đó, nó được xác định bởi một số chính sách bảo mật như vai trò hoặc quyền, không quan tâm đến những thứ như tên hoặc địa chỉ email của bạn.

Hai cái này có thể thay đổi trực giao với nhau. Ví dụ: bạn có thể thay đổi mô hình xác thực bằng cách thêm các nhà cung cấp OpenID / OpenAuth. Và bạn có thể thay đổi chính sách bảo mật bằng cách thêm vai trò mới hoặc thay đổi từ RBAC sang ABAC.

Nếu tất cả điều này đi vào một lớp hoặc trừu tượng, thì mã bảo mật của bạn, một trong những công cụ quan trọng nhất để giảm thiểu rủi ro , trở thành rủi ro cao , trớ trêu thay.

Tôi đã làm việc với các hệ thống trong đó xác thực và ủy quyền được kết nối quá chặt chẽ. Trong một hệ thống, có hai cơ sở dữ liệu người dùng song song, mỗi cơ sở cho một loại "vai trò". Người hoặc nhóm đã thiết kế nó dường như chưa bao giờ nghĩ rằng một người dùng vật lý duy nhất có thể ở cả hai vai trò hoặc có thể có một số hành động chung cho nhiều vai trò hoặc có thể có vấn đề với va chạm ID người dùng. Đây là một ví dụ cực kỳ thừa nhận, nhưng nó đã / cực kỳ đau đớn khi làm việc với.

Microsoft và Sun / Oracle (Java) đề cập đến tổng hợp thông tin xác thực và ủy quyền là Hiệu trưởng Bảo mật . Nó không hoàn hảo, nhưng nó hoạt động khá tốt. Ví dụ, trong .NET, bạn có IPrincipal, đóng gói đối tượng IIdentity- trước đây là đối tượng chính sách (ủy quyền) trong khi đối tượng sau là danh tính (xác thực). Bạn có thể đặt câu hỏi một cách hợp lý về quyết định đặt cái này vào cái kia, nhưng điều quan trọng là hầu hết các mã bạn viết sẽ chỉ dành cho một trong những trừu tượng có nghĩa là nó dễ dàng kiểm tra và cấu trúc lại.

Không có gì sai với một User.IsAdminlĩnh vực ... trừ khi đó cũng là một User.Namelĩnh vực. Điều này sẽ chỉ ra rằng khái niệm "Người dùng" không được xác định đúng và thật đáng buồn, đây là một lỗi rất phổ biến ở các nhà phát triển, những người hơi ướt sau tai khi nói đến bảo mật. Thông thường, thứ duy nhất nên được chia sẻ bởi danh tínhchính sách là ID người dùng, không phải ngẫu nhiên, chính xác là cách nó được triển khai trong cả hai mô hình bảo mật Windows và * nix.

Hoàn toàn có thể chấp nhận để tạo các đối tượng trình bao bọc cả danh tính và chính sách. Ví dụ: nó sẽ tạo điều kiện cho việc tạo màn hình bảng điều khiển nơi bạn cần hiển thị thông báo "xin chào" bên cạnh các tiện ích hoặc liên kết khác nhau mà người dùng hiện tại được phép truy cập. Miễn là trình bao bọc này chỉ bao bọc thông tin nhận dạng và chính sách và không yêu cầu sở hữu nó. Nói cách khác, miễn là nó không được trình bày dưới dạng gốc tổng hợp .

Một mô hình bảo mật đơn giản luôn có vẻ là một ý tưởng hay khi bạn lần đầu tiên thiết kế một ứng dụng mới, vì YAGNI và tất cả những thứ đó, nhưng nó hầu như luôn luôn quay trở lại để cắn bạn sau đó, bởi vì, bất ngờ, các tính năng mới được thêm vào!

Vì vậy, nếu bạn biết những gì tốt nhất cho mình, bạn sẽ tách riêng thông tin xác thực và ủy quyền. Ngay cả khi "ủy quyền" ngay bây giờ đơn giản như cờ "IsAdmin", bạn vẫn sẽ tốt hơn nếu đó không phải là một phần của cùng một lớp hoặc bảng như thông tin xác thực, do đó, nếu và khi chính sách bảo mật của bạn cần thay đổi, bạn không cần phải phẫu thuật tái tạo trên các hệ thống xác thực đã hoạt động tốt.


11
Oh tôi ước tôi đã có thời gian để viết này. Sau đó, một lần nữa, tôi có lẽ sẽ không thể đặt nó tốt như bạn đã làm mà không có nhiều suy nghĩ về phần của tôi. Rất nhiều lần bản năng ruột của tôi bảo tôi đi bằng cách này hay cách khác, nhưng giải thích tại sao - với bản thân hoặc người khác - cần nhiều suy nghĩ và nỗ lực hơn. Cảm ơn cho bài viết này. Sẽ khiến nhiều người mắc kẹt, nhưng SE sẽ không cho phép tôi ...
Marjan Venema

Điều này nghe có vẻ gây sốc tương tự như một dự án tôi đang thực hiện và câu trả lời của bạn đã cho tôi một góc nhìn khác. Cảm ơn bạn rất nhiều!
Brandon

"Không có gì sai với trường User.IsAdmin ... trừ khi có trường User.Name" bạn viết. Nhưng isAdminphương pháp là gì? Nó chỉ có thể ủy thác cho một đối tượng có trách nhiệm theo dõi các quyền. Thực hành tốt nhất trong thiết kế mô hình quan hệ (trường nào thực thể có) không nhất thiết có thể dịch thành thực tiễn tốt nhất trong mô hình OO (thông điệp mà đối tượng có thể phản hồi).
KaptajnKold

@KaptajnKold: Tôi tiếp tục đọc tình cảm này trong các chủ đề hỏi đáp khác nhau trong diễn đàn này. Nếu một phương thức tồn tại, nó không thành vấn đề nếu nó trực tiếp thực hiện các hoạt động hoặc nó ủy thác chúng, nó vẫn được coi là trách nhiệm vì các lớp khác có thể dựa vào nó . Bạn User.IsAdminrất có thể là một lớp lót không có gì ngoài cuộc gọi PermissionManager.IsAdmin(this.Id), nhưng nếu nó được tham chiếu bởi 30 lớp khác, bạn không thể thay đổi hoặc xóa nó. Đó là lý do tại sao SRP tồn tại.
Aaronaught

Và, @KaptajnKold, theo như các mô hình quan hệ, tôi không chắc bạn đang làm gì; thậm chí còn tệ hơn khi có một IsAdmin lĩnh vực . Đó là loại lĩnh vực có xu hướng tồn tại lâu sau khi hệ thống phát triển thành RBAC hoặc một thứ gì đó phức tạp hơn và dần dần không đồng bộ với các quyền "thực" và mọi người quên rằng họ không nên sử dụng nó nữa, và ... tốt, chỉ cần nói không. Ngay cả khi bạn nghĩ rằng bạn chỉ có hai vai trò là "quản trị viên" và "không phải quản trị viên", đừng lưu trữ nó dưới dạng trường boolean / bit, hãy chuẩn hóa đúng cách và có bảng quyền.
Aaronaught

28

Vâng, ít nhất nó vi phạm nguyên tắc trách nhiệm duy nhất . Có nên thay đổi (nhiều cấp quản trị viên, thậm chí nhiều vai trò khác nhau không?) Kiểu thiết kế này sẽ khá lộn xộn và khủng khiếp để duy trì.

Xem xét lợi thế của việc tách hệ thống bảo mật khỏi lớp người dùng, để cả hai có thể có vai trò duy nhất, được xác định rõ. Bạn kết thúc với sạch hơn, dễ dàng hơn để duy trì thiết kế tổng thể.


2
@GlenPeterson: Không, người dùng rất có thể tồn tại mà không cần bảo mật. Thế còn tên tôi, tên hiển thị, địa chỉ của tôi, địa chỉ e-mail của tôi, v.v ... Bạn sẽ đặt chúng ở đâu? Nhận dạng (xác thực) và ủy quyền là những con thú khác nhau hoàn toàn.
Marjan Venema

2
class Userlà một thành phần cốt lõi trong bộ ba bảo mật Nhận dạng, Xác thực, Ủy quyền . Chúng được liên kết chặt chẽ ở mức thiết kế, do đó, không phải là không hợp lý để mã phản ánh sự phụ thuộc lẫn nhau này.
MSalters

1
Câu hỏi là, lớp Người dùng có nên chứa tất cả logic để xử lý bộ ba không!
Zavior

3
@MSalters: nếu đó là API cho logic như vậy, nó sẽ biết về nó ngay cả khi nó ủy quyền ở nơi khác cho logic chính xác. Vấn đề được đưa ra là Ủy quyền (quyền) là về sự kết hợp giữa người dùng với thứ khác và do đó không nên là một phần của người dùng hoặc người khác, mà là một phần của hệ thống quyền nhận thức đúng về cả người dùng và bất cứ điều gì đang được cho phép
Marjan Venema

2
Should there be changes- tốt, chúng tôi không biết rằng sẽ có thay đổi. YAGNI và tất cả. Làm những thay đổi khi cần thiết, phải không?
Izkata

10

Tôi nghĩ rằng vấn đề, có lẽ phrased kém, là nó đặt quá nhiều trọng lượng vào Userlớp học. Không, không có vấn đề bảo mật khi có một phương pháp User.isAdmin(), như đã được đề xuất trong các bình luận đó. Rốt cuộc, nếu ai đó đủ sâu trong mã của bạn để tiêm mã độc cho một trong các lớp cốt lõi của bạn, bạn có vấn đề nghiêm trọng. Mặt khác, việc Userquyết định xem đó có phải là quản trị viên cho mọi bối cảnh hay không. Hãy tưởng tượng rằng bạn thêm các vai trò khác nhau: người điều hành cho diễn đàn của bạn, người chỉnh sửa có thể xuất bản bài đăng lên trang nhất, người viết có thể viết nội dung cho người chỉnh sửa để xuất bản, v.v. Với cách tiếp cận đặt nó vào Userđối tượng, bạn sẽ kết thúc với quá nhiều phương thức và quá nhiều logic sống trên cái Userkhông liên quan trực tiếp đến lớp.

Thay vào đó, bạn có thể sử dụng một danh sách các loại quyền khác nhau và một PermissionsManagerlớp. Có lẽ bạn có thể có một phương thức như PermissionsManager.userHasPermission(User, Permission), trả về giá trị boolean. Trong thực tế, nó có thể trông giống như (trong java):

if (!PermissionsManager.userHasPermission(user, Permissions.EDITOR)) {
  Site.renderSecurityRejectionPage();
}

Về cơ bản, nó tương đương với phương thức Rails before_filter, cung cấp một ví dụ về loại kiểm tra quyền này trong thế giới thực.


6
Nó khác với như thế nào user.hasPermission(Permissions.EDITOR), ngoại trừ việc nó dài dòng và thủ tục hơn thay vì OO?
Cephalepad

9
@Arian: vì user.hasPermissionsđặt quá nhiều trách nhiệm vào lớp người dùng. Nó đòi hỏi người dùng phải biết về các quyền, trong khi một người dùng (lớp) sẽ có thể tồn tại mà không cần kiến ​​thức như vậy. Bạn không muốn một lớp người dùng biết về cách in hoặc hiển thị (xây dựng biểu mẫu), bạn để nó cho trình kết xuất / trình tạo máy in hoặc hiển thị lớp trình kết xuất / trình tạo.
Marjan Venema

4
user.hasPermission(P)là API chính xác, nhưng đó chỉ là giao diện. Quyết định thực sự tất nhiên phải được đưa ra trong một hệ thống con bảo mật. Để giải quyết nhận xét của syrion về thử nghiệm, trong quá trình thử nghiệm, bạn có thể trao đổi hệ thống con đó. Tuy nhiên, lợi ích của việc đơn giản user.hasPermission(P)là bạn không làm ô nhiễm tất cả các mã chỉ để bạn có thể kiểm tra class User.
MSalters

3
@MarjanVenema Theo logic đó, Người dùng sẽ vẫn là một đối tượng dữ liệu mà không có trách nhiệm nào cả. Tôi không nói rằng toàn bộ quản lý cấp phép nên được thực hiện trong lớp người dùng, nếu cần một hệ thống phức tạp như vậy. Bạn nói rằng người dùng không cần biết về quyền, nhưng tôi không hiểu tại sao mọi lớp khác cần biết cách giải quyết quyền cho người dùng. Điều này làm cho việc chế nhạo và kiểm tra khó khăn hơn nhiều.
Cephalepad

6
@Arian: trong khi các lớp thiếu máu là một mùi, một số lớp không có bất kỳ hành vi nào ... Người dùng, imho, là một lớp như vậy được sử dụng tốt hơn như là một tham số cho toàn bộ các công cụ khác (những người muốn làm việc / đưa ra quyết định dựa trên người đang yêu cầu họ), hơn là được sử dụng như một thùng chứa cho tất cả các loại hành vi chỉ vì nó có vẻ thuận tiện để mã hóa nó như thế.
Marjan Venema

9

Object.isX () được sử dụng để thể hiện mối quan hệ rõ ràng giữa đối tượng và X, có thể được biểu thị dưới dạng kết quả boolean, ví dụ:

Nước.isBoiling ()

Circuit.isOpen ()

User.isAdmin () giả định một ý nghĩa duy nhất của 'Quản trị viên' trong mọi ngữ cảnh của hệ thống , rằng người dùng, trong mọi bộ phận của hệ thống, là quản trị viên.

Mặc dù nghe có vẻ đơn giản, nhưng một chương trình trong thế giới thực sẽ gần như không bao giờ phù hợp với mô hình này, chắc chắn sẽ có yêu cầu người dùng quản trị tài nguyên [X] nhưng không phải [Y] hoặc cho các loại quản trị viên khác biệt hơn (một [dự án ] quản trị viên so với quản trị viên [hệ thống].

Tình huống này thường yêu cầu điều tra các yêu cầu. Hiếm khi, nếu có, khách hàng sẽ muốn mối quan hệ mà User.isAdmin () thực sự đại diện, vì vậy tôi sẽ ngần ngại thực hiện bất kỳ giải pháp nào như vậy mà không làm rõ.


6

Các poster chỉ có một thiết kế khác nhau và phức tạp hơn trong tâm trí. Theo tôi, User.isAdmin()là tốt . Ngay cả khi sau này bạn giới thiệu một số mô hình quyền phức tạp, tôi thấy ít lý do để di chuyển User.isAdmin(). Sau đó, bạn có thể muốn tách lớp Người dùng thành một đối tượng đại diện cho người dùng đã đăng nhập và một đối tượng tĩnh biểu thị dữ liệu về người dùng. Hoặc bạn có thể không. Tiết kiệm vào ngày mai cho ngày mai.


4
Save tomorrow for tomorrow. Vâng. Khi bạn nhận ra rằng mã được ghép chặt chẽ mà bạn vừa viết đã khiến bạn phải viết lại và bạn phải viết lại vì bạn đã không mất thêm 20 phút để giải mã nó ...
MirroredFate

5
@MirroredFate: Hoàn toàn có thể có một User.isAdminphương thức được kết nối với một cơ chế cấp phép tùy ý mà không cần ghép lớp Người dùng với cơ chế cụ thể đó.
kevin cline

3
Để User.isAdmin được ghép nối với một cơ chế cấp phép tùy ý, ngay cả khi không khớp nối với một cơ chế cụ thể, yêu cầu ... cũng ... khớp nối. Tối thiểu trong số đó sẽ là một giao diện. Và giao diện đó đơn giản là không nên ở đó. Nó đang cung cấp cho lớp người dùng (và giao diện) một cái gì đó mà đơn giản là nó không chịu trách nhiệm. Aaronaught giải thích nó tốt hơn nhiều so với tôi có thể (cũng như sự kết hợp của tất cả các câu trả lời khác cho câu hỏi này).
Marjan Venema

8
@MirroredFate: Tôi không quan tâm nếu tôi phải viết lại một triển khai đơn giản để đáp ứng các yêu cầu phức tạp hơn. Nhưng tôi đã có những trải nghiệm thực sự tồi tệ với các API quá phức tạp được thiết kế để đáp ứng các yêu cầu tưởng tượng trong tương lai không bao giờ nảy sinh.
kevin cline

3
@kevincline Mặc dù các API quá phức tạp (theo định nghĩa) là một điều xấu, tôi không gợi ý rằng người ta nên viết các API quá phức tạp. Tôi đã nói rằng đó Save tomorrow for tomorrowlà một cách tiếp cận thiết kế tồi tệ bởi vì nó làm cho các vấn đề trở nên tầm thường nếu hệ thống được triển khai chính xác tồi tệ hơn nhiều. Trong thực tế, tâm lý đó là những gì dẫn đến các API quá phức tạp, khi lớp trên lớp được thêm vào để vá thiết kế kém ban đầu. Tôi đoán lập trường của tôi là measure twice, cut once.
MirroredFate

5

Không thực sự là một vấn đề ...

Tôi không thấy vấn đề gì với User.isAdmin(). Tôi chắc chắn thích nó hơn một cái gì đó giống như PermissionManager.userHasPermission(user, permissions.ADMIN), trong tên thánh của SRP làm cho mã ít rõ ràng hơn và không thêm bất cứ thứ gì có giá trị.

Tôi nghĩ SRP đang được một số người hiểu theo nghĩa đen. Tôi nghĩ nó ổn, thậm chí tốt hơn là một lớp có giao diện phong phú. SRP chỉ có nghĩa là một đối tượng phải ủy quyền cho cộng tác viên bất cứ điều gì không thuộc trách nhiệm duy nhất của nó. Nếu vai trò quản trị viên của người dùng liên quan nhiều hơn trường boolean, thì rất có thể có ý nghĩa tốt cho đối tượng người dùng ủy quyền cho một Trình quản lý quyền. Nhưng điều đó không có nghĩa là người dùng nên giữ isAdminphương thức đó cũng không phải là một ý tưởng hay . Trong thực tế, điều đó có nghĩa là khi ứng dụng của bạn tốt nghiệp từ đơn giản đến phức tạp, bạn muốn thay đổi mã sử dụng đối tượng Người dùng. IOW, khách hàng của bạn không cần biết những gì thực sự cần thiết để trả lời câu hỏi liệu người dùng có phải là quản trị viên hay không.

... nhưng tại sao bạn thực sự muốn biết?

Điều đó nói rằng, có vẻ như tôi hiếm khi cần biết Người dùng có phải là quản trị viên ngoại trừ việc có thể trả lời một số câu hỏi khác hay không, như người dùng có được phép thực hiện một số hành động cụ thể hay không, ví dụ như cập nhật Widget. Nếu đó là trường hợp, tôi muốn có một phương thức trên Widget, như isUpdatableBy(User user):

boolean isUpdatableBy(User user) {
    return user.isAdmin();
}

Bằng cách đó, Widget có trách nhiệm biết những tiêu chí nào phải được thỏa mãn để người dùng cập nhật và người dùng có trách nhiệm biết liệu đó có phải là quản trị viên hay không. Thiết kế này truyền đạt rõ ràng về ý định của nó và giúp dễ dàng chuyển sang logic kinh doanh phức tạp hơn nếu và khi thời điểm đó đến.

[Biên tập]

Vấn đề khôngUser.isAdmin()và sử dụngPermissionManager.userHasPermission(...)

Tôi nghĩ rằng tôi sẽ thêm vào câu trả lời của mình để giải thích lý do tại sao tôi rất thích gọi một phương thức trên đối tượng Người dùng để gọi một phương thức trên đối tượng PermissionManager nếu tôi muốn biết liệu người dùng có phải là quản trị viên (hoặc có vai trò quản trị viên) hay không.

Tôi nghĩ thật công bằng khi cho rằng bạn sẽ luôn phụ thuộc vào lớp Người dùng bất cứ nơi nào bạn cần đặt câu hỏi là người dùng này có phải là quản trị viên không? Đó là một sự phụ thuộc mà bạn không thể thoát khỏi. Nhưng nếu bạn cần chuyển người dùng đến một đối tượng khác để đặt câu hỏi về nó, điều đó sẽ tạo ra sự phụ thuộc mới vào đối tượng đó trên đầu trang mà bạn đã có trên Người dùng. Nếu câu hỏi được hỏi nhiều, đó là nơi bạn tạo ra một sự phụ thuộc thêm, và đó là nơi bạn có thể phải thay đổi nếu một trong hai người phụ thuộc yêu cầu nó.

So sánh điều này với việc chuyển sự phụ thuộc vào lớp Người dùng. Bây giờ, đột nhiên bạn có một hệ thống trong đó mã khách hàng (mã cần đặt câu hỏi là người dùng này là quản trị viên ) không được kết hợp với việc triển khai cách trả lời câu hỏi này. Bạn có thể tự do thay đổi hoàn toàn hệ thống cấp phép và bạn chỉ phải cập nhật một phương thức trong một lớp để thực hiện. Tất cả các mã khách hàng vẫn giữ nguyên.

Khăng khăng không có isAdminphương thức trên Người dùng vì sợ tạo ra sự phụ thuộc trong lớp Người dùng trên hệ thống con quyền, theo tôi là giống như chi một đô la để kiếm tiền. Chắc chắn, bạn tránh được một phụ thuộc trong lớp Người dùng, nhưng với chi phí tạo một phụ thuộc ở mọi nơi bạn cần đặt câu hỏi. Không phải là một món hời tốt.


2
"SRP chỉ có nghĩa là một đối tượng phải ủy quyền cho cộng tác viên bất cứ điều gì không thuộc trách nhiệm duy nhất của nó" - sai . Định nghĩa của SRP bao gồm mọi thứ mà một đối tượng thực hiện trực tiếp mọi thứ mà nó ủy nhiệm. Một lớp chỉ thực hiện trực tiếp một nhiệm vụ, nhưng thực hiện 50 nhiệm vụ khác một cách gián tiếp thông qua ủy quyền, vẫn (có thể) vi phạm SRP.
Aaronaught

KaptajnKold: Tôi + 1'd vì tôi đồng ý với bạn và tôi cảm thấy có chút tội lỗi về điều đó. Bài viết của bạn thêm vào cuộc thảo luận, nhưng nó không trả lời câu hỏi.
GlenPeterson

@GlenPeterson Vâng, tôi đã cố gắng giải quyết phần câu hỏi "nó trông như thế nào" đúng "" Ngoài ra: Bạn tuyệt đối không nên cảm thấy tội lỗi khi đồng ý với tôi :)
KaptajnKold

1
@JamesSnell Có lẽ. Nếu như. Nhưng đó là một chi tiết triển khai mà tôi vẫn sẽ giới hạn trong phương thức isAdmin trên Người dùng. Bằng cách đó, mã khách hàng của bạn không phải thay đổi khi "quyền quản trị" của Người dùng phát triển từ trường boolean sang thứ gì đó cao cấp hơn. Bạn có thể có nhiều nơi bạn cần biết nếu Người dùng là quản trị viên và bạn không muốn phải thay đổi họ mỗi khi hệ thống ủy quyền của bạn thay đổi.
KaptajnKold

1
@JamesSnell Có lẽ chúng ta hiểu lầm nhau? Câu hỏi về việc người dùng có phải là quản trị viên hay không không giống như câu hỏi liệu người dùng có được phép thực hiện một hành động cụ thể hay không . Câu trả lời cho câu hỏi đầu tiên luôn độc lập với bối cảnh. Câu trả lời thứ hai phụ thuộc rất nhiều vào bối cảnh. Đây là những gì tôi đã cố gắng giải quyết trong nửa sau của câu trả lời ban đầu của tôi.
KaptajnKold

1

Cuộc thảo luận này làm tôi nhớ đến một Bài đăng trên Blog của Eric Lippert, được tôi chú ý trong một cuộc thảo luận tương tự về thiết kế lớp học, bảo mật và tính chính xác.

Tại sao rất nhiều lớp khung được niêm phong?

Cụ thể, Eric nêu lên quan điểm:

4) An toàn. toàn bộ vấn đề đa hình là bạn có thể vượt qua các vật thể trông giống như Động vật nhưng thực tế là Giraffes. Có những vấn đề an ninh tiềm ẩn ở đây.

Mỗi khi bạn thực hiện một phương thức lấy một thể hiện của loại không được bảo mật, bạn PHẢI viết phương thức đó để trở nên mạnh mẽ khi đối mặt với các trường hợp có khả năng thù địch của loại đó. Bạn không thể dựa vào bất kỳ sự bất biến nào mà bạn biết là đúng với các triển khai của BẠN, bởi vì một số trang web thù địch có thể phân lớp thực hiện của bạn, ghi đè các phương thức ảo để thực hiện công cụ làm rối logic của bạn và chuyển nó vào. Mỗi khi tôi niêm phong một lớp , Tôi có thể viết các phương thức sử dụng lớp đó với sự tự tin rằng tôi biết lớp đó làm gì.

Điều này có vẻ như là một tiếp tuyến, nhưng tôi nghĩ rằng nó phù hợp với điểm mà các áp phích khác đã nêu lên về nguyên tắc trách nhiệm duy nhất RẮN . Xem xét lớp độc hại sau đây là lý do không thực hiện một phương thức hoặc thuộc tính.User.IsAdmin

public class MyUser : User
{

    public new boolean IsAdmin()
    {
        // You know it!
        return true;
    }

    // Anything else we can break today?

}

Cấp, đó là một chút khó khăn: chưa ai đề xuất thực hiện IsAmdminmột phương thức ảo, nhưng nó có thể xảy ra nếu kiến ​​trúc người dùng / vai trò của bạn kết thúc phức tạp một cách kỳ lạ. (. Hoặc có lẽ không xem xét public class Admin : User { ... }:. Một lớp học như vậy có thể có chính xác mã trên trong nó) Bắt một nhị phân độc hại vào đúng vị trí không phải là một vector tấn công phổ biến và có rất nhiều tiềm năng hơn cho sự hỗn loạn hơn một thư viện sử dụng tối nghĩa - sau đó một lần nữa, điều này có thể là lỗi đặc quyền Trốn thoát mở ra cánh cửa cho các shenanigans thực sự. Cuối cùng, nếu một đối tượng nhị phân hoặc đối tượng độc hại đã tìm được thời gian chạy, thì sẽ không còn quá nhiều để tưởng tượng "Đặc quyền hoặc Hệ thống bảo mật" được thay thế theo cách tương tự.

Nhưng theo quan điểm của Eric, nếu bạn đặt mã người dùng của mình vào một loại hệ thống dễ bị tổn thương cụ thể, có thể bạn đã thua trò chơi.

Ồ, và để chính xác cho hồ sơ, tôi đồng ý với định đề trong câu hỏi: Một người dùng không nên quyết định liệu đó có phải là Quản trị viên hay không. Hệ thống Đặc quyền hoặc Bảo mật nên. Một User.IsAdminphương pháp là một ý tưởng tồi nếu bạn đang chạy mã trên hệ thống mà bạn không kiểm soát 100% mã - thay vào đó bạn nên làm Permissions.IsAdmin(User user).


Huh? Làm thế nào là thích hợp này? Bất kể bạn đặt chính sách bảo mật ở đâu, bạn luôn có thể phân lớp nó với một cái gì đó không an toàn. Sẽ không có vấn đề gì nếu thứ đó là Người dùng, Hiệu trưởng, Chính sách, Giấy phép hoặc bất cứ thứ gì. Đó là một vấn đề hoàn toàn không liên quan.
Aaronaught

Đó là một đối số bảo mật có lợi cho Trách nhiệm duy nhất. Để trả lời trực tiếp quan điểm của bạn, mô-đun bảo mật & quyền sẽ là một lớp kín, nhưng một số thiết kế nhất định có thể mong muốn lớp Người dùng là ảo & được kế thừa. Đó là điểm cuối cùng trên bài viết trên blog (có lẽ là ít có khả năng / quan trọng nhất?), Và có lẽ tôi đang giải thích các vấn đề một chút, nhưng được cung cấp nguồn, rõ ràng rằng đa hình có thể là mối đe dọa bảo mật và do đó hạn chế trách nhiệm của một lớp / đối tượng nhất định sẽ an toàn hơn.
Patrick M

Tôi không chắc tại sao bạn nói vậy. Rất nhiều khung bảo mật và quyền được mở rộng cao. Hầu hết các loại .NET lõi liên quan đến bảo mật (IPrincipal, IIdentity, Claimset, v.v.) không chỉ không được niêm phong mà trên thực tế là các giao diện hoặc các lớp trừu tượng để bạn có thể cắm bất cứ thứ gì bạn muốn. Điều Eric đang nói là bạn sẽ không muốn thứ gì đó System.Stringcó thể được kế thừa bởi vì tất cả các loại mã khung quan trọng đều đưa ra các giả định rất cụ thể về cách thức hoạt động của nó. Trong thực tế, hầu hết "mã người dùng" (bao gồm bảo mật) đều có thể kế thừa để cho phép nhân đôi kiểm tra.
Aaronaught

1
Dù sao, tính đa hình không được đề cập ở bất cứ đâu trong câu hỏi và nếu bạn theo liên kết của tác giả đến nơi nhận xét ban đầu, thì rõ ràng là nó không đề cập đến sự kế thừa hay thậm chí là bất biến lớp học nói chung - đó là về trách nhiệm.
Aaronaught

Tôi biết nó không được đề cập, nhưng nguyên tắc trách nhiệm của lớp có liên quan trực tiếp đến đa hình. Nếu không có IsAdminphương pháp để ghi đè / đồng chọn, thì đó không phải là lỗ hổng bảo mật tiềm năng. Xem xét các phương pháp của các giao diện hệ thống đó, IPrincipalIIdentityrõ ràng là các nhà thiết kế không đồng ý với tôi, và vì vậy tôi thừa nhận quan điểm.
Patrick M

0

Vấn đề ở đây là:

if (user.isAdmin()) {
   doPriviledgedOp();
} else {
   // maybe we can anyway!
   doPriviledgedOp();
}

Hoặc bạn đã thiết lập bảo mật của mình đúng cách trong trường hợp phương thức đặc quyền sẽ kiểm tra xem người gọi có đủ thẩm quyền hay không - trong trường hợp đó, kiểm tra là không cần thiết. Hoặc bạn chưa và đang tin tưởng một lớp không có quyền riêng tư để đưa ra quyết định riêng về bảo mật.


4
Một lớp học không được đào tạo là gì?
Brandon

1
Bạn thực sự không thể làm gì để ngăn chặn loại mã này được viết. Cho dù bạn thiết kế ứng dụng của mình như thế nào, ở đâu đó sẽ có một kiểm tra rõ ràng như thế này và ai đó có thể viết nó không an toàn. Ngay cả khi hệ thống cấp phép của bạn được sắp xếp hợp lý như trang trí một phương thức với thuộc tính vai trò hoặc quyền - ai đó có thể ném không đúng quyền "công khai" hoặc "mọi người" vào nó. Vì vậy, tôi không thực sự thấy làm thế nào điều này làm cho một phương pháp như User.IsAdmin cụ thể là một vấn đề.
Aaronaught
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.