웹 MVC 애플리케이션에서 액세스 제어 목록을 어떻게 구현할 수 있습니까?


96

첫 번째 질문

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에 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 함수를 어디에서 호출해야합니까?

두 번째 질문

두 번째 질문은 Acl을 사용하여 역할을 얻는 것입니다. 손님, 사용자 및 사용자의 친구가 있다고 가정 해 봅시다. 사용자는 친구 만 볼 수있는 프로필보기에 대한 액세스를 제한했습니다. 모든 참석자가이 사용자의 프로필을 볼 수 없습니다. 그래서 여기에 논리가 있습니다 ..

  • 호출되는 메서드가 프로필인지 확인해야합니다.
  • 이 프로필의 소유자를 감지해야합니다.
  • 시청자가이 프로필의 소유자인지 아닌지 확인해야합니다.
  • 이 프로필에 대한 제한 규칙을 읽어야합니다.
  • 프로파일 메소드 실행 여부를 결정해야합니다

주요 질문은 프로필 소유자를 감지하는 것입니다. 우리는 모델의 $ model-> getOwner () 메소드를 실행하는 것만으로 프로필의 소유자를 감지 할 수 있지만 Acl은 모델에 대한 액세스 권한이 없습니다. 이것을 어떻게 구현할 수 있습니까?

내 생각이 분명하기를 바랍니다. 내 영어에 대해 죄송합니다.

감사합니다.


1
사용자 상호 작용을 위해 "액세스 제어 목록"이 필요한 이유도 이해하지 못합니다. 그냥 if($user->hasFriend($other_user) || $other_user->profileIsPublic()) $other_user->renderProfile()"이 사용자의 프로필에 액세스 할 수 없습니다."또는 이와 비슷한 것을 표시하지 않겠습니까? 모르겠습니다.
Buttle Butkus 2014

2
아마도 Kirzilla는 주로 구성에서 액세스를위한 모든 조건을 한 곳에서 관리하기를 원하기 때문일 것입니다. 따라서 코드를 변경하는 대신 관리자에서 권한을 변경할 수 있습니다.
Mariyo

답변:


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. 그것을 감싸고 잊어 버리십시오. .. 당신은 그것이 원래의 물건 인 할 수 있습니다 , 그것은 동일하게 반응 할 것입니다

그러나이 방법에도 한 가지 중요한 문제가 있습니다. 보안 된 객체가 구현 및 인터페이스 (기존 메서드 검색에도 적용됨)인지 또는 일부 상속 체인의 일부인지 기본적으로 확인할 수 없습니다.

두 번째 부분 / 답변 (객체에 대한 RBAC)

이 경우 인식해야 할 주요 차이점은 도메인 개체 (예 Profile:) 자체에 소유자에 대한 세부 정보가 포함되어 있다는 것입니다. 즉, 사용자가 어떤 수준에서 액세스 할 수 있는지 확인하려면 다음 행을 변경해야합니다.

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

기본적으로 두 가지 옵션이 있습니다.

  • 문제의 객체와 함께 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에서 모델은 많은 클래스를 포함하는 계층입니다. 클래스의 대부분은 책임에 따라 두 그룹으로 나눌 수 있습니다.

- 도메인 비즈니스 로직

( 더 읽기 : 여기여기 ) :

이 클래스 그룹의 인스턴스는 값 계산을 처리하고, 다른 조건을 확인하고, 판매 규칙을 구현하고, "비즈니스 논리"라고 부르는 나머지 모든 작업을 수행합니다. 그들은 데이터가 저장되는 방법, 저장 위치 또는 스토리지가 처음에 존재하는 경우에도 단서가 없습니다.

도메인 비즈니스 객체는 데이터베이스에 의존하지 않습니다. 송장을 생성 할 때 데이터의 출처는 중요하지 않습니다. SQL 또는 원격 REST API 또는 MSWord 문서의 스크린 샷일 수 있습니다. 비즈니스 로직은 변경되지 않습니다.

- 데이터 액세스 및 저장

이 클래스 그룹에서 만든 인스턴스를 데이터 액세스 개체라고도합니다. 일반적으로 데이터 매퍼 패턴 을 구현하는 구조 (동일한 이름의 ORM과 혼동하지 마십시오 .. 관계 없음). 이것은 SQL 문이있는 곳입니다 (또는 XML로 저장하기 때문에 DomDocument).

두 가지 주요 부분 외에도 언급해야 할 인스턴스 / 클래스 그룹이 하나 더 있습니다.

- 서비스

여기에서 귀하 및 타사 구성 요소가 작동합니다. 예를 들어, "인증"을 서비스로 생각할 수 있으며, 이는 사용자 자신 또는 일부 외부 코드에서 제공 할 수 있습니다. 또한 "메일 발신자"는 PHPMailer 또는 SwiftMailer 또는 자체 메일 발신자 구성 요소와 함께 일부 도메인 개체를 결합 할 수있는 서비스입니다.

또 다른 서비스 소스는 도메인 및 데이터 액세스 계층에 대한 추상화입니다. 컨트롤러에서 사용하는 코드를 단순화하기 위해 만들어졌습니다. 예 : 새 사용자 계정을 만들려면 여러 도메인 개체매퍼를 사용해야 할 수 있습니다 . 그러나 서비스를 사용하면 컨트롤러에서 한두 줄만 필요합니다.

서비스를 만들 때 기억해야 할 것은 전체 레이어가 얇아 야 한다는 것 입니다. 서비스에는 비즈니스 로직이 없습니다. 도메인 개체, 구성 요소 및 매퍼를 조정하기 위해서만 존재합니다.

그들이 공통적으로 가지고있는 것 중 하나는 서비스가 어떤 직접적인 방식으로도 뷰 레이어에 영향을주지 않고 MVC 구조 자체 외부에서 사용될 수 있고 자주 종료 될 수있을 정도로 자율적이라는 것입니다. 또한 이러한 자체 유지 구조는 서비스와 나머지 애플리케이션 간의 결합이 극히 낮기 때문에 다른 프레임 워크 / 아키텍처로의 마이그레이션을 훨씬 쉽게 만듭니다.


34
나는 이것을 몇 달 동안 읽은 것보다 5 분 만에 더 많이 배웠다. 동의하십니까? 씬 컨트롤러가 뷰 데이터를 수집하는 서비스에 파견합니까? 또한 직접 질문을 받으시면 저에게 메시지를 보내주세요.
Stephane

2
부분적으로 동의합니다. 보기에서 데이터 수집은 Request인스턴스 (또는 일부 유사) 를 초기화 할 때 MVC 트라이어드 외부에서 발생합니다 . 컨트롤러는 Request인스턴스 에서 데이터 만 추출 하고 대부분의 데이터 를 적절한 서비스로 전달합니다 (일부는보기로 이동합니다). 서비스는 사용자가 명령 한 작업을 수행합니다. 그런 다음 뷰가 응답을 생성 할 때 서비스에서 데이터를 요청하고 해당 정보를 기반으로 응답을 생성합니다. 응답은 여러 템플릿으로 만든 HTML이거나 HTTP 위치 헤더 일 수 있습니다. 컨트롤러에서 설정 한 상태에 따라 다릅니다.
tereško

4
간단한 설명을 사용하려면 컨트롤러가 모델 및보기에 "쓰기"하고 모델에서 "읽기"를 봅니다. 모델 계층은 MVC에서 영감을 얻은 모든 웹 관련 패턴의 수동 구조입니다.
tereško 2012 년

@Stephane, 직접 질문하는 경우 언제든지 트위터에서 저에게 메시지를 보낼 수 있습니다. 아니면 140 자 안에 넣을 수없는 "긴 형식"에 대한 질문입니까?
tereško

모델에서 읽음 : 모델의 적극적인 역할을 의미합니까? 나는 전에 들어 본 적이 없습니다. 원하는 경우 언제든지 트위터를 통해 링크를 보낼 수 있습니다. 보시다시피 이러한 응답은 빠르게 대화로 바뀌고이 사이트와 귀하의 트위터 팔로워를 존중하려고 노력했습니다.
스테판

16

ACL 및 컨트롤러

우선 : 이것들은 가장 자주 다른 것 / 레이어입니다. 예시적인 컨트롤러 코드를 비판 할 때 두 가지를 모두 결합합니다. 가장 명백하게 너무 빡빡합니다.

tereško 는 데코레이터 패턴으로 이것을 더 분리 할 수있는 방법을 이미 설명 했습니다.

나는 당신이 직면하고있는 원래의 문제를 찾기 위해 먼저 한 걸음 뒤로 가서 그것에 대해 조금 논의 할 것입니다.

한편으로 당신은 그들이 명령받은 작업을 수행하는 컨트롤러를 원합니다 (명령 또는 행동, 그것을 명령이라고합시다).

반면에 애플리케이션에 ACL을 넣을 수 있기를 원합니다. 이러한 ACL의 작업 분야는 귀하의 질문 권한을 이해 한 경우 애플리케이션의 특정 명령에 대한 액세스를 제어하는 ​​것이어야합니다.

따라서 이러한 종류의 액세스 제어에는이 두 가지를 결합하는 다른 것이 필요합니다. 명령이 실행되는 컨텍스트에 따라 ACL이 시작되고 특정 명령이 특정 주체 (예 : 사용자)에 의해 실행될 수 있는지 여부를 결정해야합니다.

이 시점에서 우리가 가진 것을 요약 해 봅시다.

  • 명령
  • ACL
  • 사용자

ACL 구성 요소는 여기에서 핵심입니다. 명령에 대해 최소한 (정확한 명령을 식별하기 위해) 알아야하며 사용자를 식별 할 수 있어야합니다. 사용자는 일반적으로 고유 ID로 쉽게 식별됩니다. 그러나 종종 웹 응용 프로그램에는 전혀 식별되지 않는 사용자, 종종 guest, anonymous, everybody 등이 있습니다.이 예에서는 ACL이 사용자 개체를 사용하고 이러한 세부 정보를 캡슐화 할 수 있다고 가정합니다. 사용자 개체는 응용 프로그램 요청 개체에 바인딩되며 ACL이이를 사용할 수 있습니다.

명령을 식별하는 것은 어떻습니까? MVC 패턴에 대한 귀하의 해석은 명령이 클래스 이름과 메서드 이름의 복합임을 시사합니다. 더 자세히 살펴보면 명령에 대한 인수 (매개 변수)도 있습니다. 그래서 정확히 무엇이 명령을 식별하는지 묻는 것이 타당합니까? 클래스 이름, 메소드 이름, 인수의 수 또는 이름, 심지어 인수 내부의 데이터 또는이 모든 것이 혼합되어 있습니까?

ACL에서 명령을 식별하는 데 필요한 세부 수준에 따라 이것은 많이 달라질 수 있습니다. 예를 들어 간단하게 유지하고 명령이 클래스 이름과 메서드 이름으로 식별되도록 지정하겠습니다.

따라서이 세 부분 (ACL, Command 및 User)이 서로 어떻게 속하는 지에 대한 컨텍스트가 이제 더 명확 해졌습니다.

가상의 ACL 구성 요소를 사용하여 이미 다음을 수행 할 수 있습니다.

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

여기서 무슨 일이 일어나고 있는지 확인하십시오. 명령과 사용자를 모두 식별 가능하게함으로써 ACL이 작업을 수행 할 수 있습니다. ACL의 작업은 사용자 개체 및 구체적인 명령의 작업과 관련이 없습니다.

빠진 부분이 하나뿐입니다. 이것은 공중에서 살 수 없습니다. 그리고 그렇지 않습니다. 따라서 액세스 제어가 시작되어야하는 위치를 찾아야합니다. 표준 웹 애플리케이션에서 어떤 일이 발생하는지 살펴 보겠습니다.

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 이라고 합니다. 는 AS 라우팅은 이미 명령을 찾을 수있는 직업을 가지고, 왜 안 명령이 실제로 ACL 당 허용되는 경우 확인할 연장? 예 :를 Router ACL 인식 라우터 로 확장하여 : RouterACL. 라우터가 아직 알고하지 않는 경우 User, 다음은 RouterACL'ing 명령뿐만 아니라 사용자가 식별되어야뿐만 아니라 작업 할 수 있기 때문에, 적절한 장소가 아니다. 따라서이 장소는 다를 수 있지만 확장해야하는 장소를 쉽게 찾을 수 있습니다. 사용자 및 명령 요구 사항을 채우는 장소이기 때문입니다.

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

사용자는 처음부터 사용할 수 있으며 먼저 Request(Command).

따라서 명령의 구체적인 구현 내부에 ACL 검사 를 배치하는 대신 그 앞에 배치합니다. 무거운 패턴, 마술 등 어떤 것도 필요하지 않습니다. ACL이 일을하고, 사용자가 일을하고, 특히 명령이 일을합니다. 명령 만 있으면됩니다. 명령은 어딘가에 보호되어 있는지 여부에 관계없이 역할이 적용되는지 여부를 알지 못합니다.

그러니 서로에게 속하지 않는 것은 따로 보관하십시오. SRP (Single Responsibility Principle)를 약간 바꾸어 사용하십시오. 명령을 변경하는 이유는 단 하나만 있어야합니다. 명령이 변경 되었기 때문입니다. 이제 애플리케이션에 ACL을 도입했기 때문이 아닙니다. User 개체를 전환했기 때문이 아닙니다. HTTP / HTML 인터페이스에서 SOAP 또는 명령 줄 인터페이스로 마이그레이션하기 때문이 아닙니다.

귀하의 경우 ACL은 명령 자체가 아니라 명령에 대한 액세스를 제어합니다.


두 가지 질문 : CommandNotFoundResponse 및 CommandNotAllowedResponse : ACL 클래스에서 라우터 또는 컨트롤러로이를 전달하고 범용 응답을 기대합니까? 2 : 메서드 + 속성을 포함하려면 어떻게 처리 하시겠습니까?
스테판

1 : 응답은 응답입니다. 여기서는 ACL이 아니라 라우터에서, ACL은 라우터가 응답 유형을 찾는 데 도움을줍니다 (찾을 수 없음, 특히 : 금지됨). 2 : 의존합니다. 속성을 작업의 매개 변수로 의미하고 매개 변수를 사용한 ACL이 필요한 경우 ACL 아래에 두십시오.
hakre

13

한 가지 가능성은 Controller를 확장하는 다른 클래스에 모든 컨트롤러를 래핑하고 권한을 확인한 후 래핑 된 인스턴스에 모든 함수 호출을 위임하도록하는 것입니다.

또한 디스패처 (애플리케이션에 실제로있는 경우)에서 더 업스트림을 수행하고 제어 메서드 대신 URL을 기반으로 권한을 조회 할 수 있습니다.

편집 : 데이터베이스, LDAP 서버 등에 액세스해야하는지 여부는 질문에 직교합니다. 내 요점은 컨트롤러 메서드 대신 URL을 기반으로 인증을 구현할 수 있다는 것입니다. 일반적으로 URL (URL 영역 일종의 공용 인터페이스)을 변경하지 않기 때문에 더 강력하지만 컨트롤러의 구현을 변경할 수도 있습니다.

일반적으로 특정 URL 패턴을 특정 인증 방법 및 권한 부여 지시문에 매핑하는 하나 또는 여러 구성 파일이 있습니다. 디스패처는 요청을 컨트롤러에 디스패치하기 전에 사용자가 권한이 있는지 확인하고 권한이없는 경우 디스패치를 ​​중단합니다.


답변을 업데이트하고 Dispatcher에 대한 세부 정보를 추가해 주시겠습니까? 디스패처가 있습니다-URL로 호출해야하는 컨트롤러의 메서드를 감지합니다. 하지만 Dispatcher에서 역할 (DB에 액세스해야 함)을 얻는 방법을 이해할 수 없습니다. 곧들을 수 있기를 바랍니다.
Kirzilla 2010 년

아하, 당신의 아이디어를 얻었습니다. 메서드에 액세스하지 않고 실행 허용 여부를 결정해야합니다! 좋아요! 마지막 미해결 질문-Acl에서 모델에 액세스하는 방법. 어떤 아이디어?
Kirzilla 2010 년

@Kirzilla 컨트롤러와 동일한 문제가 있습니다. 의존성이 어딘가에 있어야하는 것 같습니다. ACL이 아니더라도 모델 계층은 어떻습니까? 그것이 의존성이되는 것을 어떻게 막을 수 있습니까?
Stephane
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.