Web MVC uygulamama bir Erişim Kontrol Listesini nasıl uygulayabilirim?


96

İlk soru

Lütfen bana MVC'de en basit ACL'nin nasıl uygulanabileceğini açıklar mısınız?

İşte Acl'yi Controller'da kullanmanın ilk yaklaşımı ...

<?php
class MyController extends Controller {

  public function myMethod() {        
    //It is just abstract code
    $acl = new Acl();
    $acl->setController('MyController');
    $acl->setMethod('myMethod');
    $acl->getRole();
    if (!$acl->allowed()) die("You're not allowed to do it!");
    ...    
  }

}
?>

Bu çok kötü bir yaklaşım ve eksi, her denetleyicinin yöntemine Acl kod parçası eklememiz gerekmesi, ancak herhangi bir ek bağımlılığa ihtiyacımız yok!

Sonraki yaklaşım, kontrolörün tüm yöntemlerini yapmak privateve kontrolörün __callyöntemine ACL kodunu eklemektir .

<?php
class MyController extends Controller {

  private function myMethod() {
    ...
  }

  public function __call($name, $params) {
    //It is just abstract code
    $acl = new Acl();
    $acl->setController(__CLASS__);
    $acl->setMethod($name);
    $acl->getRole();
    if (!$acl->allowed()) die("You're not allowed to do it!");
    ...   
  }

}
?>

Önceki koddan daha iyidir, ancak ana eksiler ...

  • Tüm denetleyicinin yöntemleri özel olmalıdır
  • Her denetleyicinin __call yöntemine ACL kodu eklemeliyiz.

Bir sonraki yaklaşım, Acl kodunu ana Denetleyiciye koymaktır, ancak yine de tüm alt denetleyicinin yöntemlerini gizli tutmamız gerekir.

Çözüm nedir? Ve en iyi uygulama nedir? Yöntemin çalıştırılmasına izin vermek veya vermemek için Acl işlevlerini nerede çağırmalıyım?

İkinci soru

İkinci soru, Acl kullanarak rol almakla ilgilidir. Misafirlerimiz, kullanıcılarımız ve kullanıcı arkadaşlarımız olduğunu düşünelim. Kullanıcı, profilini yalnızca arkadaşlarının görüntüleyebileceği şekilde görüntüleme kısıtlamasına sahiptir. Tüm misafirler bu kullanıcının profilini görüntüleyemez. İşte mantık şu ...

  • çağrılan yöntemin profil olduğundan emin olmalıyız
  • bu profilin sahibini tespit etmeliyiz
  • tespit etmeliyiz görüntüleyen bu profilin sahibi mi yoksa hayır mı
  • bu profille ilgili kısıtlama kurallarını okumalıyız
  • profil yöntemini yürütmek veya yürütmemek için karar vermeliyiz

Asıl soru, profil sahibinin tespit edilmesiyle ilgilidir. Profil sahibinin kim olduğunu sadece modelin $ model-> getOwner () yöntemini çalıştırarak tespit edebiliriz, ancak Acl'nin modele erişimi yoktur. Bunu nasıl uygulayabiliriz?

Umarım düşüncelerim nettir. İngilizcem için üzgünüm.

Teşekkür ederim.


1
Kullanıcı etkileşimleri için neden "Erişim kontrol listelerine" ihtiyaç duyduğunuzu bile anlamıyorum. if($user->hasFriend($other_user) || $other_user->profileIsPublic()) $other_user->renderProfile()Şöyle bir şey söylemez miydiniz (aksi takdirde, "Bu kullanıcının profiline erişiminiz yok" mu yoksa bunun gibi bir şey mi?
Anlamıyorum

2
Muhtemelen, çünkü Kirzilla erişim için tüm koşulları tek bir yerden yönetmek istiyor - özellikle yapılandırmada. Bu nedenle, izinlerde herhangi bir değişiklik kodu değiştirmek yerine Yönetici'de yapılabilir.
Mariyo

Yanıtlar:


185

İlk bölüm / cevap (EKL uygulaması)

Benim naçizane görüşüme göre, buna en iyi yaklaşım dekoratör kalıbı kullanmaktır , Temel olarak bu, nesnenizi alıp koruyucu bir kabuk gibi davranacak başka bir nesnenin içine yerleştirmeniz anlamına gelir . Bu, orijinal sınıfı genişletmenizi GEREKMEZ. İşte bir örnek:

class SecureContainer
{

    protected $target = null;
    protected $acl = null;

    public function __construct( $target, $acl )
    {
        $this->target = $target;
        $this->acl = $acl;
    }

    public function __call( $method, $arguments )
    {
        if ( 
             method_exists( $this->target, $method )
          && $this->acl->isAllowed( get_class($this->target), $method )
        ){
            return call_user_func_array( 
                array( $this->target, $method ),
                $arguments
            );
        }
    }

}

Ve bu tür bir yapıyı şu şekilde kullanırsınız:

// assuming that you have two objects already: $currentUser and $controller
$acl = new AccessControlList( $currentUser );

$controller = new SecureContainer( $controller, $acl );
// you can execute all the methods you had in previous controller 
// only now they will be checked against ACL
$controller->actionIndex();

Fark edebileceğiniz gibi, bu çözümün birçok avantajı vardır:

  1. kapsama herhangi bir nesne üzerinde kullanılabilir, yalnızca örneklerde değil Controller
  2. yetkilendirme kontrolü hedef nesnenin dışında gerçekleşir, bu şu anlama gelir:
    • Orijinal nesne, erişim kontrolü sorumlu tutulamaz yapışan SRP
    • "izin reddedildi" mesajı aldığınızda, bir denetleyicinin içinde kilitli kalmazsınız, daha fazla seçenek
  3. bu güvenli örneği başka herhangi bir nesneye enjekte edebilirsiniz , korumayı devam ettirecektir
  4. sarın ve unut .. yapabilirsiniz taklit orijinal nesne olduğu, aynı tepki vereceğini

Ancak , bu yöntemle ilgili önemli bir sorun da vardır - güvenli nesnenin uygulanıp uygulanmadığını ve arabirimi (mevcut yöntemleri aramak için de geçerlidir) veya bazı miras zincirinin parçası olup olmadığını yerel olarak kontrol edemezsiniz.

İkinci bölüm / cevap (nesneler için RBAC)

Bu durumda bilmeniz gereken temel fark, Etki Alanı Nesnelerinizin (örneğin :)Profile kendisinin sahiple ilgili ayrıntıları içermesidir. Bu, kullanıcının erişiminin olup olmadığını (ve hangi düzeyde) kontrol etmeniz için bu satırı değiştirmeniz gerektiği anlamına gelir:

$this->acl->isAllowed( get_class($this->target), $method )

Esasen iki seçeneğiniz var:

  • ACL'ye söz konusu nesneyi sağlayın. Ancak Demeter Yasasını ihlal etmemek için dikkatli olmalısınız :

    $this->acl->isAllowed( get_class($this->target), $method )
    
  • İlgili tüm ayrıntıları isteyin ve ACL'ye yalnızca ihtiyaç duyduğu şeyi sağlayın, bu da onu biraz daha ünite testi dostu hale getirir:

    $command = array( get_class($this->target), $method );
    /* -- snip -- */
    $this->acl->isAllowed( $this->target->getPermissions(), $command )
    

Kendi uygulamanızı oluşturmanıza yardımcı olabilecek birkaç video:

Yan notlar

MVC'de Modelin ne olduğu konusunda oldukça yaygın (ve tamamen yanlış) bir anlayışa sahip görünüyorsunuz. Model bir sınıf değildir . Adlandırılmış bir sınıfınız FooBarModelveya miras alan bir şeyiniz AbstractModelvarsa, yanlış yapıyorsunuz demektir.

Uygun MVC'de Model, birçok sınıf içeren bir katmandır. Sınıfların büyük bir kısmı, sorumluluğa göre iki gruba ayrılabilir:

- Etki Alanı İş Mantığı

( daha fazlasını okuyun : burada ve burada ):

Bu sınıf grubunun örnekleri, değerlerin hesaplanmasıyla ilgilenir, farklı koşulları kontrol eder, satış kurallarını uygular ve geri kalan her şeyi "iş mantığı" olarak adlandıracağınız şeyi yapar. Verilerin nasıl depolandığı, nerede depolandığı veya ilk etapta depolamanın var olup olmadığı hakkında hiçbir fikirleri yoktur.

Etki Alanı İş nesnesi veritabanına bağlı değildir. Fatura oluştururken verilerin nereden geldiği önemli değildir. SQL'den veya uzaktaki bir REST API'sinden veya hatta bir MSWord belgesinin ekran görüntüsü olabilir. İş mantığı değişmez.

- Veri Erişimi ve Depolama

Bu sınıf grubundan yapılan örnekler bazen Veri Erişim Nesneleri olarak adlandırılır. Genellikle Veri Eşleyici modelini uygulayan yapılar (aynı isimli ORM'lerle karıştırmayın .. ilişki yok). Bu, SQL ifadelerinizin (veya belki DomDocument'iniz, çünkü XML'de depoladığınız için) olacağı yerdir.

İki ana bölümün yanında, belirtilmesi gereken bir grup örnek / sınıf daha vardır:

- Hizmetler

Bu, sizin ve üçüncü taraf bileşenlerinizin devreye girdiği yerdir. Örneğin, "kimlik doğrulama" yı sizin tarafınızdan veya bazı harici kodlar tarafından sağlanabilecek bir hizmet olarak düşünebilirsiniz. Ayrıca "posta gönderen", bazı etki alanı nesnelerini bir PHPMailer veya SwiftMailer ile veya kendi posta gönderen bileşeninizle birleştirebilen bir hizmet olabilir.

Başka kaynak hizmetleri alanı ve veri erişim katmanları üzerine soyutlamadır. Denetleyiciler tarafından kullanılan kodu basitleştirmek için oluşturulurlar. Örneğin: yeni kullanıcı hesabı oluşturmak, birkaç etki alanı nesnesi ve eşleyici ile çalışmayı gerektirebilir . Ancak, bir hizmet kullanarak, denetleyicide yalnızca bir veya iki hatta ihtiyaç duyacaktır.

Hizmet verirken hatırlamanız gereken şey, tüm katmanın ince olması gerektiğidir . Hizmetlerde iş mantığı yoktur. Yalnızca etki alanı nesnesini, bileşenlerini ve eşleyicileri kontrol etmek için oradalar.

Hepsinin ortak yönlerinden biri, hizmetlerin View katmanını doğrudan herhangi bir şekilde etkilememesi ve MVC yapısının dışında kullanılabilecek (ve sıklıkla bırakılabilecek) ölçüde özerk olmasıdır. Ayrıca bu tür kendi kendini idame ettiren yapılar, hizmet ve uygulamanın geri kalanı arasındaki son derece düşük bağlantı nedeniyle farklı bir çerçeveye / mimariye geçişi çok daha kolay hale getirir.


34
Bunu tekrar okuyarak 5 dakika içinde aylardır öğrendiğimden daha fazlasını öğrendim. Şunlara katılıyor musunuz: ince denetleyiciler, görüntüleme verilerini toplayan hizmetlere gönderilir? Ayrıca, soruları doğrudan kabul ederseniz, lütfen bana bir mesaj gönderin.
Stephane

2
Kısmen katılıyorum. Görünümden veri toplama, Requestörneği (veya bunun bir benzerini) başlattığınızda MVC triadının dışında gerçekleşir . Denetleyici yalnızca Requestörnekten veri alır ve çoğunu uygun hizmetlere aktarır (bazıları da görüntülemeye gider). Hizmetler, yapmalarını istediğiniz işlemleri gerçekleştirir. Daha sonra, görünüm yanıtı oluştururken, hizmetlerden veri ister ve bu bilgilere dayanarak yanıtı oluşturur. Söz konusu yanıt, birden çok şablondan yapılmış HTML veya yalnızca bir HTTP konum başlığı olabilir. Kontrolör tarafından belirlenen duruma bağlıdır.
tereško

4
Basitleştirilmiş bir açıklama kullanmak için: kontrolör modellemek ve görüntülemek için "yazar", modelden "okumaları" görüntüleyin. Model katmanı, MVC'den esinlenen Web ile ilgili tüm desenlerdeki pasif yapıdır.
tereško

@Stephane, direk soru sormak için bana her zaman twitter üzerinden mesaj gönderebilirsiniz. Yoksa 140 karakterle doldurulamayacak bir "uzun form" mu sorguladınız?
tereško

Modelden okuyor: Bu, model için bazı aktif rollerin olması anlamına mı geliyor? Bunu daha önce hiç duymadım. Tercihiniz buysa, size her zaman twitter üzerinden bir bağlantı gönderebilirim. Gördüğünüz gibi bu tepkiler hızla sohbetlere dönüşüyor ve ben bu siteye ve twitter takipçilerinize saygılı olmaya çalışıyordum.
Stephane

16

ACL ve Denetleyiciler

Her şeyden önce: Bunlar çoğunlukla farklı şeyler / katmanlar. Örnek denetleyici kodunu eleştirirken, her ikisini de bir araya getiriyor - en açık şekilde çok sıkı.

tereško , bunu dekoratör modeliyle nasıl daha fazla ayrıştırabileceğinizi zaten özetledi .

Önce bir adım geriye gidip karşılaştığınız asıl sorunu arar ve sonra bunu biraz tartışırdım.

Bir yandan, kendilerine komut verilen işi yapan denetleyicilere sahip olmak istiyorsunuz (komut veya eylem, hadi ona komut diyelim).

Öte yandan, uygulamanıza ACL koyabilmek istiyorsunuz. Bu ACL'lerin çalışma alanı, sorunuzu doğru anladıysam, uygulamalarınızın belirli komutlarına erişimi kontrol etmek olmalıdır.

Bu nedenle bu tür bir erişim kontrolü, bu ikisini bir araya getiren başka bir şeye ihtiyaç duyar. Bir komutun içinde yürütüldüğü bağlama bağlı olarak, ACL devreye girer ve belirli bir komutun belirli bir konu (örneğin kullanıcı) tarafından yürütülüp yürütülmeyeceğine karar verilmesi gerekir.

Bu noktaya sahip olduğumuz şeyi özetleyelim:

  • Komut
  • EKL
  • Kullanıcı

ACL bileşeni burada merkezidir: En azından komut hakkında bir şeyler bilmesi (komutu kesin olarak tanımlamak için) ve kullanıcıyı tanımlayabilmesi gerekir. Kullanıcılar normalde benzersiz bir kimlikle kolayca tanımlanır. Ancak genellikle web uygulamalarında hiç tanımlanmayan, genellikle konuk, anonim, herkes vb. Olarak adlandırılan kullanıcılar vardır. Bu örnek için, ACL'nin bir kullanıcı nesnesini tüketebileceğini ve bu ayrıntıları saklayabileceğini varsayıyoruz. Kullanıcı nesnesi, uygulama istek nesnesine bağlıdır ve EKL bunu kullanabilir.

Bir komutu tanımlamaya ne dersiniz? MVC desenini yorumlamanız, bir komutun bir sınıf adı ve bir yöntem adından oluşan bir bileşik olduğunu gösterir. Daha yakından bakarsak, bir komut için argümanlar (parametreler) bile vardır. Yani bir komutu tam olarak neyin tanımladığını sormak geçerli mi? Sınıf adı, yöntem adı, bağımsız değişkenlerin sayısı veya adları, hatta herhangi bir bağımsız değişken içindeki veriler veya bunların bir karışımı mı?

ACL'lemenizdeki bir komutu tanımlamanız gereken ayrıntı düzeyine bağlı olarak, bu çok değişebilir. Örnek için basit tutalım ve bir komutun sınıf adı ve yöntem adı ile tanımlandığını belirtelim.

Dolayısıyla, bu üç parçanın (ACL, Komut ve Kullanıcı) birbirine nasıl ait olduğu bağlamı artık daha net.

Hayali bir ACL bileşeni ile aşağıdakileri yapabiliriz diyebiliriz:

$acl->commandAllowedForUser($command, $user);

Sadece burada neler olduğuna bakın: Hem komutu hem de kullanıcıyı tanımlanabilir hale getirerek, ACL işini yapabilir. ACL'nin işi, hem kullanıcı nesnesinin hem de somut komutun çalışmasıyla ilgisizdir.

Eksik olan tek bir parça var, bu havada yaşayamaz. Ve öyle değil. Bu nedenle, erişim kontrolünün devreye girmesi gereken yeri bulmanız gerekiyor. Standart bir web uygulamasında neler olduğuna bir göz atalım:

User -> Browser -> Request (HTTP)
   -> Request (Command) -> Action (Command) -> Response (Command) 
   -> Response(HTTP) -> Browser -> User

Bu yeri bulmak için, somut komutun yerine getirilmesinden önce olması gerektiğini biliyoruz, bu yüzden bu listeyi azaltabiliriz ve yalnızca aşağıdaki (potansiyel) yerlere bakmamız gerekir:

User -> Browser -> Request (HTTP)
   -> Request (Command)

Uygulamanızın bir noktasında, belirli bir kullanıcının somut bir komut gerçekleştirmeyi talep ettiğini biliyorsunuz. Burada zaten bir çeşit ACL işlemi yapıyorsunuz: Bir kullanıcı mevcut olmayan bir komut isterse, bu komutun yürütülmesine izin vermezsiniz. Dolayısıyla, uygulamanızda bu nerede olursa olsun, "gerçek" EKL kontrollerini eklemek için iyi bir yer olabilir:

Komutun yeri belirlendi ve ACL'nin onunla başa çıkabilmesi için onun kimliğini oluşturabiliriz. Bir kullanıcı için komuta izin verilmemesi durumunda, komut çalıştırılmayacaktır (eylem). Belki dava için a CommandNotAllowedResponseyerine CommandNotFoundResponsesomut bir emir üzerine bir talep çözümlenememiştir.

Somut bir HTTPRequest'in eşlemesinin bir komutla eşlendiği yere genellikle Yönlendirme denir . As Yönlendirme zaten komutunu bulmak için işi var, neden komut aslında ACL başına izin verildiği takdirde bu kontrol etmek uzatmak? Uzatarak Örneğin Router bir ACL farkında yönlendiriciye: RouterACL. Yönlendiriciniz henüz bilmiyorsa User, o zaman Routerdoğru yer değildir, çünkü ACL'nin çalışabilmesi için yalnızca komutun değil, kullanıcının da tanımlanması gerekir. Yani burası değişebilir, ancak genişletmeniz gereken yeri kolayca bulabileceğinizden eminim, çünkü burası kullanıcı ve komut gereksinimlerini karşılayan yerdir:

User -> Browser -> Request (HTTP)
   -> Request (Command)

Kullanıcı başından beri kullanılabilir, Komut önce ile Request(Command).

Bu nedenle, ACL kontrollerinizi her komutun somut uygulamasına koymak yerine, önüne yerleştirirsiniz. Herhangi bir ağır modele, sihre ya da her neyse, ACL işini yapar, kullanıcı işini yapar ve özellikle komut işini yapar: Sadece komut, başka bir şey değil. Komutun, bir yerde korunup korunmadığını, rollerin kendisine uygulanıp uygulanmayacağını bilmekle hiçbir ilgisi yoktur.

Bu yüzden sadece birbirine ait olmayan şeyleri ayrı tutun. Tek Sorumluluk İlkesinin (SRP) biraz farklı bir ifadesini kullanın : Bir komutu değiştirmenin tek bir nedeni olmalıdır - çünkü komut değişmiştir. Şimdi başvurunuzda ACL'ing'i tanıttığınız için değil. Kullanıcı nesnesini değiştirdiğiniz için değil. Bir HTTP / HTML arayüzünden bir SOAP veya komut satırı arayüzüne geçiş yaptığınız için değil.

Sizin durumunuzdaki ACL, komutun kendisine değil bir komuta erişimi kontrol eder.


İki soru: CommandNotFoundResponse ve CommandNotAllowedResponse: bunları ACL sınıfından Yönlendiriciye veya Denetleyiciye geçirip evrensel bir yanıt bekler miydiniz? 2: Yöntem + nitelikleri dahil etmek isteseydiniz, bununla nasıl başa çıkardınız?
Stephane

1: Yanıt yanıttır, burada ACL'den değil, yönlendiriciden gelen ACL, yönlendiricinin yanıt türünü bulmasına yardımcı olur (özellikle bulunamadı: yasak). 2: Bağlıdır. Öznitelikleri eylemlerin parametreleri olarak kastediyorsanız ve parametrelerle EKL oluşturmaya ihtiyacınız varsa, bunları EKL'nin altına koyun.
hakre

13

Bir olasılık, tüm denetleyicilerinizi Denetleyiciyi genişleten başka bir sınıfa sarmak ve yetkilendirmeyi kontrol ettikten sonra sarılmış örneğe tüm işlev çağrılarını delege etmesini sağlamaktır.

Ayrıca, dağıtım programında (uygulamanızda gerçekten bir tane varsa) daha yukarı akış yapabilir ve izinleri kontrol yöntemleri yerine URL'lere göre arayabilirsiniz.

edit : Bir veritabanına, bir LDAP sunucusuna, vb. erişmeniz gerekip gerekmediği, soruya ortogonaldir. Demek istediğim, denetleyici yöntemleri yerine URL'lere dayalı bir yetkilendirme uygulayabilmenizdi. Bunlar daha sağlamdır çünkü tipik olarak URL'lerinizi değiştirmeyeceksiniz (URL'ler genel arayüz türü), ancak denetleyicilerinizin uygulamalarını da değiştirebilirsiniz.

Genellikle, belirli URL modellerini belirli kimlik doğrulama yöntemleri ve yetkilendirme yönergeleriyle eşlediğiniz bir veya birkaç yapılandırma dosyanız vardır. Sevk görevlisi, talebi kontrolörlere göndermeden önce, kullanıcının yetkili olup olmadığını belirler ve değilse gönderimi iptal eder.


Lütfen yanıtınızı güncelleyip Dispatcher hakkında daha fazla ayrıntı ekleyebilir misiniz? Göndericim var - URL ile hangi denetleyicinin yöntemini çağırmam gerektiğini algılıyor. Ancak Dispatcher'da nasıl rol alabilirim (bunu yapmak için DB'ye erişmem gerekiyor) anlayamıyorum. Yakında duymayı umuyoruz.
Kirzilla

Aha, fikrini anladım. Yönteme erişmeden yürütmeye izin verip vermemeye karar vermeliyim! Başparmak havaya! Çözülmemiş son soru - Acl'den modele nasıl erişilir. Herhangi bir fikir?
Kirzilla

@Kirzilla Denetleyicilerle aynı sorunları yaşıyorum. Görünüşe göre bağımlılıklar orada bir yerde olmalı. ACL olmasa bile, model katmanı ne olacak? Bunun bağımlılık olmasını nasıl önleyebilirsin?
Stephane
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.