PHP

PHP

    ›실전 프로젝트

    시작하기

    • eBook 다운로드
    • PHP란 무엇인가요?
    • PHP 시작하기

    개발 환경 설정

    • 코드 에디터 준비하기
    • 윈도우(Windows) 환경에 APM 설치하기
    • macOS 환경에 APM 설치하기
    • 리눅스(Linux) 환경에 APM 설치하기
    • APM 환경 테스트 및 첫 코드 실행

    PHP 기초 문법

    • PHP는 어떻게 동작할까?
    • 기초 문법 - 태그, 마침표, 그리고 주석
    • 변수와 데이터 타입 - 프로그래밍의 기본기
    • 배열과 객체, 그리고 NULL
    • 변수와 함수의 이름 짓기 - 식별자(Identifier)
    • 절대 변하지 않는 값 - 상수(Constants)와 열거형(Enum)
    • 데이터를 요리하는 도구 - 연산자(Operators)
    • 연산자 우선순위 - 괄호의 마법
    • 흐름을 제어하는 마법 - 조건문 (if, match)
    • 흐름을 제어하는 마법 - 반복문
    • 재사용의 미학 - 함수(Function)
    • 변수의 생존 범위(Scope)와 수명
    • 흐름을 제어하는 마법 - 파일 포함과 예외 처리
    • 실무에서 숨 쉬듯 쓰이는 문자열과 배열 함수
    • 문자열을 다루는 마법의 주문 - 정규표현식(Regex)
    • 에러를 두려워하지 마라 - 디버깅과 에러 처리

    웹 개발 기초

    • 절대 믿지 마라! - 미리 정의된 변수와 외부 입력 처리
    • 사용자와 소통하는 창구 - HTML 폼(Form) 다루기
    • 나를 기억해 줘! - 쿠키(Cookie)와 세션(Session)
    • 서버의 기록 보관소 - 파일 입출력(File I/O)

    데이터베이스

    • 데이터베이스(DB)와 SQL 기초 - 데이터의 든든한 금고
    • 데이터베이스와의 안전한 대화 - PDO 기초

    실전 프로젝트

    • 첫 번째 실전 프로젝트 - 방명록(Guestbook) 만들기
    • 실전 프로젝트 2단계 - 기본 게시판(CRUD) 만들기
    • 실전 프로젝트 3단계 - 계층형(답변형) 게시판 만들기
    • 실전 프로젝트 4단계 - 게시판 레벨업 (댓글, 보안, 성능)
    • 실전 프로젝트 5단계 - 안전한 회원가입과 로그인 시스템

    실전 프로젝트 5단계 - 안전한 회원가입과 로그인 시스템

    게시판을 만들 줄 안다면 이제 회원 시스템도 만들 수 있습니다. 결국 회원가입은 'DB에 사용자 정보를 INSERT 하는 것'이고, 로그인은 'DB에서 정보를 SELECT 해와서 비밀번호를 대조하는 것'에 불과하니까요.

    하지만 여기에 '세션(Session)'과 '보안(Security)'이라는 양념이 강력하게 들어가야 합니다. 회원 시스템은 사이트의 심장이자 보안의 최전선입니다.


    1. 데이터베이스(DB) 설계

    회원 정보를 담을 users 테이블을 만듭니다.

    CREATE TABLE users (
        id         INT UNSIGNED  NOT NULL AUTO_INCREMENT,
        email      VARCHAR(100)  NOT NULL UNIQUE, -- 이메일을 아이디로 씁니다. 중복 가입 방지!
        password   VARCHAR(255)  NOT NULL,        -- 암호화된 비밀번호가 들어갈 넉넉한 공간
        name       VARCHAR(50)   NOT NULL,
        created_at DATETIME      NOT NULL DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
    

    2. 공통 DB 접속 파일 (db.php)

    이전에 게시판에서 만들었던 getPdo() 함수 방식을 그대로 재사용합니다.

    <?php
    declare(strict_types=1);
    
    function getPdo(): PDO {
        static $pdo = null;
        if ($pdo === null) {
            $dsn = 'mysql:host=localhost;dbname=mydb;charset=utf8mb4';
            $pdo = new PDO($dsn, '아이디', '비밀번호', [
                PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                PDO::ATTR_EMULATE_PREPARES   => false,
            ]);
        }
        return $pdo;
    }
    

    3. 회원가입 (register.php)

    사용자의 이메일과 비밀번호를 받아서 깐깐하게 검사한 뒤 DB에 저장합니다.

    <?php
    declare(strict_types=1);
    session_start();
    require 'db.php';
    
    $errors = []; // 발생한 에러 메시지들을 모아둘 빈 바구니
    
    // 폼이 전송되었을 때만 처리합니다.
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $name     = trim($_POST['name']     ?? '');
        $email    = trim($_POST['email']    ?? '');
        $password = trim($_POST['password'] ?? '');
        $confirm  = trim($_POST['confirm']  ?? '');
    
        // 1. 입력값 깐깐하게 검증하기
        if ($name === '') {
            $errors[] = '이름을 입력해주세요.';
        }
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $errors[] = '올바른 이메일 형식이 아닙니다.';
        }
        if (mb_strlen($password) < 8) {
            $errors[] = '비밀번호는 보안을 위해 8자 이상이어야 합니다.';
        }
        if ($password !== $confirm) {
            $errors[] = '비밀번호 확인이 일치하지 않습니다.';
        }
    
        // 2. 이메일 중복 가입 확인
        if (empty($errors)) {
            $pdo  = getPdo();
            $stmt = $pdo->prepare("SELECT id FROM users WHERE email = :email");
            $stmt->execute([':email' => $email]);
            if ($stmt->fetch()) {
                $errors[] = '이미 가입된 이메일 주소입니다.';
            }
        }
    
        // 3. 에러가 하나도 없다면 DB에 안전하게 저장
        if (empty($errors)) {
            $pdo->prepare(
                "INSERT INTO users (name, email, password) VALUES (:name, :email, :password)"
            )->execute([
                ':name'     => $name,
                ':email'    => $email,
                // [핵심 보안] 사용자의 비밀번호는 무조건 해싱(단방향 암호화)해서 저장합니다!
                ':password' => password_hash($password, PASSWORD_DEFAULT),
            ]);
    
            header('Location: login.php?registered=1');
            exit;
        }
    }
    ?>
    <!DOCTYPE html>
    <html lang="ko">
    <head><meta charset="UTF-8"><title>회원가입</title></head>
    <body>
    <h2>회원가입</h2>
    
    <!-- 에러가 있다면 붉은 글씨로 경고해줍니다 -->
    <?php foreach ($errors as $error): ?>
        <p style="color:red;">* <?= htmlspecialchars($error, ENT_QUOTES, 'UTF-8') ?></p>
    <?php endforeach; ?>
    
    <form method="post">
        <!-- 사용자가 입력하다가 에러가 났을 때, 기존 입력값을 날리지 않고 다시 채워주는 센스! -->
        <p>이름: <input type="text" name="name" value="<?= htmlspecialchars($_POST['name'] ?? '', ENT_QUOTES, 'UTF-8') ?>" required></p>
        <p>이메일(아이디): <input type="email" name="email" value="<?= htmlspecialchars($_POST['email'] ?? '', ENT_QUOTES, 'UTF-8') ?>" required></p>
        <p>비밀번호: <input type="password" name="password" required> <small>(8자 이상)</small></p>
        <p>비밀번호 확인: <input type="password" name="confirm" required></p>
        <p><input type="submit" value=" 가입하기 "></p>
    </form>
    <p><a href="login.php">이미 계정이 있으신가요? 로그인</a></p>
    </body>
    </html>
    

    비밀번호 암호화(password_hash)의 중요성
    과거에 많이 쓰던 md5()나 sha1()은 이제 해커들의 1초 컷 장난감이 되었습니다. 최신 PHP가 권장하는 가장 강력한 알고리즘을 알아서 선택해 주는 PASSWORD_DEFAULT 옵션을 무조건 사용하세요.


    4. 로그인 (login.php)

    입력한 아이디와 비밀번호가 맞는지 확인하고, 맞으면 서버의 '금고(세션)'에 로그인 기록을 남깁니다.

    <?php
    declare(strict_types=1);
    session_start();
    require 'db.php';
    
    // 이미 로그인된 상태라면 굳이 여기 있을 필요가 없으므로 메인으로 보냅니다.
    if (isset($_SESSION['user_id'])) {
        header('Location: index.php');
        exit;
    }
    
    $error = '';
    
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        $email    = trim($_POST['email']    ?? '');
        $password = trim($_POST['password'] ?? '');
    
        if ($email === '' || $password === '') {
            $error = '이메일과 비밀번호를 모두 입력해주세요.';
        } else {
            $pdo  = getPdo();
            $stmt = $pdo->prepare("SELECT id, name, password FROM users WHERE email = :email");
            $stmt->execute([':email' => $email]);
            $user = $stmt->fetch();
    
            // 1. 아이디가 DB에 존재하고, 
            // 2. 폼에서 입력한 비밀번호와 DB의 암호화된 비밀번호가 서로 짝이 맞는다면?
            if ($user && password_verify($password, $user['password'])) {
                
                // [핵심 보안] 세션 고정(Session Fixation) 공격을 막기 위해 로그인 성공 즉시 열쇠표(세션 ID)를 갈아치웁니다.
                session_regenerate_id(true); 
                
                // 세션에 내 정보를 담습니다. (이로써 로그인이 완료됩니다)
                $_SESSION['user_id']   = $user['id'];
                $_SESSION['user_name'] = $user['name'];
    
                header('Location: index.php');
                exit;
            } else {
                // "아이디가 틀렸습니다" 혹은 "비번이 틀렸습니다"라고 구체적으로 알려주지 마세요. 해커에게 힌트가 됩니다.
                $error = '이메일 또는 비밀번호가 올바르지 않습니다.';
            }
        }
    }
    ?>
    <!DOCTYPE html>
    <html lang="ko">
    <head><meta charset="UTF-8"><title>로그인</title></head>
    <body>
    <h2>로그인</h2>
    
    <?php if (isset($_GET['registered'])): ?>
        <p style="color:green;">회원가입이 완료되었습니다. 로그인해 주세요!</p>
    <?php endif; ?>
    
    <?php if ($error !== ''): ?>
        <p style="color:red;"><?= htmlspecialchars($error, ENT_QUOTES, 'UTF-8') ?></p>
    <?php endif; ?>
    
    <form method="post">
        <p>이메일: <input type="email" name="email" required></p>
        <p>비밀번호: <input type="password" name="password" required></p>
        <p><input type="submit" value=" 로그인 "></p>
    </form>
    <p><a href="register.php">회원가입</a></p>
    </body>
    </html>
    

    5. 출입증 검사소 (auth.php) 와 메인 페이지 (index.php)

    회원 전용 게시판이나 마이페이지 등 로그인한 사람만 볼 수 있는 페이지에는 최상단에 항상 출입증 검사소(auth.php) 코드를 세워두어야 합니다.

    [auth.php - 출입증 검사]

    <?php
    declare(strict_types=1);
    session_start();
    
    // 세션 금고에 'user_id' 정보가 없다면? (로그인을 안 했다면)
    if (!isset($_SESSION['user_id'])) {
        // 가차 없이 로그인 페이지로 쫓아냅니다.
        header('Location: login.php');
        exit;
    }
    

    [index.php - 메인 페이지]

    <?php
    // 메인 페이지 맨 위에 경비원을 세워둡니다. 로그인 안 한 사람은 이 아래 코드를 절대 볼 수 없습니다.
    require 'auth.php';
    ?>
    <!DOCTYPE html>
    <html lang="ko">
    <head><meta charset="UTF-8"><title>환영합니다</title></head>
    <body>
        <h2>안녕하세요, <?= htmlspecialchars($_SESSION['user_name'], ENT_QUOTES, 'UTF-8') ?>님!</h2>
        <p>로그인에 성공하셨군요. 무사히 회원 전용 구역에 들어오셨습니다.</p>
        <a href="logout.php">[로그아웃]</a>
    </body>
    </html>
    

    6. 로그아웃 (logout.php)

    로그아웃은 단순히 버튼을 누른다고 끝나는 것이 아니라, 서버와 내 PC에 연결된 세션의 끈을 아주 완벽하고 산산조각 내버려야 합니다.

    <?php
    declare(strict_types=1);
    session_start();
    
    // 1. 서버 금고(세션 배열) 안의 내 데이터를 싹 비웁니다.
    $_SESSION = [];
    
    // 2. 내 브라우저에 남아있는 열쇠표(세션 쿠키)마저 빼앗아서 파기합니다.
    if (ini_get('session.use_cookies')) {
        $params = session_get_cookie_params();
        setcookie(
            session_name(), '', time() - 42000,
            $params['path'], $params['domain'],
            $params['secure'], $params['httponly']
        );
    }
    
    // 3. 서버에 존재하는 세션 파일 자체를 물리적으로 파괴합니다.
    session_destroy();
    
    header('Location: login.php');
    exit;
    

    마치며: 보안 체크리스트
    오늘 만든 로그인 시스템에는 웹 보안의 핵심 3대장이 모두 들어갔습니다.

    1. SQL 인젝션 방어: PDO Prepared Statement 사용
    2. 비밀번호 유출 방어: password_hash()로 암호화 저장
    3. 세션 하이재킹(탈취) 방어: 로그인 성공 즉시 session_regenerate_id() 호출

    회원 시스템은 겉보기엔 단순해 보이지만 이처럼 뒤에서 치열한 보안 로직이 돌아가야 합니다. 이 원칙들을 철저하게 지켜서 안전하고 튼튼한 웹 서비스를 만드시길 바랍니다!

    Last updated on 2026-4-19 by Myeongjin Cho
    ← 실전 프로젝트 4단계 - 게시판 레벨업 (댓글, 보안, 성능)
    • 1. 데이터베이스(DB) 설계
    • 2. 공통 DB 접속 파일 (db.php)
    • 3. 회원가입 (register.php)
    • 4. 로그인 (login.php)
    • 5. 출입증 검사소 (auth.php) 와 메인 페이지 (index.php)
    • 6. 로그아웃 (logout.php)
    커뮤니티
    PHP 공식 웹사이트한국 PHP 개발자 커뮤니티
    유용한 정보
    책 소스 코드
    Copyright © 2026 EZPHP.NET