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


- bawool이라는 DB에 회원정보를 담기 위한 members라는 테이블을 생성해 주었다.
- 기본 키를 사용해 주어 무결성을 유지하고, 각 레코드를 쉽게 식별하고 관리할 수 있게 해주었다.
- name과 email 필드는 Unique Key를 사용해 중복된 값이 들어올 수 없게 해주었고
created_at 필드를 이용해 사용자가 가입한 시간을 볼 수 있게 해준 후 모든 값에 NULL 값이 들어올 수 없게 설정해 주었다.
<!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로 리다이렉트 되는 걸 알 수 있다.
<?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; // 출력: <script>alert('XSS Attack!');</script>htmlspecialchars 사용하면 특수문자를 변환해 XSS공격을 방지할수있다.
<?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를 이용해 성공 메시지와 함께 로그인 페이지로 리다이렉트 되게 해주었다.
<?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를 연결하면서 에러가 많이 생겼다.. 에러는 다음 글에서 다뤄 보려고 한다!