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만 가지고 있어도, 서버가 로그인 상태를 유지해줌
예시 흐름:
- 로그인 성공 → 서버에서 세션 생성 + 세션 ID 부여
- 클라이언트는 그 ID를 쿠키로 저장 (Set-Cookie: PHPSESSID=xyz)
- 이후 요청마다 자동으로 서버에 세션 ID가 전달됨
- 서버는 해당 ID로 사용자 정보 확인 후 로그인 유지
✅ 1. 세션 ID를 노멀하게 저장
✅ 2. 세션이 저장되어 있는 위치 (서버 쪽)
/var/lib/php/sessions/
✅ 3. 세션을 직접 보면 → 서버에 저장된 것이라는 걸 알 수 있다
JWT (JSON Web Token)
웹에서 사용자 인증과 정보 전달을 위해 사용되는 토큰 기반 인증 방식
사용자 인증 정보를 안전하게 주고받기 위한 디지털 토큰
🔐 JWT의 구조
JWT는 크게 3부분으로 나뉘어 있다
Header.Payload.Signature
- Header (헤더)
- 어떤 알고리즘으로 서명했는지 등의 정보가 담김
- 예: {"alg": "HS256", "typ": "JWT"}
- Payload (페이로드)
- 실제로 전달할 정보(예: 사용자 ID, 권한 등)가 담김
- 민감한 정보는 넣으면 안 됨 (암호화되지 않음)
- Signature (서명)
- 위 두 부분을 비밀키로 서명해서 위변조 여부를 확인할 수 있게 함
- 서버가 이걸 보고 “이 토큰이 진짜인가?” 판단함
🔄 JWT는 어떻게 쓰여?
- 사용자가 로그인하면 서버가 JWT를 생성해서 사용자에게 전달
- 사용자는 이후 요청마다 JWT를 함께 보냄 (보통 Authorization: Bearer <token> 헤더에)
- 서버는 그 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주차 과제
- 오늘 수업 복습
- 로그인 로직 이해 (식별/인증)
- 지난 과제 ( 특별과제 제외)
- 로그인 페이지 만들기 (로직 4개)
- 식별/인증 동시
- 식별/인증 분리
- 식별/인증 동시 + 해시
- 식별/인증 분리 + 해시
로그인1 / 2 / 3 / 4 네가지 개발
- 추가과제
- 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;
}
}
}
?>
~
결과화면
'Information Technology > Web dev' 카테고리의 다른 글
회원가입 페이지 만들기 - SQL, SELECT, INSERT, WHERE, hash (0) | 2025.04.13 |
---|---|
로그인페이지 만들기 - SFTP, web root, NAT, PHP, GET방식, POST방식 (0) | 2025.04.13 |