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


96

最初の質問

簡単なACLをMVCに実装する方法を教えてください。

コントローラで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にACLコードを追加する__callことです。

<?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番目の質問は、Aclを使用して役割を取得することです。ゲスト、ユーザー、ユーザーの友達がいるとします。ユーザーは自分のプロファイルの表示へのアクセスを制限されており、友達だけが表示できます。すべてのゲストはこのユーザーのプロフィールを表示できません。それで、これが論理です。

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

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

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

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


1
ユーザーとのやり取りに「アクセス制御リスト」が必要になる理由もわかりません。あなただけのようなものを言わないだろうif($user->hasFriend($other_user) || $other_user->profileIsPublic()) $other_user->renderProfile()(そうでないと、ディスプレイはそのようなか何か「あなたはこのユーザーのプロフィールへのアクセスを持っていない」私はそれを得ることはありません?。
バトルバトカス

2
おそらく、Kirzillaは主に構成で、1か所でアクセスのすべての条件を管理する必要があるためです。したがって、アクセス許可の変更は、コードを変更する代わりに、管理者で行うことができます。
Mariyo 2016

回答:


185

最初の部分/回答(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. この保護されたインスタンスを他のオブジェクトに挿入できます。保護が保持されます
  4. ラップして忘れる.. 元のオブジェクトのふりをして、同じように反応します

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

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

この場合、認識すべき主な違いは、ドメインオブジェクト(例:)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つのグループに分けることができます。

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

続きを読むここここ):

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

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

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

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

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

- サービス

ここで、サードパーティのコンポーネントが登場します。たとえば、「認証」は、独自のサービスまたは外部コードによって提供されるサービスと考えることができます。また、「メール送信者」は、PHPMailerやSwiftMailer、または独自のメール送信コンポーネントを使用してドメインオブジェクトを作成するサービスです。

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

サービスを作成するときに覚えておくべきことは、層全体が薄いと想定されているということです。サービスにはビジネスロジックはありません。それらは、ドメインオブジェクト、コンポーネント、マッパーを操作するためだけに存在します。

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


34
5分間でこれを読み直すことで、数か月に比べて多くのことを学びました。同意しますか:ビューデータを収集するサービスにシンコントローラーがディスパッチしますか?また、直接質問を受け付ける場合は、メッセージを送ってください。
ステファン

2
部分的に同意します ビューRequest(またはその類似物)を初期化すると、ビューからのデータの収集はMVCトライアドの外部で行われます。コントローラーはRequestインスタンスからデータを抽出するだけで、そのほとんどを適切なサービスに渡します(一部は表示にも行きます)。サービスは、ユーザーが指示した操作を実行します。次に、ビューが応答を生成しているときに、ビューはサービスにデータを要求し、その情報に基づいて応答を生成します。この応答は、複数のテンプレートから作成されたHTMLまたは単なるHTTPロケーションヘッダーのいずれかです。コントローラによって設定された状態に依存します。
tereško

4
簡単な説明を使用するには、コントローラーがモデルとビューに「書き込み」、モデルから「読み取り」を表示します。モデルレイヤーは、MVCに触発されたすべてのWeb関連パターンのパッシブ構造です。
テレシュコ2012

@Stephane、直接質問する場合は、Twitterでいつでも私にメッセージを送ることができます。それとも、140文字で詰め込めない、ちょっとした「長い形式」に質問しましたか?
テレシュコ2012

モデルからの読み取り:それはモデルの積極的な役割を意味しますか?聞いたことがない。それがあなたの好みであれば、私はいつでもツイッターを介してあなたにリンクを送ることができます。ご覧のとおり、これらの応答はすぐに会話に変わり、私はこのサイトとあなたのTwitterフォロワーを尊重するように努めていました。
ステファン

16

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のマッピングがコマンドにマッピングされる場所は、しばしばルーティングと呼ばれます。ルーティングがすでにコマンドを見つけるために仕事をしている、なぜコマンドが実際にACLごとに許可されるかどうかを確認するためにそれを拡張しませんか?たとえばRouter 、をACL対応ルーターに拡張することにより、次のようになりますRouterACL。お使いのルーターがまだ知らない場合User、その後、RouterACL'ingは、コマンドだけでなく、利用者が特定されなければならないだけを動作させるためにあるため、適切な場所ではありません。したがって、この場所はさまざまですが、拡張する必要がある場所を簡単に見つけることができると思います。これは、ユーザーとコマンドの要件を満たす場所だからです。

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

ユーザーは最初から利用できますRequest(Command)。最初にCommandを使用します。

したがって、コマンドの具体的な実装内にACLチェックを配置する代わりに、ACLチェックをその前に配置します。特別なパターンや魔法などは必要ありません。ACLがその役目を果たし、ユーザーがその役目を果たし、特にコマンドがその役目を果たします。コマンドだけです。コマンドは、どこかに保護されているかどうかに関係なく、ロールが適用されるかどうかを知る必要はありません。

したがって、互いに属していないものを分けてください。単一責任原則(SRP)を少し言い換えてください。コマンドが変更されたため、コマンドを変更する理由は1つだけです。アプリケーションにACLを導入したからではありません。ユーザーオブジェクトを切り替えたからではありません。HTTP / HTMLインターフェイスからSOAPまたはコマンドラインインターフェイスに移行するためではありません。

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


2つの質問:CommandNotFoundResponseとCommandNotAllowedResponse:これらをACLクラスからルーターまたはコントローラーに渡して、汎用的な応答を期待しますか?2:メソッド+属性を含める場合、どのように処理しますか?
ステファン

1:応答は応答です。ここではACLからではなく、ルーターからです。ACLは、ルーターが応答タイプを見つけるのに役立ちます(特に見つかりません:禁止)。2:依存します。アクションのパラメータとして属性を意味し、パラメータでACLを実行する必要がある場合は、それらをACLの下に配置します。
hakre 2012

13

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

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

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

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


回答を更新して、Dispatcherの詳細を追加してください。私はディスパッチャーを持っています-それは私がURLで呼び出す必要のあるコントローラーのメソッドを検出します。しかし、Dispatcherでどのようにしてロールを取得できるのか(私はそれを行うためにDBにアクセスする必要があります)を理解できません。すぐにあなたの話を聞きたいです。
Kirzilla、2010

ああ、あなたの考えを得た。メソッドにアクセスせずに実行を許可するかどうかを決定する必要があります!いいぞ!最後の未解決の質問-ACLからモデルにアクセスする方法。何か案は?
Kirzilla、2010

@Kirzilla Controllersにも同じ問題があります。依存関係がどこかにある必要があるようです。ACLがそうでない場合でも、モデルレイヤーはどうですか?それが依存関係にならないようにするにはどうすればよいですか?
ステファン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.