Contoh yang digunakan dalam pertanyaan memberikan data minimum ke suatu fungsi menyentuh cara terbaik untuk menentukan apakah pengguna adalah administrator atau tidak. Satu jawaban yang umum adalah:
user.isAdmin()
Ini mendorong komentar yang diulang beberapa kali dan banyak dipilih:
Seorang pengguna tidak boleh memutuskan apakah itu seorang Admin atau bukan. Hak Istimewa atau sistem Keamanan harus. Sesuatu yang tergabung erat dengan kelas tidak berarti itu adalah ide yang baik untuk menjadikannya bagian dari kelas itu.
Saya membalas,
Pengguna tidak memutuskan apa pun. Objek / tabel Pengguna menyimpan data tentang setiap pengguna. Pengguna yang sebenarnya tidak bisa mengubah segalanya tentang diri mereka sendiri.
Tetapi ini tidak produktif. Jelas ada perbedaan mendasar dari perspektif yang membuat komunikasi menjadi sulit. Dapatkah seseorang menjelaskan kepada saya mengapa user.isAdmin () buruk, dan melukis sketsa singkat tentang apa yang tampak seperti dilakukan "benar"?
Sungguh, saya gagal melihat keuntungan memisahkan keamanan dari sistem yang dilindungi. Teks keamanan apa pun akan mengatakan bahwa keamanan perlu dirancang ke dalam sistem sejak awal dan dipertimbangkan pada setiap tahap pengembangan, penerapan, pemeliharaan, dan bahkan akhir masa pakainya. Itu bukan sesuatu yang bisa dibaut ke samping. Namun sejauh ini 17 suara di komentar ini mengatakan bahwa saya kehilangan sesuatu yang penting.
sumber
User
objek denganisAdmin()
metode (apa pun yang dilakukannya di balik layar)Jawaban:
Pikirkan tentang Pengguna sebagai kunci, dan izin sebagai nilai.
Konteks yang berbeda mungkin memiliki izin yang berbeda untuk setiap pengguna. Karena izin relevan dengan konteks, Anda harus mengubah pengguna untuk setiap konteks. Jadi lebih baik Anda meletakkan logika itu di tempat lain (misalnya kelas konteks), dan lihat saja izin yang diperlukan.
Efek sampingnya adalah itu mungkin membuat sistem Anda lebih aman, karena Anda tidak bisa lupa untuk menghapus flag admin untuk tempat-tempat yang tidak relevan.
sumber
Komentar itu sangat buruk.
Intinya bukan apakah objek pengguna "memutuskan" apakah itu admin, pengguna uji coba, pengguna super atau apa pun di dalam atau di luar kebiasaan. Jelas itu tidak memutuskan hal-hal itu, sistem perangkat lunak pada umumnya, seperti yang diterapkan oleh Anda, tidak. Intinya adalah apakah "pengguna" kelas harus mewakili peran kepala sekolah bahwa benda-benda yang mewakili, atau apakah ini merupakan tanggung jawab dari kelas lain.
sumber
Saya tidak akan memilih untuk mengucapkan komentar asli seperti yang dikatakan, tetapi mengidentifikasi masalah yang berpotensi sah.
Secara khusus, masalah yang memerlukan pemisahan adalah otentikasi vs otorisasi .
Otentikasi mengacu pada proses masuk dan mendapatkan identitas. Begitulah sistem mengetahui siapa Anda , dan digunakan untuk hal-hal seperti personalisasi, kepemilikan objek, dll.
Otorisasi mengacu pada apa yang Anda boleh lakukan , dan ini (umumnya) tidak ditentukan oleh siapa Anda . Sebaliknya, itu ditentukan oleh beberapa kebijakan keamanan seperti peran atau izin, yang tidak peduli tentang hal-hal seperti nama atau alamat email Anda.
Keduanya dapat berubah secara orthogonal satu sama lain. Misalnya, Anda dapat mengubah model otentikasi dengan menambahkan penyedia OpenID / OpenAuth. Dan Anda dapat mengubah kebijakan keamanan dengan menambahkan peran baru, atau mengubah dari RBAC ke ABAC.
Jika semua ini masuk ke dalam satu kelas atau abstraksi, maka kode keamanan Anda, yang merupakan salah satu alat terpenting Anda untuk mitigasi risiko , ironisnya, berisiko tinggi.
Saya telah bekerja dengan sistem di mana otentikasi dan otorisasi terlalu erat digabungkan. Dalam satu sistem, ada dua database pengguna paralel, masing-masing untuk satu jenis "peran". Orang atau tim yang mendesainnya tampaknya tidak pernah mempertimbangkan bahwa pengguna fisik tunggal mungkin berada di kedua peran, atau bahwa mungkin ada tindakan tertentu yang umum terjadi pada beberapa peran, atau bahwa mungkin ada masalah dengan tabrakan ID Pengguna. Ini adalah contoh yang diakui ekstrem, tetapi sangat menyakitkan untuk dikerjakan.
Microsoft dan Sun / Oracle (Java) merujuk pada agregat informasi otentikasi dan otorisasi sebagai Principal Keamanan . Itu tidak sempurna, tetapi bekerja dengan cukup baik. NET, misalnya, Anda memiliki
IPrincipal
, yang merangkum yangIIdentity
- mantan makhluk kebijakan (otorisasi) objek sedangkan yang kedua adalah identitas (otentikasi). Anda bisa mempertanyakan keputusan untuk menempatkan satu di dalam yang lain, tetapi yang penting adalah bahwa sebagian besar kode yang Anda tulis hanya untuk salah satu abstraksi yang berarti mudah untuk menguji dan refactor.Tidak ada yang salah dengan
User.IsAdmin
bidang ... kecuali ada jugaUser.Name
bidang. Ini akan menunjukkan bahwa konsep "Pengguna" tidak didefinisikan dengan benar dan ini, sayangnya, adalah kesalahan yang sangat umum di kalangan pengembang yang agak basah di belakang telinga ketika datang ke keamanan. Biasanya, satu-satunya hal yang harus dibagikan oleh identitas dan kebijakan adalah ID Pengguna, yang, tidak secara kebetulan, adalah persis bagaimana itu diterapkan di kedua model keamanan Windows dan * nix.Benar-benar dapat diterima untuk membuat objek pembungkus yang merangkum identitas dan kebijakan. Misalnya, ini akan memfasilitasi pembuatan layar dasbor di mana Anda perlu menampilkan pesan "halo" di samping berbagai widget atau tautan yang diizinkan diakses oleh pengguna saat ini. Selama pembungkus ini hanya membungkus identitas dan informasi kebijakan, dan tidak mengklaim memilikinya. Dengan kata lain, selama itu tidak disajikan sebagai akar agregat .
Model keamanan yang sederhana selalu tampak seperti ide yang bagus ketika Anda pertama kali merancang aplikasi baru, karena YAGNI dan semua itu, tetapi hampir selalu akhirnya kembali menggigit Anda nanti, karena, kejutan yang mengejutkan, fitur baru ditambahkan!
Jadi, jika Anda tahu yang terbaik untuk Anda, Anda akan memisahkan informasi otentikasi dan otorisasi. Sekalipun "otorisasi" saat ini sesederhana bendera "IsAdmin", Anda masih akan lebih baik jika itu bukan bagian dari kelas atau tabel yang sama dengan informasi otentikasi, sehingga jika dan ketika kebijakan keamanan Anda perlu ubah, Anda tidak perlu melakukan operasi rekonstruksi pada sistem otentikasi Anda yang sudah berfungsi dengan baik.
sumber
isAdmin
itu metode? Mungkin saja mendelegasikan ke objek yang tanggung jawabnya untuk melacak izin. Apa praktik terbaik dalam desain model relasional (bidang apa yang dimiliki entitas) belum tentu diterjemahkan ke praktik terbaik dalam model OO (pesan apa yang dapat ditanggapi oleh objek).User.IsAdmin
mungkin hanya menjadi satu-liner yang tidak melakukan apa-apa selain meneleponPermissionManager.IsAdmin(this.Id)
, tetapi jika direferensikan oleh 30 kelas lain, Anda tidak dapat mengubah atau menghapusnya. Itu sebabnya SRP ada.IsAdmin
ladang . Ini adalah jenis bidang yang cenderung bertahan lama setelah sistem berevolusi menjadi RBAC atau sesuatu yang lebih kompleks, dan secara bertahap keluar dari sinkronisasi dengan izin "nyata", dan orang-orang lupa bahwa mereka tidak seharusnya menggunakannya lagi, dan ... well, katakan saja tidak. Bahkan jika Anda berpikir Anda hanya memiliki dua peran, "admin" dan "non-admin", jangan menyimpannya sebagai bidang boolean / bit, normalkan dengan benar dan miliki tabel izin.Ya, setidaknya itu melanggar prinsip tanggung jawab tunggal . Haruskah ada perubahan (beberapa level admin, peran bahkan lebih berbeda?) Desain semacam ini akan cukup berantakan dan mengerikan untuk dipertahankan.
Pertimbangkan keuntungan memisahkan sistem keamanan dari kelas pengguna, sehingga keduanya dapat memiliki peran tunggal yang didefinisikan dengan baik. Anda berakhir dengan lebih bersih, lebih mudah untuk mempertahankan desain secara keseluruhan.
sumber
class User
adalah komponen inti dalam triplet keamanan Identifikasi, Otentikasi, Otorisasi . Ini terkait erat pada tingkat desain, sehingga tidak masuk akal bagi kode untuk mencerminkan saling ketergantungan ini.Should there be changes
- yah, kita tidak tahu bahwa akan ada perubahan. YAGNI dan semuanya. Apakah perubahan itu ketika diperlukan, kan?Saya pikir masalahnya, mungkin tidak diutarakan dengan baik, adalah terlalu banyak memberatkan
User
kelas. Tidak, tidak ada masalah keamanan dengan memiliki metodeUser.isAdmin()
, seperti yang disarankan dalam komentar tersebut. Lagi pula, jika seseorang cukup dalam dalam kode Anda untuk menyuntikkan kode berbahaya untuk salah satu kelas inti Anda, Anda memiliki masalah serius. Di sisi lain, tidak masuk akalUser
jika memutuskan apakah itu administrator untuk setiap konteks. Bayangkan Anda menambahkan peran yang berbeda: moderator untuk forum Anda, editor yang dapat mempublikasikan posting ke halaman depan, penulis yang dapat menulis konten untuk diterbitkan oleh editor, dan sebagainya. Dengan pendekatan meletakkannya padaUser
objek, Anda akan berakhir dengan terlalu banyak metode dan terlalu banyak logika yang hidup padaUser
yang tidak berhubungan langsung dengan kelas.Sebagai gantinya, Anda bisa menggunakan enum dari berbagai jenis izin, dan
PermissionsManager
kelas. Mungkin Anda bisa memiliki metode sepertiPermissionsManager.userHasPermission(User, Permission)
, mengembalikan nilai boolean. Dalam praktiknya, ini mungkin terlihat seperti (dalam java):Ini pada dasarnya setara dengan metode
before_filter
metode Rails , yang memberikan contoh jenis pemeriksaan izin di dunia nyata.sumber
user.hasPermission(Permissions.EDITOR)
, kecuali bahwa itu lebih verbose dan prosedural daripada OO?user.hasPermissions
menempatkan terlalu banyak tanggung jawab ke dalam kelas pengguna. Ini mengharuskan pengguna mengetahui tentang izin, sementara pengguna (kelas) harus bisa ada tanpa pengetahuan tersebut. Anda tidak ingin kelas pengguna mengetahui tentang cara mencetak atau menampilkan (membuat formulir) itu sendiri, Anda menyerahkannya ke kelas pembuat render / pembangun atau tampilan kelas renderer / pembangun.user.hasPermission(P)
adalah API yang benar, tetapi itu hanya antarmuka. Keputusan nyata tentu saja harus dibuat dalam subsistem keamanan. Untuk menanggapi komentar Syrion tentang pengujian, selama pengujian Anda dapat menukar subsistem itu. Namun, manfaat langsungnyauser.hasPermission(P)
adalah Anda tidak mencemari semua kode hanya agar Anda dapat mengujiclass User
.Object.isX () digunakan untuk mewakili hubungan yang jelas antara objek dan X, yang dapat diekspresikan sebagai hasil boolean, misalnya:
Water.isBoiling ()
Circuit.isOpen ()
User.isAdmin () mengasumsikan makna tunggal 'Administrator' dalam setiap konteks sistem , bahwa pengguna, di setiap bagian sistem, adalah administrator.
Walaupun kedengarannya cukup sederhana, program dunia nyata hampir tidak akan pernah cocok dengan model ini, pasti akan ada persyaratan bagi pengguna untuk mengelola sumber daya [X] tetapi tidak [Y], atau untuk jenis administrator yang lebih berbeda (proyek [ ] administrator vs administrator [sistem]).
Situasi ini biasanya memerlukan penyelidikan persyaratan. Jarang, jika pernah, klien menginginkan hubungan yang benar-benar diwakili oleh User.isAdmin (), jadi saya akan ragu untuk mengimplementasikan solusi semacam itu tanpa klarifikasi.
sumber
Poster itu hanya memiliki desain yang berbeda dan lebih rumit dalam pikiran. Menurut saya,
User.isAdmin()
tidak apa-apa . Bahkan jika Anda kemudian memperkenalkan beberapa model izin yang rumit, saya melihat sedikit alasan untuk pindahUser.isAdmin()
. Kemudian, Anda mungkin ingin membagi kelas Pengguna menjadi objek yang mewakili pengguna yang masuk dan objek statis yang mewakili data tentang pengguna. Atau mungkin juga tidak. Simpan besok untuk besok.sumber
Save tomorrow for tomorrow
. Ya. Ketika Anda mengetahui bahwa kode yang digabungkan dengan ketat yang baru saja Anda tulis telah membuat Anda bingung dan Anda harus menulis ulang karena Anda tidak mengambil 20 menit ekstra untuk memisahkannya ...User.isAdmin
metode yang terhubung ke mekanisme izin sewenang-wenang tanpa menghubungkan kelas Pengguna ke mekanisme tertentu.Save tomorrow for tomorrow
adalah pendekatan desain yang mengerikan karena itu membuat masalah yang akan sepele jika sistem diimplementasikan dengan benar jauh lebih buruk. Faktanya, mentalitas itulah yang menghasilkan API yang terlalu rumit, karena lapisan demi lapisan ditambahkan untuk menambal desain awal yang buruk. Saya kira sikap saya adalahmeasure twice, cut once
.Tidak terlalu masalah ...
Saya tidak melihat masalah dengan
User.isAdmin()
. Saya tentu saja lebih suka yang seperti ituPermissionManager.userHasPermission(user, permissions.ADMIN)
, yang dalam nama suci SRP membuat kode kurang jelas dan tidak menambah nilai.Saya pikir SRP sedang ditafsirkan sedikit secara harfiah oleh beberapa orang. Saya pikir itu baik-baik saja, bahkan lebih baik bagi kelas untuk memiliki antarmuka yang kaya. SRP berarti bahwa suatu objek harus mendelegasikan kepada kolaborator apa pun yang tidak termasuk dalam tanggung jawab tunggal itu. Jika peran admin pengguna adalah sesuatu yang lebih terlibat daripada bidang boolean, mungkin sangat masuk akal bagi objek pengguna untuk mendelegasikan ke Manajer Izin. Tetapi itu tidak berarti bahwa itu juga bukan ide yang baik bagi pengguna untuk mempertahankan
isAdmin
metodenya. Bahkan itu berarti bahwa ketika aplikasi Anda lulus dari yang sederhana ke kompleks, Anda ingin mengubah kode yang menggunakan objek Pengguna. TKI, klien Anda tidak perlu tahu apa yang sebenarnya diperlukan untuk menjawab pertanyaan apakah pengguna adalah admin atau tidak.... tetapi mengapa Anda benar-benar ingin tahu?
Yang mengatakan, Sepertinya saya Anda jarang perlu tahu apakah Pengguna adalah admin kecuali untuk dapat menjawab beberapa pertanyaan lain, seperti apakah pengguna diizinkan untuk melakukan beberapa tindakan tertentu, misalnya memperbarui Widget. Jika itu masalahnya, saya lebih suka memiliki metode pada Widget, seperti
isUpdatableBy(User user)
:Dengan cara itu Widget bertanggung jawab untuk mengetahui kriteria apa yang harus dipenuhi agar dapat diperbarui oleh pengguna, dan pengguna bertanggung jawab untuk mengetahui apakah itu adalah admin. Desain ini mengkomunikasikan dengan jelas tentang niatnya dan membuatnya lebih mudah untuk lulus ke logika bisnis yang lebih kompleks jika dan ketika saatnya tiba.
[Sunting]
Masalah dengan tidak memiliki
User.isAdmin()
dan menggunakanPermissionManager.userHasPermission(...)
Saya pikir saya akan menambahkan jawaban saya untuk menjelaskan mengapa saya lebih suka memanggil metode pada objek Pengguna daripada memanggil metode pada objek PermissionManager jika saya ingin tahu apakah pengguna adalah admin (atau memiliki peran admin).
Saya pikir adil untuk menganggap bahwa Anda akan selalu bergantung pada kelas Pengguna di mana pun Anda perlu bertanya apakah pengguna ini admin? Itu ketergantungan yang tidak bisa Anda singkirkan. Tetapi jika Anda perlu meneruskan pengguna ke objek yang berbeda untuk mengajukan pertanyaan tentangnya, itu menciptakan ketergantungan baru pada objek di atas yang sudah Anda miliki pada Pengguna. Jika pertanyaannya banyak ditanyakan, itu adalah banyak tempat di mana Anda membuat ketergantungan ekstra, dan itu adalah banyak tempat yang mungkin harus Anda ubah jika salah satu dari ketergantungan itu menuntutnya.
Bandingkan ini dengan memindahkan ketergantungan ke kelas Pengguna. Sekarang, tiba-tiba Anda memiliki sistem di mana kode klien (kode yang perlu mengajukan pertanyaan adalah pengguna ini admin ) tidak digabungkan dengan implementasi bagaimana pertanyaan ini dijawab. Anda bebas untuk mengubah sistem izin sepenuhnya, dan Anda hanya perlu memperbarui satu metode dalam satu kelas untuk melakukannya. Semua kode klien tetap sama.
Bersikeras tidak memiliki
isAdmin
metode pada Pengguna karena takut menciptakan ketergantungan di kelas Pengguna pada subsistem izin, menurut saya mirip dengan menghabiskan dolar untuk mendapatkan uang receh. Tentu, Anda menghindari satu ketergantungan di kelas Pengguna, tetapi dengan biaya menciptakan satu di setiap tempat di mana Anda perlu mengajukan pertanyaan. Bukan tawaran yang bagus.sumber
Diskusi ini mengingatkan saya pada Blog Post oleh Eric Lippert, yang menarik perhatian saya dalam diskusi serupa tentang desain kelas, keamanan dan kebenaran.
Mengapa Banyak Kelas Kerangka Disegel?
Secara khusus, Eric mengemukakan intinya:
Ini mungkin tampak seperti bersinggungan, tapi saya pikir itu sesuai dengan poin yang disampaikan poster lain tentang prinsip tanggung jawab tunggal SOLID . Pertimbangkan kelas jahat berikut sebagai alasan untuk tidak menerapkan metode atau properti.
User.IsAdmin
Memang, ini sedikit sulit: belum ada yang menyarankan membuat
IsAmdmin
metode virtual, tetapi itu bisa terjadi jika arsitektur pengguna / peran Anda akhirnya menjadi sangat rumit. (Atau mungkin tidak. Pertimbangkanpublic class Admin : User { ... }
: kelas seperti itu mungkin memiliki persis kode di atas di dalamnya.) Mendapatkan biner berbahaya ke tempatnya bukanlah vektor serangan umum dan memiliki potensi lebih besar untuk kekacauan daripada perpustakaan pengguna yang tidak jelas - sekali lagi, ini bisa jadi bug Privilege Escalation yang membuka pintu bagi kejahatan nyata. Akhirnya, jika sebuah instance biner atau objek jahat menemukan jalannya ke run time, itu tidak lebih dari sekadar membayangkan "Privilege or Security System" diganti dengan cara yang sama.Tetapi mengingat inti dari hati Eric, jika Anda memasukkan kode pengguna Anda ke dalam jenis sistem rentan tertentu, mungkin Anda memang baru saja kehilangan permainan.
Oh, dan lebih tepatnya untuk catatan, saya setuju dengan postulat dalam pertanyaan: “Seorang pengguna tidak boleh memutuskan apakah itu seorang Admin atau bukan. Sistem Privileges atau Keamanan seharusnya. ”
User.IsAdmin
Metode adalah ide yang buruk jika Anda menjalankan kode pada sistem Anda tidak tetap dalam kontrol 100% dari kode - Anda harus melakukannyaPermissions.IsAdmin(User user)
.sumber
System.String
warisan karena semua jenis kode kerangka kerja kritis membuat asumsi yang sangat spesifik tentang cara kerjanya. Dalam praktiknya, sebagian besar "kode pengguna" (termasuk keamanan) dapat diwarisi untuk memungkinkan uji ganda.IsAdmin
metode untuk mengganti / mengkooptasi, tidak ada lubang keamanan potensial. Meninjau metode dari antarmuka sistem itu,IPrincipal
danIIdentity
, jelas bahwa para desainer tidak setuju dengan saya, dan jadi saya mengakui intinya.Masalahnya di sini adalah:
Entah Anda telah mengatur keamanan Anda dengan benar dalam hal mana metode istimewa harus memeriksa apakah penelepon memiliki otoritas yang memadai - dalam hal ini cek itu berlebihan. Atau Anda belum dan mempercayai kelas yang tidak memiliki hak istimewa untuk membuat keputusan sendiri tentang keamanan.
sumber
User.IsAdmin
khusus masalah.