IBM®
메인 컨텐츠로 가기
    Korea [국가변경]    이용약관
 
 
   
        제품    서비스 & 솔루션    고객지원 & 다운로드    회원 서비스    
메인 컨텐츠로 가기

한국 developerWorks  >  오픈 소스  >

PHP를 이용하여 자신만의 온-디맨드 비디오 사이트 구축하기, Part 2: 기본 구조

오픈 소스 도구를 이용하여 엔터프라이즈급으로도 쓸 수 있는 온-디맨드 비디오 라이브러리를 만들자

developerWorks
Go to the previous page9 페이지 중 3 페이지Go to the next page

문서 옵션

샘플 코드


제안 및 의견
피드백

튜토리얼 평가

이 컨텐츠를 개선하기 위한 도움을 주십시오.


사용자 등록

이제 기본적인 건 끝났으니 먼저 사용자 등록 같은 간단한 작업부터 하자. 이 애플리케이션은 그저 비디오 파일 관리나 스트리밍 비디오 제공이 주 업무이고, 비디오를 업로드할 수 있는 사용자들은 정말로 몇 사람 정도만 등록하게 될 것이기 때문에 모든 게 다 설정되고 나면 사용자 등록 기능은 꺼버리고 싶을 것이다. 그럴 땐 나중에 간단히 Register 링크와 사용자 컨트롤러에 add 액션을 제거하면 된다.

사용자 컨트롤러에서 add 함수 변경

아주 단순한 신규 사용자 추가용 페이지는 이미 만들어 두었지만 드러내 보일만한 애플리케이션으로 만들고자 한다면 좀 더 복잡한 일이 필요할 것이다. 특히 훤히 드러나는 텍스트로 암호를 저장하는 건 좋지 못한 사례다. 다행히도 PHP는 자체적으로 md5 해시 함수를 제공한다. 이 함수는 CakePHP의 Security.salt 값(Part 1에서 설정)과 함께 사용되면 사용자가 정확한 암호를 이용해 다시 로그온할 경우 직접 보이진 않지만 재계산될 수 있는 해시 문자열을 암호로 저장한다.

이제 사용자 컨트롤러에서 add 함수를 변경할 준비가 되었다. app/controllers/users_controller.php를 열고 add 함수로 가보자.

$this->User->create(); 코드로 사용자를 생성하기 바로 전 줄로 가서 일반 텍스트 형태의 암호값을 Security.salt를 이용해 해시된 암호로 저장하는 줄을 삽입한다. 줄의 내용은 다음과 같다: $this->data['User']['password'] = md5($this->data['User']['password'] . Configure::read('Security.salt'));.

다음 단계는 기본 로그인 함수와 뷰를 추가하는 것인데 먼저 데이터베이스에서 등록된 사용자를 검색해야 한다.

공교롭게도 CakePHP는 애플리케이션 내에 이미 데이터 관련 '마술'을 제공한다. findByUsername() 함수는 인자로 유효한 사용자 이름이 주어지면 기대했던 대로 인자로 주어진 사용자 기록을 내어준다. 이런 일을 하는 함수를 따로 정의할 필요가 없을 것이다. 그저 애플리케이션에 건네면 애플리케이션이 우리가 뭘 하려는지 알고 동작해준다. 데이터베이스에 있는 어떤 필드든 똑같은 작업을 손 안대고 할 수 있다(findByPassword가 동작하는 것처럼 Halibut 필드가 있다면 findByHalibut도 동작할 것이다). 새로 작성한 login 함수에서 실제로 이와 같은 마술 함수 중 하나가 어떻게 동작하는지 살펴보자.


Listing 5. Login 함수
                    
function login() {
   if ($this->data) {
       $results = $this->User->findByUsername($this->data['User']['username']);
       if ($results && $results['User']['password'] == md5($this->data['User']
	                         ['password'] . Configure::read('Security.salt'))) {
            $this->Session->write('user', $results['User']);
            $this->redirect(array('action' => 'index'), null, true);
        } else {
            $this->set('error', true);
        }
   }
}

데이터베이스에 있는 해시된 암호를, 로그인 시도 시 전달된 값과 Security.salt를 이용해 계산한 해시값과 비교하여 암호를 확인한다. 이 둘이 같다면 비교가 끝나고 세션 변수에 사용자 데이터를 저장함으로써 사용자는 로그인된다.

이 시점에서 logout 함수도 추가해보자.


Listing 6. Logout 함수
                    
    function logout() {
        $this->Session->delete('user');
        $this->redirect(array('action' => 'login'), null, true);
    }

logout 함수는 항상 login 액션으로 리다이렉트해버린다. 따라서 자체 뷰를 필요로 하진 않는다. 로그인 뷰는 추가해야 하니 app/views/users/login.ctp를 생성하자. 지금 로그인 뷰 파일에 정말로 필요한 건 아래와 같은 코드 정도다.


Listing 7. 로그인 뷰
                    
<?php
if (isset($error)) {
  echo('Invalid Login.');
}
?>

<p>Please log in.</p>
<?php echo $form->create('User', array('action' => 'login')); ?>

<?php
    echo $form->input('username');
    echo $form->input('password');
?>

<?php echo $form->end('Login');?>
<?php echo $html->link('Register', array('action' => 'add')); ?>

테스트하기 전에 리다이렉트되는 인덱스 뷰에 성공적으로 로그인되었음을 보여주는 시각적인 내용을 추가하면 좋을 것이다. 같은 디렉터리에 위치한 인덱스 뷰(index.ctp)를 열고 처음 div 태그 아래에 다음 세 줄을 직접 집어넣는다.


Listing 8. 인덱스 뷰
                    
<p>Hello, <?php echo($session_user['username']); ?>!
&nbsp;&nbsp;|&nbsp;&nbsp;
<?php echo $html->link('logout', array('controller' => 'users', 
                             'action' => 'logout')); ?></p>

이제 인덱스 뷰는 성공적으로 로그인한 모든 사용자에게 환영 메시지와 함께 로그아웃 링크를 표시하게 되었다. 또는 그런 의도 말고도 그보다는 $session_user를 이용해 인덱스 뷰가 의도하는 바를 컨트롤러에게 곧바로 이야기하는 것이라고도 할 수 있다. 따라서 index 액션에 코드를 추가하는 건 users_controller.php로 되돌아가는 것이다. app/controllers_user_controller.php 파일을 연다. 컨트롤러와 뷰 사이를 끝없이 이동하며 세션 변수를 테스트하게 되는데 index 함수에 다음 여섯 줄을 추가한다.


Listing 9. user 세션 변수 테스트
                    
        $user = $this->Session->read('user');
        if (!$user['username']) {
            $this->redirect(array('controller' => 'users', 'action' 
			                                  => 'login'), null, true);
        } else {
            $this->set('session_user', $user);
        }

이 여섯 줄에서는 user 세션 변수를 테스트한다. 만약 사용자 이름(username)이 설정되어 있지 않으면 로그인이 안 된 것으로 간주되어 로그인 페이지로 리다이렉트된다. 설정되어 있으면 user 세션 변수를 뷰가 사용할 수 있는 session_user 변수로 복사한다. 뷰에서 이 세 가지를 조합한 여섯 줄 코드는, 로그인하지 않은 웹 서퍼들이 페이지를 브라우징하지 못하게 막고 싶은 곳이라면 어디든지 사용할 수 있다.

지금까지의 작업을 견고히 하기 위해 스스로 신규 사용자로 등록하여 로그인할 것이다. 브라우저를 이용해 애플리케이션의 users 인덱스로 간다. http://[도메인]/users/index라고 입력하면 될 것이다. 로그인하지 않은 상태에서 인덱스 페이지를 보려면 하면 로그인하는 과정이 필요하기 때문에 즉석에서 로그인 페이지로 이동할 것이다. 화면은 그림 1과 같을 것이다.


그림 1. 로그인 화면
로그인 화면

아직 등록된 사용자가 없기 때문에 페이지 하단에 위치한 Register 링크를 클릭한다. 이렇게 하여 md5와 Security.salt를 사용하도록 수정된 add 액션으로 가게 될 것이다.


그림 2. 사용자 등록
사용자 등록

예제에서는 사용자 암호로 video를 입력했다. Submit 버튼을 누르면 사용자가 저장되었다는 메시지와 함께 로그인 화면으로 되돌아온다. 새로 등록한 사용자의 사용자 이름과 암호를 이용해 로그인하면 사용자 인덱스에 접근할 수 있게 되고 그 곳에서 두 가지 내용이 보일 것이다. 앞서 적은 대로 개인 환영 메시지가 나타날 것이고, 암호는 데이터베이스 내에 저장된 16진수 형태의 알기 힘든 형태로 보일 것이다.


그림 3. 사용자 인덱스
사용자 인덱스




위로


사용자 코드 업데이트

일반에 공개하기 전에 이 페이지에서 바꿔야 할 게 좀 있다. 아이디, 암호, 이름, 성을 나타내는 열은 아무래도 모두 없애야 할 것이다. 이메일은 그대로 두고(혹은 뜯어고치고), editdelete 액션은 개인 계정에 대해서만 표시되도록 해야 한다. 또한 페이지 하단에 사용 가능한 액션을 나타내는 화면도 바꿔야 한다. 이러한 비디오 뷰와 컨트롤러 편집 작업을 어떻게 수행하는지 볼 것이다. 비디오 소유자만 editdelete 함수를 쓸 수 있도록 제한할 것이고 My Videos 페이지를 추가할 것이다.

짚고 넘어갈 게 또 있는데 바로 edit user 액션이다. 바꾸지 않고 그대로 둔다면 뷰의 암호 컨트롤에 자동으로 채워진 암호는 생성된 해시가 될 것이다. edit user 페이지를 이용할 수 있도록 하기 전에 두 가지 과정은 거쳐야 한다. 첫 번째, 정보를 수정하려는 사용자가 로그인한 계정인지 확실히 체크해야 한다. 비디오 delete 액션에서도 마찬가지로 했던 작업이다. 두 번째, 사용자가 어떤 변경도 하지 않고 이 페이지로 온다면 해시된 암호가 컨트롤로 보내지지 않도록 막을 필요가 있다. 이를 Listing 10에서 다뤘다.


Listing 10. 해시된 암호가 컨트롤로 보내지지 않도록 중단
                    
if (empty($this->data)) {
      $this->data = $this->User->read(null, $id);
      unset($this->data['User']['password']);
}

데이터는 사용자 테이블에서 읽히며, 그리고 나서 암호 키 설정을 해제(혹은 제거)해 버린다. 다음으로 사용자가 제출한 데이터가 있다면 해시된 암호를 다뤄야 한다. 이는 데이터가 저장되기 바로 전인 이 부분에서 하게 된다.


Listing 11. 해시된 암호 다루기
                    
if (!empty($this->data['User']['password'])) {
       $this->data['User']['password'] = md5($this->data['User']['password'] . 
                    Configure::read('Security.salt'));
} else {
       unset($this->data['User']['password']);
}

암호가 비어 있지 않다면 우리가 공란으로 비워둔 암호 입력란에 뭔가를 입력한 것일 테고, 따라서 이를 해시하여 저장해야 한다. 암호가 비어 있다면 사용자가 현재 설정 변경을 원치 않는다는 의미로 가정하고 들어온 데이터에서 암호 변수를 해제한다. 이렇게 하여 신규 사용자 데이터가 데이터베이스에 저장될 때 기존 암호값이 변경되는 걸 막게 된다.




위로


비디오 코드 업데이트

이제 기본적인 사용자 기능은 갖췄으니 비디오 관련 상세 내용 표시에 쓸 코드를 업데이트하는 데 시간을 쓸 차례다. 여러분은 사용자들이 다른 사람의 비디오를 편집하고 삭제할 수 없도록 하고 싶을 것이다. 이 말 뜻은, 단순히 삭제 관련 링크를 감추는 것만이 아니라 편집되거나 삭제될 비디오를 사용자가 소유하도록 해야 한다는 의미다.

비디오 컨트롤러에서 index 함수는 Listing 12와 같을 것이다.


Listing 12. 비디오 컨트롤러의 index 함수
                    
    function index() {
        $this->Video->recursive = 0;
        $this->set('videos', $this->paginate());

    //add the next six lines, everywhere user data is required
        $user = $this->Session->read('user');
        if (!$user['username']) {
            $this->redirect(array('controller' => 'users', 'action' => 
			                                     'login'), null, true);
        } else {
            $this->set('session_user', $user);
        }
    }

어디서 본 것 같지 않은가. 그렇다. 사용자 컨트롤러의 index 함수와 거의 똑같다. 차이점이라곤 테이블에서 사용자 대신 비디오를 꺼내준다는 점이다. 로그인하지 않은 웹 서퍼들에 대한 리다이렉션에서 사용자 컨트롤러를 명시하고 있다는 점은 중요하다. 그렇게 하지 않는다면 현재 컨트롤러에서 login 액션을 찾겠지만 현재 컨트롤러에는 없다.

이제 사용자 자신의 것이 아닌 비디오를 편집하거나 삭제할 수 없도록 해보자. 물론 우리는 다른 사용자들에게 속한 모든 비디오에 대해 비디오 인덱스 페이지에서 이런 액션 링크들을 제거하고 싶어하겠지만 그것만으로는 충분하지 않다. 또한 액션 그 자체에서도 소유권을 테스트해 볼 필요도 있다. 지금은 비디오 컨트롤러 부분을 다루고 있으니 delete 액션을 통해 해보자. 바꾸기 전의 delete 액션은 Listing 13과 같다.


Listing 13. delete 액션
                    
    function delete($id = null) {
        if (!$id) {
            $this->Session->setFlash(__('Invalid id for Video', true));
            $this->redirect(array('action'=>'index'));
        }
        if ($this->Video->del($id)) {
            $this->Session->setFlash(__('Video deleted', true));
            $this->redirect(array('action'=>'index'));
        }
    }

이처럼 할 경우 유효한 비디오 아이디가 주어진다면 어떤 비디오든지 누구든 삭제할 수 있다. 우리는 이런 일이 일어나기 전에 먼저 사용자의 로그인 아이디와 비디오 소유자의 아이디를 꺼내 일치하는지 살펴봐야 할 필요가 있다. 사용자 아이디는 세션 변수에 감춰져 있고, 소유자의 사용자 ID를 포함하여 이 특정 비디오에 대한 정보를 얻기 위해 데이터베이스에서 할 일은 $id 인자를 이용해 $this->Video->read를 호출한 결과값으로 감별하는 것이다.


Listing 14. $id 인자를 이용한 delete 액션
                    
    function delete($id = null) {
        $user = $this->Session->read('user');
        $results = $this->Video->read(null, $id);
        if ($results && $results['User']['id'] == $user['id']) {
            if (!$id) {
                $this->Session->setFlash(__('Invalid id for Video', true));
                $this->redirect(array('action'=>'index'));
            }
            if ($this->Video->del($id)) {
                $this->Session->setFlash(__('Video deleted', true));
                $this->redirect(array('action'=>'index'));
            }
        } else {
            $this->Session->setFlash(__('Video belongs to ' . 
			 $results['User']['id'] . ', not ' . $user['id']  , true));
            $this->redirect(array('action'=>'index'));
        }
    }

모델의 read 메서드를 사용하여 delete 함수 인자로 전달된 아이디로 비디오를 찾도록 read 메서드에 이야기하게 되며 한 줄이면 된다.

user 세션 변수의 내용, if문, 좀 당황스러운 원시 문자 같은 오류 메시지를 이용하여 권한이 있는 적절한 사람이 요청한 게 아니라면 생성자에게만 삭제할 수 있도록 제한을 두었다. 나중에는 목록에 애플리케이션 관리자 추가를 하고 싶을 것이다. 하지만 그건 나중 이야기다. 이제 우리 소유가 아닌 모든 비디오에서 이같은 편집, 삭제 링크를 들어내자. 이런 류의 작업은 뷰에서 해야 한다. 이를 위해 app/views/videos/index.ctp를 연다. View, Edit, Delete 링크를 만들어주는 세 줄은 Listing 15와 같을 것이다.


Listing 15. View, Edit, Delete 링크
                    
            <?php echo $html->link(__('View', true), 
			     array('action'=>'view', $video['Video']['id'])); ?>
            <?php echo $html->link(__('Edit', true), 
			      array('action'=>'edit', $video['Video']['id'])); ?>
            <?php echo $html->link(__('Delete', true), 
			      array('action'=>'delete', $video['Video']['id']), 
		 null, sprintf(__('Are you sure you want to delete # %s?', 
		 true), $video['Video']['id'])); ?>

브라우저에서 애플리케이션을 열면 거기 표시된 URL에 따라 이동한다는 점을 명심하라. 예를 들어 비디오 ID 3번에서 view 함수는 http://[도메인]/videos/view/3으로 간다.

이것만 봐도 편집과 삭제용 URL이 어떻게 동작할지 추론해내는 일이 천재들만 할 수 있는 일은 아님을 알 수 있을 것이다. 바로 이것이 공개된 이후 이 사람 저 사람이 여러분 애플리케이션 여기저기를 헤집고 돌아다니게 하기 전에 컨트롤러에서 소유권을 검사해야 하는 중요한 이유다. 좀 이야기가 빗나가고 있긴 하지만 말이다.

인덱스 뷰에서 비디오에 적용되는 세 가지 액션은 이미 살펴 보았다. 이 액션 중 소유자가 아닐 경우 두 가지 액션은 빠져야 한다. index 액션에서 이미 코드 여섯 줄을 추가했고 이 코드 중 하나는 user 세션 변수를 $session_user를 이용해 뷰에 전달한다. 사용자 신원 테스트를 추가하면 되는데, 액션 링크는 Listing 16과 같을 것이다.


Listing 16. 사용자 신원 테스트를 추가한 이후의 액션 링크
                    
            <?php echo $html->link(__('View', true), 
			       array('action'=>'view', $video['Video']['id'])); ?>
            <?php if ($video['User']['id'] == $session_user['id'] ) { ?>
            <?php echo $html->link(__('Edit', true), 
				    array('action'=>'edit', $video['Video']['id'])); ?>
                <?php echo $html->link(__('Delete', true), 
				array('action'=>'delete', $video['Video']['id']), 
				null, sprintf(__('Are you sure you want to delete # %s?', 
				true), $video['Video']['id'])); ?>
            <?php } ?>

이제 다른 사람의 비디오 삭제를 불허하도록 했으며 어쨌든 간에 사용자들이 시도할 필요가 없는 것에 대해 알게끔 했다. 이제는 delete에서 했던 것과 같은 방식으로 동작하도록 비디오 컨트롤러의 edit 액션을 수정할 때가 되었다. 결국 링크를 없애는 것만으로는 충분하지 않다. 링크를 알아채는 건 쉽다는 걸 알았을 것이다. 따라서 컨트롤러에서 정확한 사용자가 해당 액션을 요청하는 것을 검증해야 한다.




위로



Go to the previous page9 페이지 중 3 페이지Go to the next page
    IBM 소개 개인정보 보호정책 문의