web-dev-qa-db-ja.com

Web MVCアプリケーションにアクセス制御リストを実装するにはどうすればよいですか?

最初の質問

MVCで最もシンプルなACLを実装する方法を教えてください。

ControllerでAclを使用する最初の方法を次に示します...

<?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!");
    ...    
  }

}
?>

これは非常に悪いアプローチであり、マイナスは、Aclのコードを各コントローラーのメソッドに追加する必要があることですが、追加の依存関係は必要ありません!

次のアプローチは、すべてのコントローラーのメソッドをprivateにし、コントローラーの__callメソッドにACLコードを追加することです。

<?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!");
    ...   
  }

}
?>

以前のコードよりも優れていますが、主なマイナスは...

  • すべてのコントローラーのメソッドはプライベートである必要があります
  • 各コントローラーの__callメソッドにACLコードを追加する必要があります。

次のアプローチは、Aclコードを親コントローラーに配置することですが、子コントローラーのすべてのメソッドをプライベートに保つ必要があります。

解決策は何ですか?そして、ベストプラクティスは何ですか?メソッドの実行を許可または禁止するために、Acl関数をどこで呼び出す必要がありますか。

2番目の質問

2番目の質問は、Aclを使用して役割を取得することです。ゲスト、ユーザー、ユーザーの友人がいると想像してみましょう。ユーザーは、自分のプロフィールの表示へのアクセスを制限しており、友達のみが閲覧できます。すべてのゲストはこのユーザーのプロファイルを表示できません。それで、ここにロジックがあります。

  • 呼び出されるメソッドがプロファイルであることを確認する必要があります
  • このプロファイルの所有者を検出する必要があります
  • 視聴者がこのプロファイルの所有者であるかどうかを検出する必要があります
  • このプロファイルに関する制限ルールを読む必要があります
  • プロファイルメソッドを実行するかどうかを決定する必要があります

主な質問は、プロファイルの所有者を検出することです。モデルのメソッド$ model-> getOwner()のみを実行するプロファイルの所有者を検出できますが、Aclはモデルにアクセスできません。これをどのように実装できますか?

私の考えが明確であることを願っています。私の英語でごめんなさい。

ありがとうございました。

94
Kirzilla

最初の部分/回答(ACL実装)

私の謙虚な意見では、これにアプローチする最善の方法は、 装飾パターン を使用することです、基本的に、これはあなたがあなたのオブジェクトを取り、それを内部に置くことを意味します保護シェルのように機能するオブジェクト。これにより、元のクラスを拡張する必要がなくなります。以下に例を示します。

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
            );
        }
    }

}

そして、これはこの種の構造をどのように使用するかです:

// 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();

お気づきかもしれませんが、このソリューションにはいくつかの利点があります。

  1. 格納は、Controllerのインスタンスだけでなく、任意のオブジェクトで使用できます
  2. 許可のチェックは、ターゲットオブジェクトの外部で行われます。つまり、
    • 元のオブジェクトはアクセス制御の責任を負わず、 [〜#〜] srp [〜#〜] を順守します
    • 「許可が拒否された」場合、コントローラー内にロックされていません。より多くのオプション
  3. このsecured instanceを他のオブジェクトに挿入できます。保護を保持します
  4. それをラップして忘れてください..あなたはそれが元のオブジェクトであることをふりをすることができます、それは同じ反応をします

しかし、、このメソッドにも1つの大きな問題があります-保護されたオブジェクトの実装とインターフェースをネイティブに確認することはできません(既存のメソッドの検索にも適用されます)または、いくつかの継承チェーンの一部です。

2番目の部分/回答(オブジェクトのRBAC)

この場合、認識すべき主な違いは、Domain Objects(例:Profile)自体に所有者に関する詳細が含まれていることです。これは、ユーザーがチェックするために(そしてどのレベルで)ユーザーがアクセスできるかを確認するには、この行を変更する必要があることを意味します。

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

基本的に2つのオプションがあります。

  • 問題のオブジェクトをACLに提供します。しかし、違反しないように注意する必要があります デメテルの法則

    $this->acl->isAllowed( get_class($this->target), $method )
    
  • 関連するすべての詳細を要求し、ACLに必要なものだけを提供します。これにより、ユニットテストが少しわかりやすくなります。

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

独自の実装を思い付くのに役立つかもしれないカップルのビデオ:

サイドノート

MVCのモデルが何であるかについて、ごく一般的な(そして完全に間違った)理解を持っているようです。 モデルはクラスではありませんFooBarModelという名前のクラス、またはAbstractModelを継承するクラスがある場合、それは間違っています。

適切なMVCでは、モデルは多くのクラスを含むレイヤーです。クラスの大部分は、責任に基づいて2つのグループに分けることができます。

-ドメインビジネスロジック

続きを読むhere および here ):

このクラスのグループのインスタンスは、値の計算を処理し、さまざまな条件をチェックし、販売ルールを実装し、残りのすべてを「ビジネスロジック」と呼びます。彼らは、データがどのように保存されているのか、どこに保存されているのか、あるいはストレージが最初から存在するのかどうかさえもわかりません。

ドメインビジネスオブジェクトはデータベースに依存しません。請求書を作成する場合、データの送信元は関係ありません。 SQLまたはリモートのREST API、またはMSWordドキュメントのスクリーンショットからでも可能です。ビジネスロジックは変更されません。

-データアクセスとストレージ

このクラスのグループから作成されたインスタンスは、データアクセスオブジェクトと呼ばれることもあります。通常、 Data Mapper patternを実装する構造体(同じ名前のORMと混同しないでください。関係はありません)。これは、SQLステートメント(または、DomDocumentをXMLに保存するため)が置かれる場所です。

2つの主要な部分のほかに、インスタンス/クラスのグループがもう1つあります。

-サービス

これが、あなたとサードパーティのコンポーネントの出番です。たとえば、「認証」をサービスと考えることができます。これは、独自のコードまたは外部コードで提供できます。また、「メール送信者」は、PHPMailerまたはSwiftMailer、または独自のメール送信者コンポーネントでいくつかのドメインオブジェクトを結び付けるサービスです。

services の別のソースは、ドメインおよびデータアクセスレイヤー上の抽象化です。これらは、コントローラーが使用するコードを簡素化するために作成されます。たとえば、新しいユーザーアカウントを作成するには、いくつかのdomainオブジェクトおよびmappersを使用する必要があります。ただし、サービスを使用することで、コントローラーに必要なのは1行または2行だけです。

サービスを作成するときに覚えておく必要があるのは、レイヤー全体がthinであることになっているということです。サービスにはビジネスロジックはありません。それらは、ドメインオブジェクト、コンポーネント、マッパーをジャグリングするためだけにあります。

それらがすべて共通していることの1つは、サービスがViewレイヤーに直接的な影響を与えず、MVC構造自体の外部で使用できる(そして頻繁に終了する)程度に自律的であるということです。また、このような自立構造により、サービスとアプリケーションの他の部分との結合が非常に低いため、異なるフレームワーク/アーキテクチャへの移行がはるかに簡単になります。

182
tereško

ACLとコントローラー

まず第一に、これらは異なるもの/レイヤーであることが最も多いです。模範的なコントローラーコードを批判すると、両方が一緒になります-明らかにタイトすぎます。

既に説明したtereško デコレータパターンでこれをさらに分離する方法。

最初に一歩戻って、あなたが直面している元の問題を探し、それについて少し議論します。

一方では、命令された作業(コマンドまたはアクション、コマンドと呼びましょう)を行うだけのコントローラーが必要です。

一方、アプリケーションにACLを配置できるようにしたい場合。これらのACLの作業分野は、あなたの質問の権利を理解した場合、アプリケーションの特定のコマンドへのアクセスを制御することです。

したがって、この種のアクセス制御には、これら2つを統合する何かが必要です。コマンドが実行されるコンテキストに基づいて、特定のサブジェクト(ユーザーなど)が特定のコマンドを実行できるかどうかにかかわらず、ACLが開始され、決定を行う必要があります。

ここまでの内容をまとめましょう。

  • コマンド
  • ACL
  • ユーザー

ここではACLコンポーネントが中心となります。コマンドについて少なくとも何かを知る必要があり(正確にコマンドを識別するため)、ユーザーを識別することができる必要があります。通常、ユーザーは一意のIDで簡単に識別されます。しかし、多くの場合、Webアプリケーションには、ゲスト、匿名、全員などと呼ばれる、まったく識別されないユーザーがいます。この例では、ACLがユーザーオブジェクトを消費し、これらの詳細をカプセル化します。ユーザーオブジェクトはアプリケーション要求オブジェクトにバインドされ、ACLはそれを使用できます。

コマンドの識別はどうですか? MVCパターンの解釈は、コマンドがクラス名とメソッド名の複合であることを示唆しています。よく見ると、コマンドの引数(パラメーター)さえあります。だから、コマンドを正確に識別するものを尋ねることは有効ですか?クラス名、メソッド名、引数の数または名前、引数内のデータ、またはこれらすべての混合物ですか?

ACLでコマンドを識別するために必要な詳細レベルに応じて、これは大きく異なります。この例では、単純にしておき、コマンドがクラス名とメソッド名で識別されるように指定します。

したがって、これら3つの部分(ACL、コマンド、およびユーザー)が互いにどのように属しているかのコンテキストは、より明確になりました。

想像上のACLコンポーネントを使用して、次のことを既に実行できると言えます。

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

コマンドとユーザーの両方を識別可能にすることで、ACLが機能します。 ACLのジョブは、ユーザーオブジェクトと具象コマンドの両方の作業とは無関係です。

不足している部分が1つだけあります。これは空中で生きることはできません。そして、そうではありません。そのため、アクセス制御を開始する必要がある場所を見つける必要があります。標準のWebアプリケーションで何が起こるか見てみましょう。

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

その場所を見つけるには、具体的なコマンドが実行される前でなければならないことがわかっているため、そのリストを縮小し、次の(潜在的な)場所のみを調べる必要があります。

_User -> Browser -> Request (HTTP)
   -> Request (Command)
_

アプリケーションのある時点で、特定のユーザーが具体的なコマンドの実行を要求したことがわかります。ここで既に何らかのACLを実行しています。ユーザーが存在しないコマンドを要求した場合、そのコマンドの実行を許可しません。したがって、アプリケーションでそれが発生する場合はどこでも、「実際の」ACLチェックを追加するのに適した場所です。

コマンドが見つかったため、ACLで処理できるようにコマンドのIDを作成できます。コマンドがユーザーに許可されていない場合、コマンドは実行されません(アクション)。リクエストを具体的なコマンドに解決できなかった場合、CommandNotAllowedResponseの代わりにCommandNotFoundResponseである可能性があります。

具体的なHTTPRequestのマッピングがコマンドにマッピングされる場所は、多くの場合Routingと呼ばれます。 Routingには既にコマンドを見つけるジョブがあるので、ACLごとにコマンドが実際に許可されているかどうかを確認するためにそれを拡張してみませんか?例えば。 RouterをACL対応ルーターに拡張することにより:RouterACL。ルーターがUserをまだ知らない場合、Routerは適切な場所ではありません。ACLが機能するためには、コマンドだけでなくユーザーも識別する必要があるためです。この場所はさまざまですが、ユーザーとコマンドの要件を満たす場所であるため、拡張する必要がある場所を簡単に見つけることができると確信しています。

_User -> Browser -> Request (HTTP)
   -> Request (Command)
_

ユーザーは最初から使用可能です。コマンドは最初にRequest(Command)を使用します。

そのため、ACLチェックをeachコマンドの具体的な実装の代わりに、その前に配置します。重いパターン、魔法など何も必要ありません。ACLが仕事をし、ユーザーが仕事をし、特にコマンドが仕事をします。コマンドだけで、それ以外は何もしません。コマンドは、どこかにガードされているかどうかに関係なく、役割が適用されるかどうかを知ることに関心がありません。

だから、お互いに属していないものをバラバラにしてください。 Single Responsibility Principle(SRP)のわずかな言い回しを使用してください:コマンドを変更する理由は1つだけです(コマンドが変更されたため)。アプリケーションにACLを導入したからではありません。ユーザーオブジェクトを切り替えるためではありません。 HTTP/HTMLインターフェースからSOAPまたはコマンドラインインターフェースに移行するためではありません。

この場合のACLは、コマンド自体ではなく、コマンドへのアクセスを制御します。

16
hakre

1つの可能性は、コントローラーを拡張する別のクラスですべてのコントローラーをラップし、承認を確認した後、ラップされたインスタンスにすべての関数呼び出しを委任することです。

また、より上流のディスパッチャ(アプリケーションに実際にある場合)で実行し、制御メソッドの代わりにURLに基​​づいてアクセス許可を検索することもできます。

編集:データベース、LDAPサーバーなどにアクセスする必要があるかどうかは、質問とは正反対です。私のポイントは、コントローラーメソッドの代わりにURLに基​​づいて承認を実装できることです。通常はURL(URLエリアの種類のパブリックインターフェイス)を変更しないため、これらはより堅牢ですが、コントローラーの実装も変更する可能性があります。

通常、特定のURLパターンを特定の認証方法と許可ディレクティブにマップする1つまたは複数の構成ファイルがあります。ディスパッチャは、リクエストをコントローラにディスパッチする前に、ユーザーが許可されているかどうかを判断し、許可されていない場合はディスパッチを中止します。

13
Artefacto