Information Technology/Web dev

로그인 페이지 만들기 - 식별,인증,SQL 연산, JWT, 암호화, 인코딩, 해시, 쿠키, 세션

Brandon IT 2025. 4. 21. 22:40
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