320x100

SQL 연산의 우선순위

우선순위 연산자 설명
1 () 괄호 (가장 우선, 내부부터 먼저 계산)
2 +, -, ~ 단항 연산자 (예: -5, +x)
3 *, /, % 곱셈, 나눗셈, 나머지
4 +, - 덧셈, 뺄셈
5 =, >, <, >=, <=, <>, !=, IS, LIKE, BETWEEN, IN 비교 연산자
6 NOT 논리 NOT
7 AND 논리 AND
8 OR 논리 OR

 

SQL에서는 **문자열(string)**을 비교할 때는 항상 '작은 따옴표(single quotes)'로 감싸줘야한다.

 

SELECT * FROM board WHERE authr = '$author' AND [조건]

 

 

로그인 로직

(1) 식별 / 인증 동시

(2) 식별 / 인증 분리

(3) 식별 / 인증 동시 + 해시

(4) 식별 / 인증 분리 + 해시

 


암호화 (Encryption)

  • 목적: 데이터를 보호하기 위해 읽을 수 없는 형태로 변환
  • 특징:
    • 암호화된 데이터를 다시 원래대로 복호화할 수 있음
    • 복호화하려면 KEY(열쇠) 가 필요함
  • 예시: AES, RSA, DES 등
  • 사용처: 로그인 정보 보호, HTTPS 통신 등
  • “암호화 → 복호화 가능 (KEY 필요)”

인코딩 (Encoding)

  • 목적: 데이터를 다른 시스템이나 네트워크에서도 안정적으로 전송하기 위해 변환
  • 특징:
    • 본질적으로 보안 목적은 아님
    • 누구나 규칙만 알면 디코딩(원상복원) 가능
  • 예시: Base64, URL Encoding, UTF-8 등
  • 사용처: 이메일 전송, 웹에서 특수문자 처리, 파일 저장 등
  • “인코딩 → 디코딩 가능 (공개된 규칙)”

해시 (Hash)

  • 목적: 입력값에 대한 고정된 길이의 고유 값 생성
  • 특징:
    • 복호화 불가능 (한 번 해시하면 되돌릴 수 없음)
    • 같은 입력 → 항상 같은 해시값
    • 입력이 조금만 달라도 완전히 다른 값이 나옴
  • 예시: SHA-256, MD5, bcrypt 등
  • 사용처: 비밀번호 저장, 무결성 검사, 디지털 서명 등
  • “해시 → 복호화 불가 (단방향 변환)”

🔐 로그인 유지 방법 (쿠키 VS 세션)

✅ 1. 쿠키 (Cookie)

  • 클라이언트(브라우저)에 저장되는 작은 데이터 조각
  • 사용자가 로그인하면, 로그인 정보를 쿠키에 저장할 수 있음
  • 다음 요청 시 브라우저가 쿠키를 자동으로 서버에 전송함

 단점:

  • 클라이언트 쪽에 저장됨 → 쉽게 조작 가능
  • (ex: 쿠키 값을 바꿔서 다른 사람처럼 가장할 수 있음)
  • 쿠키가 유출되면 해커가 그대로 로그인 상태를 훔쳐갈 수 있음 (XSS, 탈취 등)

 

✅ 2. 세션 (Session)

  • 서버에 저장되는 로그인 정보
  • 사용자는 브라우저에 세션 ID(식별자) 만 가지고 있음 (보통 쿠키에 담김)
  • 서버는 이 ID로 사용자의 정보를 찾아냄

장점:

  • 민감한 정보는 서버에만 저장되기 때문에 보안이 더 좋음
  • 클라이언트가 세션 ID만 가지고 있어도, 서버가 로그인 상태를 유지해줌

예시 흐름:

  1. 로그인 성공 → 서버에서 세션 생성 + 세션 ID 부여
  2. 클라이언트는 그 ID를 쿠키로 저장 (Set-Cookie: PHPSESSID=xyz)
  3. 이후 요청마다 자동으로 서버에 세션 ID가 전달됨
  4. 서버는 해당 ID로 사용자 정보 확인 후 로그인 유지

 

✅ 1. 세션 ID를 노멀하게 저장

✅ 2. 세션이 저장되어 있는 위치 (서버 쪽)

/var/lib/php/sessions/

✅ 3. 세션을 직접 보면 → 서버에 저장된 것이라는 걸 알 수 있다

 


JWT (JSON Web Token)

 

웹에서 사용자 인증과 정보 전달을 위해 사용되는 토큰 기반 인증 방식

사용자 인증 정보를 안전하게 주고받기 위한 디지털 토큰

 

 

🔐 JWT의 구조

JWT는 크게 3부분으로 나뉘어 있다

 

Header.Payload.Signature

 

 

  1. Header (헤더)
    • 어떤 알고리즘으로 서명했는지 등의 정보가 담김
    • 예: {"alg": "HS256", "typ": "JWT"}
  2. Payload (페이로드)
    • 실제로 전달할 정보(예: 사용자 ID, 권한 등)가 담김
    • 민감한 정보는 넣으면 안 됨 (암호화되지 않음)
  3. Signature (서명)
    • 위 두 부분을 비밀키로 서명해서 위변조 여부를 확인할 수 있게 함
    • 서버가 이걸 보고 “이 토큰이 진짜인가?” 판단함

 

🔄 JWT는 어떻게 쓰여?

  1. 사용자가 로그인하면 서버가 JWT를 생성해서 사용자에게 전달
  2. 사용자는 이후 요청마다 JWT를 함께 보냄 (보통 Authorization: Bearer <token> 헤더에)
  3. 서버는 그 JWT를 검증해서 사용자가 누구인지 파악함

 

✅ JWT의 장점

  • 서버가 세션을 따로 저장하지 않아도 됨무상태(stateless)
  • 여러 서비스 간(마이크로서비스 등) 토큰 하나로 인증 공유 가능
  • 구조가 단순하고, 사용하기 쉬움

 

⚠️ JWT 쓸 때 주의할 점

  • Payload는 암호화가 안 됨, 민감 정보는 넣지 마!
  • 토큰이 탈취되면 위험 → HTTPS는 필수
  • 만료 시간 설정은 꼭 하자 (유효기간 없이 무한 사용은 위험)

 

JWT 예제 코드 (Python + PyJWT 라이브러리)

import jwt
import datetime

# 비밀 키 (서명에 사용됨)
SECRET_KEY = "mysecret"

# 1. JWT 생성하기
payload = {
    "user_id": 123,
    "role": "admin",
    "exp": datetime.datetime.utcnow() + datetime.timedelta(hours=1)  # 만료시간
}

token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
print("JWT Token:", token)

# 2. JWT 검증하기
try:
    decoded = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
    print("Decoded Payload:", decoded)
except jwt.ExpiredSignatureError:
    print("토큰이 만료됐습니다.")
except jwt.InvalidTokenError:
    print("유효하지 않은 토큰입니다.")

 

이 코드는 JWT를 만들고, 다시 해석(검증)하는 전체 흐름을 보여줘. PyJWT는 Python에서 자주 쓰이는 JWT 라이브러리이다.

 

 

🕒 "exp": datetime.datetime.utcnow() + datetime.timedelta(hours=1)

이건 JWT의 만료 시간(exp) 을 설정하는 코드

  • exp는 **“expiration”**의 줄임말로, 이 토큰이 언제까지 유효한지를 의미해.
  • JWT는 exp 값을 넣으면, 서버가 자동으로 이 시간이 지나면 토큰을 거부하게 돼.

코드 분석

  • datetime 모듈 안에는 또 datetime이라는 클래스가 있다.
  • utcnow()는 현재 시각을 UTC 기준(협정 세계시) 으로 가져오는 함수

timedelta

  • time delta”**의 축약형이야. 여기서 **delta(델타)**는 수학/과학에서 자주 쓰는 단어인데, 의미는 “차이, 변화량

🔐 algorithms=["HS256"]

 HS256은 대칭키 방식 (secret key 하나), RS256은 비대칭키 방식 (public/private key

 


더보기

3주차 과제

  1. 오늘 수업 복습
  • 로그인 로직 이해 (식별/인증)
  1. 지난 과제 ( 특별과제 제외)
  2. 로그인 페이지 만들기 (로직 4개)
  • 식별/인증 동시
  • 식별/인증 분리
  • 식별/인증 동시 + 해시
  • 식별/인증 분리 + 해시

로그인1 / 2 / 3 / 4 네가지 개발

  1. 추가과제
  • jwt 찾아보기
  • jwt 토큰 직접 구현해보기, 로그인 만들어보기

 

 

로그인 페이지 구현 (4가지)

login.php

 

핵심 ///   name="login_type" value="식별/인증 동시"

  • name 을 login_type 으로 통일시켰고, value 를 통해 실행 구분
  • login_prcc.php 
    • name = $type 으로 연결
    • value == case 으로 연결 
<!DOCTYPE html>
<html lang="ko">
<head>
        <meta charset="UTF-8">
        <title>Login Page</title>

        <style>
          body {
                display: flex; /* 내부 요소를 정렬할 수 있게 함(기본 정렬 방식 대신 유연한 배치가 가능해짐) */
                justify-content: center; /* 가로 방향 가운데 정렬*/
                align-items: center; /* 세로 방향 가운데 정렬*/
                height: 100vh; /* 화면 전체 높이        
                                100vh는 화면 높이의 100%를 의미함 → 페이지 전체를 꽉 채워서 가운데 배치 가능 */
                margin: 0; /*기본 브라우저 여백을 없애서 딱 맞게 배치*/
                background-color: #f0f2f5;
                }

          .container {
                text-align: center;
                padding: 40px; /*박스 안쪽 여백을 40픽셀로 설정해서 넉넉한 공간 확보*/
                border-radius: 12px; /* 박스 모서리를 둥글게*/

                     }

         img {
                width: 500px; /*이미지 가로 너비를 500픽셀로 고정한다*/
                border-radius: 12px; /* 이미지 모서리를 둥글게*/
                margin-bottom: 20px; /* 이미지 아래쪽 공간에 20px 추가해서 폼 사이의 여백 추가*/
             }
        </style>
</head>


<body>
        <div class="container">
                <!-- src 이미지를 가져오는데 만약 못불러올 경우 alt 내용을 출력하고 사진의 크기는 500으로 가져와라 -->
          <img src="search.pstatic.jpeg" alt="Login Banner" width="500" />

          <form method="POST" action="login_prcc.php">
                <!-- 서버에서 name 또는 value 이름으로 값을 받을 수 있음, placeholder는 입력창에 흐리게 표시되는 글자 역할-->
                <input type="text" name="id" placeholder="User ID" />
                <input type="password" name="password" placeholder="User Password" />
                <input type="submit" value="Login" />
                <input type="submit" name="login_type" value="식별/인증 동시" style="padding: 20px;"/>
                <input type="submit" name="login_type" value="식별/인증 분리" style="padding: 20px;"/>
                <input type="submit" name="login_type" value="식별/인증 동시 + 해시" style="padding: 20px;"/>
                <input type="submit" name="login_type" value="식별/인증 분리 + 해시" style="padding: 20px;"/>
          </form>

          <form method="POST" action="register.php" style="padding-top: 20px;">
                <input type="submit" value="회원가입" />
          </form>

        </div>
</body>
</html>
~                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         
~

 

 

login_prcc.php

 

$_SERVER["REQUEST_METHOD"]

$_SERVER는 PHP에서 제공하는 슈퍼 글로벌 변수 중 하나예요.

→ 이건 서버와 클라이언트(브라우저) 사이에 오가는 요청 정보, 환경 정보, 헤더 정보 등을 담고 있는 연관 배열이에요.

<?php
session_start();

// DB 연결
$conn = new mysqli("localhost:3306", "choyongyeop", "1234", "test");
if ($conn->connect_error) {
    die("DB 연결 실패: " . $conn->connect_error);
}

 // POST 방식으로 전송된 요청일 때만 실행됨
if ($_SERVER["REQUEST_METHOD"] === "POST" && isset($_POST['login_type'])) {
    $type = $_POST["login_type"];

    switch ($type) {
     case "식별/인증 동시":
             if(isset($_POST['id']) && isset($_POST['password'])) {
               $id = $_POST['id'];
               $pw = $_POST['password'];

               $sql = "SELECT * from users where id = '$id' AND password = '$pw' ";
               $result = $conn->query($sql);

               if ($result && $result->num_rows>0) {
                               $_SESSION['id'] = $id;
                               header("Location: login_succ.php");
                               exit;
               }
              header("Location: login_fail.php");
              exit;
               }
             break;

   case "식별/인증 분리":
    if (isset($_POST['id']) && isset($_POST['password'])) {
        $id = $_POST['id'];
        $pw = $_POST['password'];

        // 1단계: ID가 존재하는지 확인
        $sql = "SELECT * FROM users WHERE id = '$id'";
        $result = $conn->query($sql);

        if ($result && $result->num_rows > 0) {
            $user = $result->fetch_assoc();

            // 2단계: PW가 같은지 확인 (해시 사용 안 하는 버전)
            if ($user['password'] === $pw) {
                $_SESSION['id'] = $id;

                header("Location: login_succ.php");
                exit;
            }
        }
              header("Location: login_fail.php");
              exit;
        }
        break;

     case "식별/인증 동시 + 해시":
             if(isset($_POST['id']) && isset($_POST['password'])) {
               $id = $_POST['id'];
               $pw = $_POST['password'];

               $sql = "SELECT * from users where id = '$id' ";
               $result = $conn->query($sql);

               if ($result && $result->num_rows>0) {
                       $user = $result->fetch_assoc();

                       if (password_verify($pw, $user['password'])) {
                               $_SESSION['id'] = $id;

                               header("Location: login_succ.php");
                               exit;
                       }
               }
              header("Location: login_fail.php");
              exit;
             }
             break;

     case "식별/인증 분리 + 해시" :
             if(isset($_POST['id']) && isset($_POST['password'])) {
               $id = $_POST['id'];
               $pw = $_POST['password'];

               $sql = "SELECT * from users where id = '$id'";
               $result = $conn->query($sql);

               if ($result && $result->num_rows>0) {
                       $user = $result->fetch_assoc();

                       if (password_verify($pw, $user['password'])) {
                               $_SESSION['id'] = $id;

                               header("Location: login_succ.php");
                                  exit;
                      }
                }
              header("Location: login_fail.php");
              exit;
              }
              break;

     default :
             echo "알 수 없는 로그인 방식입니다.";
        }
}


// 폼이 제출되었을 때만 처리
// 사용자의 id 값과 password 값이 존재할 경우, 전달된 값을 id와pw에 변수에 저장한다.
if (isset($_POST['id']) && isset($_POST['password'])) {
    $id = $_POST['id'];
    $pw = $_POST['password'];

    // DB에서 해당 ID의 사용자 정보 조회
    $sql = "SELECT * FROM users WHERE id = '$id'";
    $result = $conn->query($sql); // 위에서 만든 sql쿼리를 실제로 mysql에 실행하는 부분

    if ($result->num_rows > 0) {  // 사용자가 입력한 아이디가 DB에 존재하면 실행
        $user = $result->fetch_assoc(); // DB 결과를 배열로 가져오기

        // 비밀번호 확인
      if (password_verify($pw, $user['password'])) {
            $_SESSION['id'] = $id;  // 세션 저장
            header("Location: login_succ.php");  // 로그인 성공
            exit;
        } else {
            header("Location: login_fail.php");  // 비밀번호 틀림
            exit;
        }
    }
}

?>

~

 

 

결과화면

300x250
320x100

Client서버에서 Local 서버로 SSH 연결 중 에러 발생

인터넷 연결 여부 확인

→ ping이나 traceroute 명령어로 연결 여부를 점검합니다.

. SSH 서비스 상태 점검

  • 서버 관리자가 SSH 서비스를 제대로 켰는지 점검해야 합니다.

서버 측에서 SSH 서비스 상태 점검

sudo systemctl status sshd

→ ssh.service 서비스를 찾을수없다고 나옴

SSH 서비스가 설치되지 않았거나 없는 경우:

SSH가 설치되지 않은 경우라면, 아래 방법으로 설치합니다.

  • Ubuntu/Debian:
sudo apt update
sudo apt install openssh-server
sudo systemctl enable --now ssh

SSH 서비스가 설치완료

sudo systemctl start ssh

로컬에서 SSH 접속 확인하기

이제 터미널에서 실제로 SSH 접속을 시도해 봅니다.

 

확인할 사항: 방화벽 설정

SSH 접속을 허용하려면 방화벽 설정도 확인해야 합니다.

  • Ubuntu (ufw 사용하는 경우)

sudo ufw allow ssh

 

성공

300x250
320x100

🧾 MySQL 설치 오류 리포트

✅ 시스템 정보

OS: Ubuntu (ARM64 기반)

환경: LAMP 스택 구성 중 (Apache, PHP, MySQL)

문제 발생 시점: mysql-server 설치 이후 systemctl로 실행 시도

❗ 오류 코드

error 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)

systemctl 로그:

mysql.service: Failed with result 'exit-code'
/etc/init.d/mysql: WARNING: /etc/mysql/my.cnf cannot be read.

🔍 원인 분석

• MySQL이 정상 설치되지 않았거나, 중간에 손상되면서 /etc/mysql/my.cnf 설정 파일이 없거나 읽을 수 없는 상태로 유지됨.

• systemctl status mysql 시도 시 설정 파일 미존재로 인해 mysqld 데몬이 시작 실패.

• 이 상태에서 mysql 명령어는 소켓(/tmp/mysql.sock)을 찾지 못해 error 2002 발생.

• 추가적으로 dpkg -l | grep mysql 결과, MySQL 관련 패키지 일부가 남아 있었고, mariadb-common 도 섞여 있어 패키지 간 충돌 가능성 존재.


🛠 해결 방법

  1. 다음 명령어로 관련 패키지 전부 삭제: (DB가 있을 시 X)
sudo apt purge dbconfig-mysql libmysqlclient-dev libmysqlclient21 mysql-client-8.0 mysql-client-core-8.0 mysql-common mysql-server mysql-server-8.0 mysql-server-core-8.0 php-mariadb-mysql-kbs php-mysql php8.3-mysql mariadb-common -y
  1. 설정, 데이터, 로그 파일 모두 수동 삭제:
sudo rm -rf /etc/mysql /var/lib/mysql /var/log/mysql* /var/run/mysqld
  1. 시스템 정리:
sudo apt autoremove -y
sudo apt autoclean
  1. 재설치:
sudo apt update
sudo apt install mysql-server -y

✅ 결과

• MySQL이 정상적으로 설치되고 실행됨

• systemctl status mysql → active (running)

• sudo mysql 접속 가능

• PHP 연동 테스트 및 웹서버 정상 작동 확인


💡 결론 및 예방 조치

• 설치 중단, 꼬인 설치, 의존성 충돌 발생 시 불완전한 설정 파일 상태로 남을 수 있음.

• MySQL이나 MariaDB 같은 DB 서비스는 설정 파일이 필수이므로 my.cnf 경로 오류 시 바로 점검 필요.

• dpkg -l | grep mysql 으로 설치된 패키지 현황 확인 및 정리 후 재설치가 가장 깔끔한 해결 방법임.

300x250
320x100

 

✅ WEB - WAS - DB 구조 요약

📌 역할 구분

  • WEB 서버 (Apache, Nginx 등)→ 클라이언트의 요청을 처리하고, 필요한 경우 WAS에 전달
  • → 정적 페이지(.html, .jpg, .css 등) 제공
  • WAS (Web Application Server)→ 비즈니스 로직 처리, DB 연동 등 수행
  • → 동적 페이지(.php, .jsp, .asp 등) 처리
  • DB (MySQL, MariaDB 등)→ WAS가 데이터를 요청하고 처리 결과를 돌려줌
  • → 실제 데이터 저장소

 

✅ 웹 루트 경로 (기본)

  • 기본적으로 /var/www/html
  • → 웹 서버는 이 폴더를 기준으로 파일을 찾아 응답

 

✅ 응답 헤더 설정

  • header() 함수 사용 → 응답의 헤더를 직접 설정 가능
  • 코드 마지막에 exit; 을 적으면 실행이 종료되어 아래쪽에 있는 코드가 보이지 않는다. (보안상 필요)

 

 

⚠️ 에러 디버깅 방법

  • PHP 에러 출력 켜기

ini_set('display_errors', 1);
error_reporting(E_ALL);

 

→ 몇 번째 줄에서 어떤 오류가 발생했는지 알려줌

 

 

  • echo 디버깅

echo "여기까지 실행됨";

 

→ 코드 흐름을 확인하거나 값 확인할 때 사용

 

 

 

   의 역할 (PHP)

  • PHP에서 .문자열을 이어주는 연산자입니다.
  • JavaScript의 +, Python의 +와 비슷한 역할이지만, PHP에서는 +를 문자열 연결에 사용하지 않고 반드시 .을 써야 합니다.

 

 


 

✅ phpMyAdmin에서 테이블 만들기 예시

 

 

1️⃣ 사용자 로그인

  • 브라우저에서 phpMyAdmin 접속:

http://localhost/phpmyadmin

 

  • Username: test
  • Password: (설정한 비밀번호 입력)

 

2️⃣ 데이터베이스 생성 (또는 선택)

  • 좌측 메뉴에서 “New” 클릭 → 새로운 데이터베이스 생성
  • 이름: test
  • Collation: utf8_general_ci
  • ➡️ “Create” 클릭

 

3️⃣ 테이블 생성

  • 데이터베이스 test 선택
  • 새 테이블 이름 입력: score_table
  • 필드 수 입력: 4
  • ➡️ “Go” 클릭

 

 

필드 이름 타입길이 /값 인덱스 AI (자동 증가)
idx INT 10 PRIMARY ✅ 체크
name VARCHAR 20 없음  
score VARCHAR 20 없음  
pass VARCHAR 20 없음  
  • idx 필드는 고유 식별자 역할 → PRIMARY 키 설정, A_I(Auto Increment) 체크

 

 

✅ SQL 기본 명령어 정리

 

🔍 1. SELECT 문 — 데이터를 조회할 때 사용

SELECT [컬럼이름] FROM [테이블이름];

 

예시: SELECT name, score FROM score_table;

score_table 테이블에서 namescore 컬럼의 값을 가져옴

 

 

전체 조회하려면?

SELECT * FROM score_table;

 

 

📝 2. INSERT 문 — 데이터를 추가할 때 사용

INSERT INTO [테이블이름] (컬럼1, 컬럼2, ...) VALUES (값1, 값2, ...);

 

예시: INSERT INTO score_table (name, score, pass) VALUES ('Alice', '90', 'Pass');

name, score, pass 컬럼에 각각 ‘Alice’, ‘90’, ‘Pass’ 값을 추가

⚠️ idxAuto Increment로 자동 증가되므로 따로 넣지 않아도 됨!

 

컬럼이름을 *(전체)로 넣을때는 아래와 같이 적지 않고 바로 value로 넘어간다.

 

 

 

 

✅ 정교한 SELECT 문 - WHERE 조건 사용

SELECT [컬럼명] FROM [테이블명] WHERE [조건];

 

📌 조건이 하나일 때

SELECT * FROM score_table WHERE name = 'normaltic';

 

 

📌 조건이 여러 개일 때

 

AND: 모든 조건이 참일 경우

SELECT * FROM score_table 
WHERE name = 'normaltic' AND pass = 'Pass';

 

 

OR: 하나라도 조건이 참이면

SELECT * FROM score_table 
WHERE name = 'normaltic' OR pass = 'Pass';

 

 

 

✅ PHP - MySQL 연동 예제 (db_test.php)

<?php

// 데이터베이스 접속 정보 정의
define('DB_SERVER', 'localhost');       // DB 서버 주소
define('DB_USERNAME', 'admin');         // DB 아이디
define('DB_PASSWORD', 'admin1234');     // DB 비밀번호
define('DB_NAME', 'test');              // 사용할 DB 이름

// DB 연결 시도
$db_conn = mysqli_connect(DB_SERVER, DB_USERNAME, DB_PASSWORD, DB_NAME);

// 연결 확인
if ($db_conn === false) {
    die("ERROR: Could not connect. " . mysqli_connect_error());
} else {
    echo "DB 연결 성공!";
}

?>

 


db_conn 이라는 변수에 넣어  mysqli_connect를 통해서 mysql과 연동하겠다.

if 만약 db_conn 변수가 연결이 되면 OK, 아닐경우, Fail 문구가 나오게 해라

sql 이라는 변수 안에 select를 통해 test_table 속 전체를 보여줘라

그 결과를 result에 담아라


MySQL 결과 중에서 맨 위의 하나의 행만 가져오는 방법으로 mysqli_fetch_array() 또는 mysqli_fetch_assoc()을 사용합니다.

 

함수 반환 형태 설명
mysqli_fetch_array() 연관 배열 + 숫자 인덱스 ["name"], [1] 둘 다 가능
mysqli_fetch_assoc() 연관 배열만 ["name"] 처럼 컬럼명으로만 접근

 var_dump() 란?

  • 변수에 어떤 값이 들어있는지, 데이터 타입까지 자세히 출력해주는 PHP 함수입니다.
  • 디버깅(오류 찾기)할 때 많이 사용돼요.

출력 결과

 

동일한 문구를 한번 더 쓰고 dump로 출력하면 두번째가 나온다

 

 

.은 두개의 문자열을 연결시켜주는 문구

[ ] = 객체를 가져오는 괄호

( ) = 함수 호출, 조건문, 수학 계산식 등 다양한 용도로 사용

 

여담

docker 를 날려도, db 파일은 안날아간다.

 

주석, 변수이름을 잘 적어야한다.

더보기

2주차 과제

[1] 복습 (Database, SQL)

[2] Mini Mission

학생 이름과 점수가 들어가있는 db를 만들고, get 방식으로 그 학생의 이름을 넣으면 학생의 점수가 출력되는 페이지 만들기

[3] 회원가입 페이지 만들기 (기능 구현) - 아이디, 비밀번호, 이름, 이메일 등

[4] 로그인 페이지 (DB 연동하기)

  • 추가 미션

→ 가입한 나의 정보를 보는 ‘마이페이지’ 개발 (비밀번호 안보이고, 이름, 회원가입 정보가 보이는 페이지)

 

부트스트랩 이용해서 아래 정도로만 하면 된다고 함

디자인 템플릿 다른사람이 만들어온거 가져와도 된다. CSS

그런데, 어려울 것이다.

빠르게 사이트 만드려면 부트스트랩 사용하는게 좋다.

개발자들도 만들어져있는 템플릿 틀을 가져와서 내 입맛대로 바꾼다.

 


 

✅ 회원가입 페이지 만들기

register.php

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

<head>
    <meta charset="UTF-8">
    <title>회원가입</title>
    <style>
        body {
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            background-color: #f0f2f5;
            font-family: 'Segoe UI', sans-serif;
        }
        .register-box form {
            display: flex;
            flex-direction: column;  /* ← 세로 정렬 */
            gap: 10px;               /* ← 입력창 사이 간격 */
        }
        .input-group {
            display: flex;
            align-items: center;
            margin-bottom: 16px;
        }

        .input-group label {
            width: 120px; /* ✅ 라벨 너비 고정해서 정렬 맞추기 */
            font-weight: bold;
            margin-right: 10px;
        }

        .input-group input {
            flex: 1; /* ✅ 남은 공간을 입력칸이 꽉 채우도록 */
            padding: 10px; /*입력 창 안에 내용과 테두리 사이 간격을 지정*/
            font-size: 16px;
            border-radius: 6px;
            border: 1px solid #ccc; /*테두리 두께 1px, 실선, 연한 회색*/
        }




    </style>
</head>

<body>

<div class="register-box">
    <h2>회원가입</h2>
    <form method="POST" action="register_process.php">
      <div class="input-group">
        <label for="name">이름</label>
        <input type="text" name="name" placeholder="이름" required />
      </div>


      <div class="input-group">
        <label for="id">아이디</label>
        <input type="text" name="id" placeholder="아이디" required />
      </div>

      <div class="input-group">
        <label for="password">비밀번호</label>
        <input type="password" name="password" placeholder="비밀번호" required />
      </div>

      <div class="input-group">
        <label for="password_confirm">비밀번호 확인</label>
        <input type="password" name="password_confirm" placeholder="비밀번호 확인" required />
      </div>


      <div class="input-group">
        <label for="email">이메일</label>
        <input type="email" name="email" placeholder="이메일" required />
      </div>

        <input type="submit" value="가입하기" />
    </form>
</div>

</body>
</html>

 

register_process.php

<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
// DB 연결
$conn = new mysqli("localhost:3306", "choyongyeop", "1234", "test");

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


// 입력값 받기
$name = $_POST['name'];
$id = $_POST['id'];
$pw = $_POST['password'];
$pw2 = $_POST['password_confirm'];
$email = $_POST['email'];

// 비밀번호 일치 확인
if ($pw !== $pw2) {
    die("❌ 비밀번호가 일치하지 않습니다.");
}
// ✅ 바로 여기에 암호화 코드 추가!
$hashed_pw = password_hash($pw, PASSWORD_DEFAULT);


$sql = "INSERT INTO users (name, id, password, email) VALUES ('$name', '$id', '$hashed_pw', '$email')";
$result = $conn->query($sql);

// 결과 확인
if ($result) {
    echo "✅ 회원가입이 완료되었습니다!";
} else {
    echo "❌ 회원가입 실패: " . $conn->error;
}

$conn->close();
?>

 

 

 

1. 아래와 같이 회원가입을 진행할 경우

 

2. mysql 테이블에 정상적으로 삽입이 되는 것을 확인할 수 있다.

 

 

3. 또한, 로그인페이지에서 로그인을 시도하면 아래와 같이 로그인에 성공했다는 화면을 볼 수 있다.

 

 

300x250
320x100

This site can’t be reached 172.16.146.111 refused to connect.

Try:

Checking the connection Checking the proxy and the firewall ERR_CONNECTION_REFUSED

명확한 해결 방법:

1️⃣ Python HTTP 서버를 실행할 때 IP 명시하기

다음과 같이 명령어를 입력하세요:

bash
복사편집
sudo python3 -m http.server 80 --bind 0.0.0.0

이렇게 하면 서버가 모든 인터페이스에서 연결을 허용합니다.

위 명령어로 실행하는 서버는 일회성으로 터미널이 열려 있는 동안에만 동작하는 임시 서버 이며,

터미널에서 Ctrl + C 를 누르면 바로 중지

 

✅ 방법 1: 백그라운드로 돌리기

터미널을 닫아도 유지되도록 하려면 아래 명령어를 쓰세요.

bash
복사편집
sudo nohup python3 -m http.server 80 --bind 0.0.0.0 &

이제 터미널을 닫아도 서버가 계속 동작합니다.

나중에 서버 종료 시 (PID 찾아 종료):

bash
복사편집
sudo lsof -i :80

나온 PID를 찾아서:

bash
복사편집
sudo kill -9 [PID번호]

종료하면 됩니다.




login.php 파일 코드를 제대로 넣은 것 같은데 계속 php.info() 만 보여주고 있는 오류

1️⃣ 실제로 열리는 파일이 login.php가 아님 (파일 경로가 다름)

현재 경로는:

bash
복사편집
~/Downloads/webpage/login.php

하지만 Apache가 읽는 경로는:

bash
복사편집
/var/www/html/login.php

즉, 웹서버는 당신이 만든 login.php 파일이 아닌, /var/www/html/login.php에 있는 예전 phpinfo() 파일을 열고 있는 겁니다!


✅ 해결 방법:

🔁 지금 만든 파일을 Apache 웹 루트로 복사하세요:

bash
복사편집
sudo cp ~/Downloads/webpage/login.php /var/www/html/login.php

그리고 다시 접속해 보세요:

arduino
복사편집
<http://172.16.146.130/login.php>

이제는 폼만 출력되는 HTML 페이지가 나와야 합니다.

성공

300x250
320x100

SFTP : SSH를 활용해서 파일을 전송하는 프로토콜

* 가상머신에 어떤 파일을 넣고 싶을 때 SFTP를 사용하면 편하다

 

 

🔧 sudo python3 -m http.server 80 이란?

이 명령어는 Python을 이용해서 **간단한 웹서버(HTTP 서버)**를 실행시키는 명령입니다.

 

  • 현재 터미널 위치(디렉토리)에 있는 파일들을 웹을 통해 공유할 수 있게 해줍니다.
  • 웹브라우저에서 http://<서버의IP주소> 로 접속하면, 그 디렉토리의 파일 목록이 나옵니다.
  • 해당 파일을 클릭해서 다운로드할 수도 있어요.

 

✅ 웹 서버에게 어떻게 파일을 요청할까?

  • 웹 브라우저를 통해 파일을 요청할 수 있습니다.
  • 브라우저의 주소창에 URL을 입력하면 웹 서버에 요청이 전달됩니다.

[Protocol]://[Domain or IP Address]:[Port]/[File Path]

 

Example :

http://172.16.146.111:80/normaltic_pic.jpeg

 

Linux 필수 단축어

1. pwd 현재경로 보는법

2. mkdir 디렉토리 만들기

 

 

✅ Web Root 경로란?

  • Web Root 경로웹서버가 실행된 위치를 기준으로 한 파일 공유의 시작점입니다.
  • 웹 브라우저가 요청하는 파일들은 이 Web Root 아래에 있어야 웹 서버가 응답할 수 있어요.

 

⚠️ Web Root가 루트(/) 경로일 경우의 위험성

  • 만약 웹서버를 시스템의 루트 디렉토리(/)에서 실행하면,
  • 시스템 전체 파일이 웹을 통해 모두 공개될 수 있습니다.
  • 이는 보안상 매우 위험한 설정입니다.

 

✅ URL 비교 및 주요 개념 정리

항목 URL 1 URL 2
URL http://172.16.146.111:80/normaltic_pic.jpeg https://www.naver.com:443/index.html
Protocol http (비보안) https (보안 연결)
Address Type IP 주소 도메인 이름
Port 80 (HTTP 기본 포트) 443 (HTTPS 기본 포트)
File normaltic_pic.jpeg index.html

 

 

 

📎 포트 개념 정리

프로토콜 기본 포트
HTTP 80
HTTPS 443

 

💡 포트를 명시하지 않아도 브라우저는 기본 포트를 자동으로 사용합니다.

예: http://example.com → 내부적으로는 http://example.com:80

 

 

🗂️ 파일 경로 지정 안 했을 때?

  • URL 뒤에 /만 적고 파일명을 생략하면, 웹서버는 기본 파일(index.html 등) 을 자동으로 보여줍니다.
    • 예: http://172.16.146.111/index.html 자동 제공 (있는 경우)

 

 

🌐 NAT 네트워크와 인터넷 서버

✅ NAT란?

  • NAT (Network Address Translation) 는 사설 IP를 공인 IP로 변환하는 기술입니다.
  • 로컬 네트워크에서는 보통 사설 IP (예: 192.168.x.x, 172.16.x.x) 를 사용합니다.

 

❓ 왜 인터넷 망에 있는 서버를 빌려야 할까?

  • 사설망(NAT 내)에서는 외부에서 직접 접속할 수 없습니다.
  • 따라서 인터넷에 직접 연결된 공인 IP 서버(호스팅 서버, 클라우드 등) 를 사용해야
  • 누구나 접근 가능한 웹 서비스를 만들 수 있습니다.

 

✅ 정적 페이지 vs 동적 페이지

 

📁 정적 페이지 (Static Page)

  • 미리 만들어진 .html, .jpg, .txt 파일 등
  • 웹서버가 직접 클라이언트에게 전달
  • 예: A_Score.txt, B_Score.txt 같은 성적표 텍스트 파일

 

 

⚙️ 동적 페이지 (Dynamic Page)

  • .php, .jsp, .asp 등 서버에서 실행되어야 하는 코드가 포함된 페이지
  • 사용자의 요청에 따라 내용이 바뀌는 페이지

 

Client (브라우저)
   ↓ 요청
Web Server (정적 파일 처리)
   ↓ 전달
WAS (Web Application Server, 동적 파일 처리)
   ↓ 요청
DB (데이터베이스, 실제 데이터 저장)

 

📌 동작 흐름 예시: 클라이언트가 score.php 요청

 

  1. 클라이언트가 score.php 파일 요청
  2. 웹서버가 요청을 보고 “이건 내가 처리할 수 없는 동적 파일이네!” 하고 WAS로 전달
  3. WASscore.php 내부의 코드를 실행
  4. (예: 어떤 학생 이름에 따라 다른 성적을 가져오도록)
  5. 결과를 HTML 형태로 변환해서 웹서버에 전달
  6. 웹서버는 그 결과를 클라이언트에게 전달
✔️ 즉, PHP 코드 같은 백엔드 코드는 웹 브라우저의 “소스 보기”에서는 보이지 않습니다.
서버가 이미 코드를 실행해서 결과만 사용자에게 보내기 때문입니다.

 

✅ PHP 코드 구간

<?php

    // 여기가 서버에서 실행되는 백엔드 코드

?>

  • 이 구간은 서버(WAS) 가 실행합니다.
  • 결과만 클라이언트에게 전달되므로 백엔드 코드는 외부에 노출되지 않습니다.

<? ~~~~~~~~~~~~ ?> 사이가 백엔드 php 언어 이다. WAS가 실행해야하는 코드이다.

 

 

드래그 한 부분은 서버의 소스코드이기 때문에 WAS가 변환해서 줘서 백엔드 코드는 네이버 소스보기로 확인 할 수 없다.

 

✅ PHP 언어와 파라미터 처리

📌 $_GET['name'] 이란?

  • $_GETGET 방식으로 전달된 파라미터를 받아오는 PHP의 전역 변수입니다.
  • 예시 
    • URL이 http://example.com/score.php?name=Alice 라면,
    • $_GET['name'// 결과: "Alice"

 

🧾 파라미터란?

  • 웹 페이지에 데이터를 전달하기 위해 사용되는 정보
  • 사용자가 웹서버에게 보내는 값
  • 예: 이름, 검색어, 로그인 정보 등

 

🔄 파라미터를 보내는 방식 (2가지)

1. GET 방식

  • 파라미터가 URL에 붙어서 전달됨
  • 형식:

http://example.com/page.php?name=normaltic

  • 특징:
    • 주소창에 보임
    • 즐겨찾기 가능
    • 길이 제한 있음
    • 민감한 정보 전달에는 부적절

 

2. POST 방식

  • 파라미터가 HTTP 본문(body) 에 담겨 전달됨
  • 주소창에 노출되지 않음
  • 특징:
    • 데이터가 숨겨져 있음
    • 많은 양의 데이터 전달 가능
    • 보안에 더 적합

 

Front-End, Back-End 비교

구분 Front-End Back-End
위치 클라이언트(브라우저) 서버
실행 주체 사용자 브라우저 서버 컴퓨터
주요 기능 화면 표시, 인터랙션 데이터 처리, 로직 수행
언어 HTML, CSS, JavaScript PHP, ASP, JSP 등
사용자 눈에 보임 보인다 보이지 않는다

 

 

✅ PHP 맛보기

GET방식

POST방식

📌 form 태그란?

  • 사용자로부터 데이터를 입력받아 서버에 전달하는 태그
  • 보통 input, select, textarea 등의 입력 요소들과 함께 사용됨

🧾  input 태그와 name 속성

  • input 태그는 사용자 입력을 받는 필드
  • name 속성은 서버에 전달되는 파라미터의 이름을 의미함

 

✅ POST 방식의 데이터 전달

  • POST 방식은 URL에 데이터를 포함하지 않습니다.
  • 대신, **HTTP 요청의 Body(본문)**에 데이터를 담아서 서버로 전송합니다.

URL name=stone으로 실행시 결과화면

 


 

✅ 로그인 페이지 만들기 샘플

1. placeholder 를 사용하면 무엇을 입력하라고 하는지 표시 시킬 수 있다.

 

2. 전달하고 싶은 페이지를 action= 을 이용해서 적어주면 로그인을 했을때 페이지가 이동하면서 연결이 된다.

 

 

 

더보기

1주차 과제

 

[1] 복습 (웹 서버 이해)

[2] 간이 로그인 페이지 만들기 (DB연결x)

(admin / admin1234) 라고 입력했을 때 로그인을 시켜주는 페이지를 만들어 보기

<?php

$userName = $_POST[’id’];

if($userName == “admin” && userPass == “admin1234”) {

로그인 성공 , 실패

}

?>

[3] 로그인 페이지 이쁘게 만들기 (CSS / Bootstrap) 둘 중 하나를 공부해야함

 

 

✅ 로그인 페이지 만들기

login.php

<?php
session_start();

// DB 연결
$conn = new mysqli("localhost:3306", "choyongyeop", "1234", "test");
if ($conn->connect_error) {
    die("DB 연결 실패: " . $conn->connect_error);
}


// 폼이 제출되었을 때만 처리
// 사용자의 id 값과 password 값이 존재할 경우, 전달된 값을 id와pw에 변수에 저장한다.
if (isset($_POST['id']) && isset($_POST['password'])) {
    $id = $_POST['id'];
    $pw = $_POST['password'];

    // DB에서 해당 ID의 사용자 정보 조회
    $sql = "SELECT * FROM users WHERE id = '$id'";
    $result = $conn->query($sql); // 위에서 만든 sql쿼리를 실제로 mysql에 실행하는 부분

    if ($result && $result->num_rows > 0) {  // 사용자가 입력한 아이디가 DB에 존재하면 실행
        $user = $result->fetch_assoc(); // DB 결과를 배열로 가져오기

        // 비밀번호 확인
        if (password_verify($pw, $user['password'])) {
            $_SESSION['id'] = $id;  // 세션 저장
            header("Location: login_prcc.php");  // 로그인 성공
            exit;
        } else {
            header("Location: login_fail.php");  // 비밀번호 틀림
            exit;
        }
    } else {
        header("Location: login_fail.php");  // ID가 없음
        exit;
    }
}

?>

<!DOCTYPE html>
<html lang="ko">
<head>
        <meta charset="UTF-8">
        <title>Login Page</title>

        <style>
          body {
                display: flex; /* 내부 요소를 정렬할 수 있게 함(기본 정렬 방식 대신 유연한 배치가 가능해짐) */
                justify-content: center; /* 가로 방향 가운데 정렬*/
                align-items: center; /* 세로 방향 가운데 정렬*/
                height: 100vh; /* 화면 전체 높이        
                                100vh는 화면 높이의 100%를 의미함 → 페이지 전체를 꽉 채워서 가운데 배치 가능 */
                margin: 0; /*기본 브라우저 여백을 없애서 딱 맞게 배치*/
                background-color: #f0f2f5;
                }

          .container {
                text-align: center;
                padding: 40px; /*박스 안쪽 여백을 40픽셀로 설정해서 넉넉한 공간 확보*/
                border-radius: 12px; /* 박스 모서리를 둥글게*/

                     }

         img {
                width: 500px; /*이미지 가로 너비를 500픽셀로 고정한다*/
                border-radius: 12px; /* 이미지 모서리를 둥글게*/
                margin-bottom: 20px; /* 이미지 아래쪽 공간에 20px 추가해서 폼 사이의 여백 추가*/
             }
        </style>
</head>

<body>
        <div class="container">
                <!-- src 이미지를 가져오는데 만약 못불러올 경우 alt 내용을 출력하고 사진의 크기는 500으로 가져와라 -->
          <img src="search.pstatic.jpeg" alt="Login Banner" width="500" />

          <form method="POST">
                <!-- 서버에서 name 또는 value 이름으로 값을 받을 수 있음, placeholder는 입력창에 흐리게 표시되는 글자 역할-->
                <input type="text" name="id" placeholder="User ID" />
                <input type="password" name="password" placeholder="User Password" />
                <input type="submit" value="Login" />

          </form>

          <form action="register.php" method="POST" style="padding-top: 20px;">
                <input type="submit" value="회원가입" />
          </form>

        </div>
</body>
</html>

 

login_prcc.php

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

if (!isset($_SESSION['id'])) {
    echo "<h1>세션이 만료되었거나 로그인하지 않았습니다.</h1>";
    exit;
}

$id = htmlspecialchars($_SESSION['id']);
echo "<h1>$id 님, 로그인에 성공했습니다 🎉</h1>";

// 학생 이름이 GET으로 전달되었을 때만 실행
if (isset($_GET['name'])) {
    $name = $_GET['name'];

    // DB 연결 정보 (내 DB 환경에 맞게 수정!)
    $host = "localhost:3306";
    $user = "choyongyeop";
    $pass = "1234";         // ← 너의 MySQL 비밀번호로 변경
    $dbname = "test";      // ← 너가 만든 DB 이름


    // DB 연결
    $conn = new mysqli($host, $user, $pass, $dbname);

    // 연결 확인
    if($conn){
            echo "DB Connect OK";
    }else{
            echo "DB Connect Fail";
    }

    // 학생 점수 조회 쿼리
    $sql = "select score from test_table where name = '$name' ";
    $result = mysqli_query($conn, $sql);
    $row = mysqli_fetch_array($result);   // 조회된 데이터 가져오기
    $score = $row['score'];               // 점수 값을 변수로 꺼냄



    // 결과 확인
    if($result) {
            echo "<h2>{$name} 학생의 점수는 {$score}점 입니다. </h2>";
    }else{
            echo "<h2>{$name} 학생의 점수를 찾을 수 없습니다. </h2>";
    }



}
?>

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>로그인 성공</title>
    <style>
        body {
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            height: 100vh; /*현재 브라우저 화면 높이를 꽉 채움(세로 전체) */
            margin: 0;
            font-family: 'Segoe UI', sans-serif; /*첫번째가 없을 경우 빽업용 글씨 sans-serif로 대체함 */
            background-color: #f0f2f5;
        }
        h1 {
            font-size: 36px;
            margin-bottom: 20px;
            color: #333;
        }
    </style>
</head>

 

 

로그인 페이지

 

로그인 성공 시

 

로그인 실패 시

300x250

+ Recent posts