[모의해킹 스터디] 2주차 과제

바울·2024년 10월 31일
0

모의해킹 스터디

목록 보기
4/40

2주차 과제

로그인 페이지(DB 연동) & 회원가입 페이지 만들기


기존에 만들었던 로그인 페이지를 DB에 회원정보가 있는 사용자만 로그인할 수 있게 하고 회원가입을 하면 DB에 가입한 정보가 저장될 수 있게 구현해 보았다.

Database & Table 구축

  • Database생성 (bawool)
  • Table생성 (members)
  • bawool이라는 DB에 회원정보를 담기 위한 members라는 테이블을 생성해 주었다.

  • 기본 키를 사용해 주어 무결성을 유지하고, 각 레코드를 쉽게 식별하고 관리할 수 있게 해주었다.

  • name과 email 필드는 Unique Key를 사용해 중복된 값이 들어올 수 없게 해주었고
    created_at 필드를 이용해 사용자가 가입한 시간을 볼 수 있게 해준 후 모든 값에 NULL 값이 들어올 수 없게 설정해 주었다.

index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Login Page</title>
    <link rel="stylesheet" href="styles.css">
</head>

<body>
    <div class="container">
        <h1>LOGIN</h1>
        <form action="proc/login_proc.php" method="POST">
            <label for="name">Username</label>
            <input type="text" name="name" required="">

            <label for="password">Password</label>
            <input type="password" name="passwd" required="">

            <button type="submit">Sign In</button>
        </form>
        <div class="signup-link">
            <p>Don't have an account? <a href="join.php">Sign up</a></p>
        </div>
    </div>
</body>

</html>

index.html 코드를 살펴보면 Sign up을 누르게 되면 join.php로 리다이렉트 되는 걸 알 수 있다.


join.php

<?php session_start(); ?>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Sign Up Page</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <div class="container">
        <h1>SIGN UP</h1>
        <form action="proc/join_proc.php" method="POST">
            <label for="username">Username</label>
            <input type="text" name="name" required="">
            <?php echo display_error('error_name'); ?>

            <label for="email">Email</label>
            <input type="email" name="email" required="">
            <?php echo display_error('error_email'); ?>

            <label for="password">Password</label>
            <input type="password" name="passwd" required="">
            <?php echo display_error('error_password_format'); ?>

            <label for="confirm_password">Confirm Password</label>
            <input type="password" name="confirm_passwd" required="">
            <?php echo display_error('error_password'); ?>

            <button type="submit">Sign Up</button>
        </form>
        <div class="signup-link">
            <p>Already have an account? <a href="index.html">Log in</a></p>
        </div>
    </div>
</body>
</html>

<?php
function display_error($error_key) {
    if (isset($_SESSION[$error_key])) {
        $error_message = htmlspecialchars($_SESSION[$error_key]);
        unset($_SESSION[$error_key]);
        return "<small style='color: red;'>$error_message</small>";
    }
    return '';
}
?>

회원가입 로직에서 회원가입 시 생길 수 있는 에러를 설정 후 에러를 SESSION 변수로 가져와 그에 맞는 에러 처리를 해주기 위해 기존 페이지에 php 코드를 추가해 준 후에 반복되는 코드들은 가독성을 위해 따로 함수로 만들어서 사용해 주었다.

XSS공격 방지를 위해 htmlspecialchars를 사용해 주었다.

$user_input = "<script>alert('XSS Attack!');</script>";
$safe_output = htmlspecialchars($user_input);
echo $safe_output; // 출력: &lt;script&gt;alert('XSS Attack!');&lt;/script&gt;

htmlspecialchars 사용하면 특수문자를 변환해 XSS공격을 방지할수있다.


join_proc.php

<?php
session_start(); // 세션 시작

// 데이터베이스 연결 설정
define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASSWORD', '1');
define('DB_NAME', 'bawool');

// MySQLi 연결 생성
$conn = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);

// 연결 오류 확인
if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}

// 입력값 검증 함수
function validate_input($name, $email, $passwd, $confirm_passwd) {
    if (empty($name) || strlen($name) < 3) {
        $_SESSION['error_name'] = "사용자 이름은 3자 이상이어야 합니다.";
        return false;
    }

    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $_SESSION['error_email'] = "유효한 이메일 주소를 입력하세요.";
        return false;
    }

    $pattern = '/^(?=.*\d)(?=.*[!@#$%^&*])[A-Za-z\d!@#$%^&*]{8,}$/'; 
    if (!preg_match($pattern, $passwd)) {
        $_SESSION['error_password_format'] = "비밀번호는 최소 8자 이상, 특수문자와 숫자를 포함해야 합니다.";
        return false;
    }

    if ($passwd !== $confirm_passwd) {
        $_SESSION['error_password'] = "패스워드가 일치하지 않습니다.";
        return false;
    }

    return true;
}

// POST 요청으로 받은 사용자 입력 데이터 가져오기
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $name = trim($_POST['name']);
    $email = trim($_POST['email']);
    $passwd = trim($_POST['passwd']);
    $confirm_passwd = trim($_POST['confirm_passwd']);

    // 입력값 검증
    if (!validate_input($name, $email, $passwd, $confirm_passwd)) {
        header("Location: /join.php");
        exit();
    }

    // 비밀번호 해싱
    $hashed_passwd = password_hash($passwd, PASSWORD_DEFAULT);

    // 중복 확인 및 회원가입 처리 함수
    function check_duplicates_and_insert($conn, $name, $email, $hashed_passwd) {
        // 중복 확인
        $sql_check = "SELECT * FROM members WHERE name = ? OR email = ?";
        $stmt_check = $conn->prepare($sql_check);
        $stmt_check->bind_param("ss", $name, $email);
        $stmt_check->execute();
        $result = $stmt_check->get_result();

        if ($result->num_rows > 0) {
            $existing_user = $result->fetch_assoc();
            if ($existing_user['name'] === $name) {
                $_SESSION['error_name'] = "이미 존재하는 아이디입니다.";
            } else {
                $_SESSION['error_email'] = "이미 존재하는 이메일입니다.";
            }
            $stmt_check->close(); // stmt_check 닫기
            return false; // 중복이 있는 경우
        }

        // 회원가입 처리
        $sql_insert = "INSERT INTO members (name, email, passwd, created_at) VALUES (?, ?, ?, NOW())";
        $stmt_insert = $conn->prepare($sql_insert);
        $stmt_insert->bind_param("sss", $name, $email, $hashed_passwd);

        $result = $stmt_insert->execute(); // 성공 여부 반환
        $stmt_insert->close(); // stmt_insert 닫기
        return $result;
    }

    // 회원가입 처리
    if (!check_duplicates_and_insert($conn, $name, $email, $hashed_passwd)) {
        header("Location: /join.php");
        exit();
    }

    echo "<script>alert('회원가입에 성공했습니다!'); window.location.href = '/index.html';</script>";
}

// 자원 해제
$conn->close();
?>
  • 회원가입 로직을 하나하나 살펴보면 DB 연결에 필요한 설정은 변경될 가능성이 낮고 가독성을 위해 변수 대신 상수로 정의해 주었다.

  • 비밀번호 정규식 등 에러 처리는 함수를 이용해 가독성 있게 처리해 주었다.

  • 회원가입 시 POST로 요청받은 데이터를 가져올 시 trim함수를 사용해 공백(스페이스, 탭, 줄바꿈 등)을 제거해 불필요한 공백으로 인해 발생할 수 있는 오류를 방지해 주었다.

  • 아이디 및 이메일 중복 확인을 하는 부분도 역시 함수를 이용해 작성해 주었다.

  • 회원가입 처리를 하는 부분에서 prepared statements를 사용해 SQL 쿼리와 데이터가 분리되어 처리하게 하여 SQL 인젝션 공격을 방지해 주었다.

  • 회원가입 성공 시 alert를 이용해 성공 메시지와 함께 로그인 페이지로 리다이렉트 되게 해주었다.

login_proc.php

<?php
session_start(); // 세션 시작

// 데이터베이스 연결 설정
define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASSWORD', '1');
define('DB_NAME', 'bawool');

// MySQLi 연결 생성
$conn = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);

// 연결 오류 확인
if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}

// 입력값 검증 함수
function validate_input($name, $passwd) {
    if (empty($name) || empty($passwd)) {
        throw new Exception("아이디와 비밀번호를 모두 입력하세요.");
    }
}

// 로그인 처리 함수
function login_user($conn, $name, $passwd) {
    // SQL 쿼리 생성 및 준비
    $sql = "SELECT name, passwd FROM members WHERE name = ?";
    $stmt = $conn->prepare($sql);
    $stmt->bind_param("s", $name); // 사용자 입력값 바인딩

    // 쿼리 실행
    $stmt->execute();
    $result = $stmt->get_result();

    // 결과 확인
    if ($result->num_rows > 0) {
        $row = $result->fetch_assoc();
        // 입력된 비밀번호와 저장된 해시값을 비교
        if (password_verify($passwd, $row['passwd'])) {
            // 세션 시작 및 로그인 정보 저장
            $_SESSION['LoginID'] = $name; // 사용자의 이름 저장
            header("Location: /login.php"); // 로그인 성공 시 login.php로 이동
            exit();
        } else {
            throw new Exception("아이디 또는 비밀번호가 잘못 되었습니다.");
        }
    } else {
        throw new Exception("아이디 또는 비밀번호가 잘못 되었습니다.");
    }
}

// POST 요청으로 받은 사용자 입력 데이터 가져오기
$name = trim($_POST['name']);
$passwd = trim($_POST['passwd']);

// 예외 처리
try {
    validate_input($name, $passwd); // 입력값 검증
    login_user($conn, $name, $passwd); // 로그인 처리
} catch (Exception $e) {
    echo "<script>
            alert('" . $e->getMessage() . "');
            window.location.href = '/index.html';
          </script>";
} finally {
    // 연결 종료
    if (isset($stmt)) {
        $stmt->close();
    }
    $conn->close();
}
?>
  • 로그인 로직에서는 try, catch, finally를 이용해 예외 처리를 해주었다.

  • throw 키워드를 사용하여 새로운 예외를 발생시킬 수 있다. 이때 Exception 객체를 생성하고, 오류 메시지를 전달한다. 이로 인해 코드 실행이 중단되고, 해당 예외는 가까운 catch 블록으로 전달된다.

  • try 블록 안에는 예외가 발생할 가능성이 있는 코드를 작성해 주었다.

  • try 블록에서 예외가 발생하면, 그 예외를 catch 블록에서 잡아 처리한다.

  • finally 블록은 try 블록에서 예외가 발생하든 발생하지 않든 항상 실행되는 코드이다.

회원가입 & 로그인

직접 회원가입을 해보자!

name = bawool, email = bawool@google.com으로 회원가입을 해보았다.

회원가입은 무사히 된 거 같다. DB에 값이 들어왔는지 확인해 보자!

DB에도 데이터가 무사히 저장되었다! 에러 확인을 위해 같은 이름으로 로그인을 해보자

이미 DB에 존재하는 bawool이라는 이름으로 회원가입을 해보았는데 중복된 이름이라 에러가 나오는 것을 볼 수 있다. 이제 가입한 아이디로 로그인을 해보자

로그인을 하면 자신의 아이디가 출력되고 my page랑 로그아웃을 할 수 있는 버튼이 생기는 페이지로 이동하는 것을 볼 수 있다! 만약 잘못된 정보를 입력하면 어떻게 되는지 확인해 보자

잘못된 정보를 입력하면 미리 설정해둔 에러가 나는 것을 볼 수 있다!

이번에 DB를 연결하면서 에러가 많이 생겼다.. 에러는 다음 글에서 다뤄 보려고 한다!

0개의 댓글