Programming-[Backend]/php, codeigniter

생활코딩 codeigniter - 4. 세션, 사용자 인증, Core 확장처리, 로그인, 회원가입

컴퓨터 탐험가 찰리 2024. 4. 6. 20:58
728x90
반응형

1. Session & 사용자 인증

 

Session 설정

 

CI에서 세션 처리를 위해서 필수적으로 수정해야하는 부분은 아래와 같다. 당연히 encryption_key 값은 유출되면 안된다. 그리고  DB에 저장하지 않고 파일로써 컴퓨터에 저장하면 사용자의 로컬 컴퓨터에 저장되므로 보안상 위험할 수 있기 때문에 DB에 저장하는 것으로 설정한다.

  • $config['encryption_key'] = ''; // 32글자의 문자 입력
  • $config['sess_use_database'] = TRUE; // 세션 데이터를 DB에 저장하기 위함

보안을 위해서 추가적으로 수정해야하는 부분은 아래와 같다. ip와 browser까지 체크해주는 옵션이다.

  • $config['sess_match_ip'] = TRUE;
  • $config['sess_match_useragent'] = TRUE;

 

그리고 autoload에 session을 추가한다.

$autoload['libraries'] = array('session');

 

 

Session 사용

아래와 같은 방식으로 설정하고 사용한다.

$this->session->set_userdata('session_test', 'some_value');
$this->session->userdata('session_test');

 

이런 세션 정보는 개발자도구에서 session 항목에 기본적으로 ci_session 이라는 이름으로 저장된다. 그리고 config에 따라 ci_sessions(config의 sess_table_name 값)라는 테이블을 바라보게 된다. 다만 ci_sessions 테이블은 직접 만들어줘야한다. 다음 SQL 문을 통해 DB에 만들어주면 된다.

CREATE TABLE IF NOT EXISTS  `ci_sessions` (
    session_id varchar(40) DEFAULT '0' NOT NULL,
    ip_address varchar(16) DEFAULT '0' NOT NULL,
    user_agent varchar(120) NOT NULL,
    last_activity int(10) unsigned DEFAULT 0 NOT NULL,
    user_data text NOT NULL,
    PRIMARY KEY (session_id),
    KEY `last_activity_idx` (`last_activity`)
);

 

 

세션을 생성하면 DB에는 session_id 형태로 저장되고, 유저는 cookie에 저장된 ci_session이라는 값을 서버에 보내서 사용자를 식별할 수 있게 된다. 아래는 강의 내용 중 DB에 저장된 session 정보를 캡쳐한 것이다.

 

 

Session 활용: 로그인

세션 정보를 활용해서 정보가 존재하는 경우에는 글 등록 등 기능이 정상작동하도록 하고, 아닌 경우에는 로그인 페이지로 리다이렉트되도록 하는 로직을 구현할 수 있다. 다만 강의에서는 실제로 로그인 기능을 구현하지는 않고, 임의의 id, password 값을 만들어서 처리한다.

 

로그인 필요 화면

로그인이 필요한 화면에 아래 예시처럼 if문을 작성한다. session 테이블에 user_data라는 항목을 만들어놨으므로 여기에 저장된 값이 정상적으로 존재하는지 확인하는 코드를 넣어주면 되는 방식이다.

 

function process() {
  if(!$this->session->userdata('is_login')){
    $this->load->helper('url');
    redirect('/auth/login');
  }
  //...중략
}

 

 

로그인 처리

session에서 userdata 중 'is_login'으로 저장된 값ㅇ르 불러오고, 만약 그 값이 존재하지 않는다면 url 헬퍼에서 redirect 메서드를 불러와서 로그인 화면으로 가도록 했다. 로그인 컨트롤러는 아래처럼 구성한다.

 

임시로 관리자 계정 같은 정보로써 config['authentication']에 특정 id와 password를 저장해놓는다. 그리고 view 페이지에서 form을 이용해서 id, password 값을 사용자로부터 제출받도록 하는 view를 만들어놓은 상태라고 가정하고 아래 컨트롤러 페이지를 작성한 것이다. 이때, config['authentication']에 저장된 정보와 사용자의 입력값이 일치하는 경우라면 $this->session->set_userdata 를 통해서 'is_login' 정보를 저장한다. 그러면 위 코드의 process 메서드에서 userdata 중 is_login 정보를 확인하여 로그인 여부에 따라 기능의 작동여부가 결정되도록 할 수 있게 되는 것이다.

function authentication(){
  $authentication = $this->config->item('authentication');
  if(
    $this->input->post('id') == $authentication['id'] &&
    $this->input->post('password') == $authentication['password']
    ) {
      $this->session->set_userdata('is_login', true);
      redirect("리다이렉트하고자하는 페이지 주소, 보통 이전 페이지로 지정함");
    } else {
      echo "불일치";
      $this->session->set_flashdata('message', '로그인에 실패했습니다.');
      $this->load->helper('url');
      redirect('/auth/login');
    }
  }
}

 

로그인에 실패한 경우 session->set_flashdata 메서드를 사용하는데, 'message'라는 속성 값에 한 번만 사용되고 삭제되는 데이터를 저장할 수 있게 된다. is_login 처럼 계속해서(만료 전까지) 데이터를 갖고 있어야되는 경우가 아니라면 flashdata 메서드를 사용하는 것이다.

 

redirect 처리: returnURL

사용자가 로그인 화면에 접근하기 전에 원래 URL을 저장해놓고 이것을 로그인 페이지에 쿼리파라미터로 전달하여 로그인 성공 시 원래 페이지로 돌아가게 할 수 있다. redirect 부분을 아래처럼 바꿔주면 된다. 다만 returnURL 뒤에 오는 url 자체에 포함된 ?, /등은 encoding으로 escape처리해줘야하는데, 이를 위해 rawurlencode() 메서드를 사용하였다. 또한 호스트 주소나 기본적으로 포함된 index.php가 변경될 가능성이 있으므로 이를 자동으로 동적으로 따주는 site_url() 이라는 메서드도 적용하였다.

//... 중략
if(!$this->session->userdata('is_login')){
  $this->load->helper('url');
  //redirect('/auth/login?returnURL='.rawurlencode('http://localhost/index.php/topic/add?page=1'));
  redirect('/auth/login?returnURL='.rawurlencode(site_url(topic/add?page=1)));
}

 

 

 

logout

logout을 위해서는 session 정보만 삭제해주면 된다. 우선 로그인, 로그아웃을 위한 링크를 만들어준다. 로직은 단순하다. session->userdata에 'is_login' 여부를 판단하여 존재하는 경우 로그아웃을, 아닌 경우 로그인 링크를 넣어주는 것이다. php 코드로 작성된 부분만 <?php ?> 태그로 감싸져있다는 것도 고려해서 살펴보자.

<div class="nav-collapse collapse">
  <ul class="nav pull-right">
    <?php
    if($this->session->userdata('is_login')){
    ?>
      <li><a href="/index.php/auth/logout">로그아웃</a></li>
    <?php
    } else {
    ?>
      <li><a href="/index.php/auth/login">로그인</a></li>
    <?php
    }
    ?>
  </ul>
</div>

 

 

그리고 auth/logout로 이동했을 때에 대한 처리 코드를 넣어주면 된다. url 헬퍼의 sess_destroy() 라는 메서드를 사용하여 처리한다. 로그아웃 이후에는 홈페이지(/)로 이동하도록 처리해주었다.

function logout(){
  $this->session->sess_destroy();
  $this->load->helper('url');
  redirect('/');
}

 

 

 

2. Core 클래스 확장처리

 

CI 프레임워크가 제공하는 system/core의 기능을 override 또는 상속하여 확장하는 방법에 대해서 배운다. 시스템 코드이기 때문에 잘못 설정하는 경우 프로젝트 전체에 영향을 미칠 수 있어 유의해서 처리할 필요가 있다. 여기서는 안전하게 상속을 이용하여 처리하는 방법에 대해서 학습한다.

 

앞서 3번 글에서 배운 바대로 $config['subclass_prefix'] 값에서 prefix로 설정된 기본 값이 'MY_'이므로 예를 들어 system/core/Controller.php 를 상속받아 처리하고 싶다면 controllers 디렉토리에 MY_Controller.php 라는 파일을 만들어주면 된다. 그리고 각종 controller들에서 CI_Controller가 아닌 MY_Controller를 상속받도록 처리하면 된다. 다만 MY_Controller 파일 상에서 부모로 CI_Controller를 상속받고 부모의 생성자를 받도록 __construct() 생성자를 처리해주면 된다.

 

<?php
class MY_Controller extends CI_Controller {
  function __construct()
  {
    parent::__construct();
  }
  
  //공통으로 쓰면서 커스터마이징할 로직들 추가
}

 

 

 

3. 회원가입

session 사용 부분에서 config에 Admin용 임의의 id, password를 저장하는 방식을 user 테이블을 만들어서 그곳에 회원정보를 저장해놓고 회원 및 세션관리 처리를 하는 기능을 만든다. 회원가입은 여러 프레임워크에서 기본적으로 제공하는 기능이기 때문에 회원가입을 위한 view 입력 양식, controller 기능 부분 등 전부 다 살펴보기 보다는 주요 로직, CI에서 제공하는 주요 기능만 살펴보면 될 것 같다.

 

회원가입을 처리하는 controller 부분

form_validation에서 각종 입력값들을 검증처리한다. is_unique[user.email]에서는 user 모델의 email 속성 값을 불러와서 유니크한지 검증해준다. 그리고 validation을 통과했을 때는 user_model에 접근하여 email, password 등 각 값들을 post 방식으로 user 모델의 add() 메서드로 전송한다.

fucntion register(){
  $this->_head();
  
  $this->load->library('form_validation');
  
  $this->form_validation->set_rules('email', '이메일 주소', 'required|valid_email|is_unique[user.email]');
  $this->form_validation->set_rules('nickname', '닉네임', 'required|min_length[5]|max_length[20]');
  //...중략
  
  if($this->form_validation->run() === false){
    $this->load->view('register');
  } else {
    $this->load->model('user_model');
    $this->user_model->add(array(
      'email'=>$this->input->post('email',
      'password'=>$this->input->post('password'),
      'nickname'=>$this->input->post('nickname')
    ));
  }
}

 

 

user 모델의 add 메서드

User 모델에서는 db->set, db->insert, db->insert_id 메서드를 사용한다. 파라미터는 option이라는 이름으로 하나만 받아오도록 처리하고, 그 값이 상기 controller의 register에서 전달한 array 값인 것이다.

<?php
class User_model extends CI_Model {
  function __construct()
  {
    parent::__construct();
  }
  
  function add($option)
  {
    $this->db->set('email', $option['email']);
    $this->db->set('password', $option['password']);
    $this->db->set('created', 'NOW()', false);
    $this->db->insert('user');
    $result = $this->db->insert_id();
    return $result;
  }
}

 

 

비밀번호 해싱처리

보안을 위해서 password 부분은 해싱 처리해야한다. php에서는 CLI 방식으로도 사용할 수 있다.

php -r "echo md5(1);"

 

 

MD5 단방향 해싱 방식을 사용하여 1이라는 문자를 hash 코드로 변환해주는 것을 확인할 수 있다. 참고로 MD5는 해석이 되어버렸기 때문에 사용하면 안된다. 강의에서는 bcrypt 방식을 이용하여 처리한다. 그리고 매뉴얼을 보면 password 관련 라이브러리를 사용하지 말고 php 자체에서 제공하는 password_hash() 메서드를 사용하라고 되어있다. 이 메서드는 php 5.5.0 이상부터만 사용이 가능하다.

 

ref.)

https://www.php.net/manual/en/function.password-hash.php

 

첫 번째 인자로 password 값을 받고, algo 부분은 algorithem을 적어주면 되는데 통상 PASSWORD_BCRYPT를 사용한다. 그리고 옵션에서는 얼마나 복잡하게 처리할지에 대한 'cost' 부분과 'salt' 부분을 array로 주면 된다. 다만 참조 페이지를 보면 salt 옵션은 deprecated되고 자동으로 salt를 생성하여 처리한다고 하니 참고하자.

 

 

코드는 아래처럼 처리하면 된다.

if(function_exists('password_hash')) {
  $hash = password_hash($this->input->post('password'), 1);
  //...중략
  $this->user_model->add(array(
  	//...중략
    'password'=>$hash,
  ));
}

 

password_hash() 메서드에 들어가보면 $algo 부분은 int로 입력하게 되어있으며. 링크 페이지를 참고하여 원하는 해싱 알고리즘을 선택해주면 될 것 같다.

 

 

사용자 인증 로직 수정하기

마지막으로 db에서의 email값과 hashing된 비밀번호로 인증처리를 하기 위해서 아래처럼 controller 로직을 수정한다. 위에서 작성했던 authentication 메서드는 id, password를 config에 저장하고 불러오는 방식이였으나, 그 방식을 db와 통신하도록 변경하는 것이다.

 

controller 코드

function authentication(){
  $this->load->model('user_model');
  $user = $this->user_model->getByEmail(array('email'=>$this->input->post('email')));
  if(
    $this->input->post('email') == $user->email &
    password_verify($this->input->post('password'), $user->password)
  ) {
    //기존 로그인 로직(중략)
    }
  }
}

 

user_model 코드: getByEmail 부분

function getByEmail($option)
{
  $result = $this->$db->get_where('user', array('email'=>$option['email']))->row();
  return $result;
}

 

 

 

 


 

참조

생활코딩 유튜브 채널

https://www.youtube.com/@coohde

728x90
반응형