내 프로젝트 2 : Flask Authentication System (Python + Flask 사용)
Source: Dev.to

🔐 Flask 인증 시스템
나는 매일 사용하는 웹사이트가 실제로 어떻게 만들어지는지 항상 궁금했다.
우리는 회원가입, 로그인, 로그아웃 같은 기능을 끊임없이 사용하지만, 대부분 그 뒤에서 무슨 일이 일어나는지 잘 모른다.
나도 예외는 아니었다 — 이런 호기심이 점점 커졌고, 직접 구조를 경험해보고 싶었다.
그래서 처음부터 직접 만들어 보기로 결심했고, 손으로 직접 해보면서 이러한 시스템이 실제로 어떻게 동작하는지 이해할 수 있기를 바랐다.
📂 1. 프로젝트 구조
auth_blog/
│── main.py
│── users.db (auto-created)
└── templates/
├── home.html
├── signup.html
└── login.html
🧠 2. 이 프로젝트가 하는 일
- 사용자 회원가입 (해시된 비밀번호 저장)
- 세션 관리가 포함된 사용자 로그인
login_required데코레이터를 이용한 보호된 대시보드 페이지- 로그아웃 기능
- 기본 미니 블로그 UI 레이아웃
이 구조는 거의 모든 현대 웹 애플리케이션의 기반이 된다.
🖥️ 3. 백엔드 코드 (main.py)
from flask import Flask, render_template, request, redirect, session
import sqlite3
from werkzeug.security import generate_password_hash, check_password_hash
app = Flask(__name__)
app.secret_key = "super_secret_key"
# Login‑required decorator
def login_required(func):
def wrapper(*args, **kwargs):
if "user_id" not in session:
return redirect("/login")
return func(*args, **kwargs)
wrapper.__name__ = func.__name__
return wrapper
# Initialize database
def init_db():
conn = sqlite3.connect("users.db")
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL
)
""")
conn.commit()
conn.close()
init_db()
# Signup
@app.route("/signup", methods=["GET", "POST"])
def signup():
if request.method == "POST":
username = request.form["username"]
password = request.form["password"]
hashed_pw = generate_password_hash(password)
conn = sqlite3.connect("users.db")
cur = conn.cursor()
try:
cur.execute(
"INSERT INTO users (username, password) VALUES (?, ?)",
(username, hashed_pw)
)
conn.commit()
except sqlite3.IntegrityError:
return "Signup failed: Username already exists."
finally:
conn.close()
return redirect("/login")
return render_template("signup.html")
# Login
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = request.form["username"]
password = request.form["password"]
conn = sqlite3.connect("users.db")
cur = conn.cursor()
cur.execute(
"SELECT id, username, password FROM users WHERE username = ?",
(username,)
)
user = cur.fetchone()
conn.close()
if user and check_password_hash(user[2], password):
session["user_id"] = user[0]
session["username"] = user[1]
return redirect("/dashboard")
else:
return "Login failed: Invalid username or password."
return render_template("login.html")
# Dashboard (protected)
@app.route("/dashboard")
@login_required
def dashboard():
return f"Welcome, {session['username']}! (Dashboard Page)"
# Logout
@app.route("/logout")
def logout():
session.clear()
return redirect("/login")
# Home
@app.route("/")
def home():
return render_template("home.html")
if __name__ == "__main__":
app.run(debug=True)
🖼️ 4. 템플릿
🏠 home.html
<!DOCTYPE html>
<html>
<head>
<title>Mini Blog</title>
<style>
body { font-family: sans-serif; padding: 20px; background-color: #f4f7f9; }
.post {
border: 1px solid #ddd;
padding: 15px;
margin-bottom: 15px;
border-radius: 8px;
background-color: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.top {
display: flex; justify-content: space-between;
align-items: center; border-bottom: 2px solid #007bff;
margin-bottom: 20px; padding-bottom: 10px;
}
a { text-decoration: none; color: #007bff; font-weight: bold; }
</style>
</head>
<body>
<div class="top">
<h1>Mini Blog Posts</h1>
<a href="/new">Write New Post →</a>
</div>
{% for post in posts %}
<div class="post">
<h3>{{ post[1] }}</h3>
<p>{{ post[2] }}</p>
</div>
{% endfor %}
{% if not posts %}
<p>No posts yet. Start writing your first entry!</p>
{% endif %}
</body>
</html>
🔑 login.html
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
<style>
body { font-family: sans-serif; padding: 20px; }
form { display: flex; flex-direction: column; width: 300px; gap: 10px; }
input { padding: 10px; border: 1px solid #ccc; border-radius: 4px; }
button { padding: 10px; background: #28a745; color: white;
border: none; border-radius: 4px; cursor: pointer; }
button:hover { background: #1e7e34; }
</style>
</head>
<body>
<h2>Log In</h2>
<form method="POST" action="/login">
<input type="text" name="username" placeholder="Username" required>
<input type="password" name="password" placeholder="Password" required>
<button type="submit">Log In</button>
</form>
</body>
</html>
📝 signup.html
<!DOCTYPE html>
<html>
<head>
<title>Sign Up</title>
<style>
body { font-family: sans-serif; padding: 20px; }
form { display: flex; flex-direction: column; width: 300px; gap: 10px; }
input { padding: 10px; border: 1px solid #ccc; border-radius: 4px; }
button { padding: 10px; background: #007bff; color: white;
border: none; border-radius: 4px; cursor: pointer; }
button:hover { background: #0056b3; }
</style>
</head>
<body>
<h2>Sign Up</h2>
<form method="POST" action="/signup">
<input type="text" name="username" placeholder="Username" required>
<input type="password" name="password" placeholder="Password" required>
<button type="submit">Register</button>
</form>
</body>
</html>
🔧 5. 직접 해보기 — 간단한 개선 사항
- 인증된 대시보드에 게시물 작성 기능 추가
- 사용자가 자신의 게시물을 수정/삭제할 수 있게 하기
- 게시물에 타임스탬프 추가
- 비밀번호 재설정 기능 구현
- CSS 스타일시트를 이용해 UI 개선
이러한 작은 개선을 통해 웹 인증과 백엔드 구조를 더욱 깊이 이해할 수 있다. 하나 혹은 두 개 정도 변경해보고, 이 프로젝트가 실제 애플리케이션으로 빠르게 진화하는 모습을 확인해 보자!