반응형
PostgreSQL에서 사용자 로그인 및 비밀번호를 사용하는 프로그램을 위한 테이블 구성 시 고려해야 할 주요 항목은 다음과 같습니다.

1. 보안 (Security)
- 비밀번호 해싱 (Password Hashing): 절대로 비밀번호를 평문으로 저장해서는 안 됩니다. 비밀번호를 데이터베이스에 저장하기 전에 강력한 단방향 해싱 알고리즘(예: bcrypt, scrypt, Argon2)을 사용하여 해시(hash)해야 합니다. 이 알고리즘들은 비밀번호 해싱을 위해 특별히 설계되었으며, 해독이 불가능합니다.
- 솔트(Salt) 사용: 각 비밀번호 해시에 고유한 랜덤 솔트(salt)를 사용해야 합니다. 솔트는 동일한 비밀번호라도 다른 해시 값을 가지게 하여, 레인보우 테이블 공격(rainbow table attack)과 같은 사전 연산 공격을 방지하는 데 필수적입니다.
- 해싱 알고리즘과 솔트 저장: 비밀번호 해시, 솔트, 그리고 사용된 해싱 알고리즘을 함께 저장하는 것이 좋습니다. 이렇게 하면 나중에 더 강력한 알고리즘으로 업그레이드할 때 유연하게 대응할 수 있습니다.
- 비밀번호 만료 정책: 정기적으로 비밀번호 변경을 강제하는 정책을 고려할 수 있습니다. 이는 특히 높은 보안이 요구되는 시스템에서 유용합니다.
- 계정 잠금 (Account Lockout): 일정 횟수 이상 로그인 실패 시 해당 계정을 일시적으로 잠금 처리하여 무차별 대입 공격(brute-force attack)을 방지해야 합니다.
- 비밀번호 재설정 (Password Reset): 비밀번호를 잊어버렸을 때 사용자가 안전하게 비밀번호를 재설정할 수 있는 메커니즘을 고려해야 합니다. 이때 임시 토큰(token)을 이메일로 보내고, 이 토큰이 만료되도록 설계하는 것이 일반적입니다.
2. 효율성 및 기능성 (Efficiency & Functionality)
- 테이블 구조:
- users 테이블:
- user_id: 사용자 식별을 위한 기본 키(Primary Key) (UUID 또는 SERIAL)
- username: 사용자 이름 (UNIQUE 제약 조건)
- email: 이메일 주소 (UNIQUE 제약 조건, NULL 허용 여부)
- password_hash: 해시된 비밀번호를 저장하는 열 (TEXT)
- salt: 비밀번호 해시에 사용된 솔트를 저장하는 열 (TEXT)
- created_at: 계정 생성 시간 (TIMESTAMP)
- last_login_at: 마지막 로그인 시간 (TIMESTAMP)
- is_active: 계정 활성화 여부 (BOOLEAN)
- failed_login_attempts: 로그인 실패 횟수 (INTEGER)
- locked_until: 계정이 잠금 해제될 시간 (TIMESTAMP)
- users 테이블:
- 인덱스 (Indexes): 로그인 시 자주 검색되는 열(예: username, email)에 인덱스를 생성하여 검색 성능을 향상시켜야 합니다.
- 역할 기반 접근 제어 (RBAC - Role-Based Access Control): 사용자의 권한을 관리하기 위해 별도의 roles 테이블과 user_roles 테이블을 구성하여, 사용자에게 하나 이상의 역할을 부여할 수 있도록 설계하는 것이 좋습니다. 이는 시스템 확장 시 유연성을 제공합니다.
- 토큰 관리 (Token Management): 세션 관리나 "로그인 상태 유지" 기능을 위해 JWT(JSON Web Token)나 다른 형태의 세션 토큰을 사용한다면, 이 토큰의 무효화(invalidate)와 같은 관리 메커니즘도 고려해야 합니다.

▣ 사용자 계정 생성 프로그램 (Create An Account)
1. Python 환경 설정 - 라이브러리 설치
pip install psycopg2-binary
- psycopg2-binary: PostgreSQL 데이터베이스 연결.
2. 데이터베이스 생성 (예: user_accounts)
3. User 테이블 만들기
사용자 정보를 입력하면, 자동으로 User 테이블이 생성 되고 테이블에 정보가 저장 됩니다.

테이블의 파이썬 코드는 아래와 같이 정의 됩니다
| user_id SERIAL PRIMARY KEY |
|
| username VARCHAR(50) UNIQUE NOT NULL |
|
| email VARCHAR(100) UNIQUE NOT NULL |
|
| email VARCHAR(100) UNIQUE NOT NULL |
|
| password_hash BYTEA NOT NULL |
|
| salt BYTEA NOT NULL |
|
| created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP |
|
| last_login_at TIMESTAMP | last_login_at: 사용자가 마지막으로 로그인한 시간을 저장합니다. NULL로 남길 수도 있음(처음 로그인 전까지). |
| is_active BOOLEAN DEFAULT TRUE |
|
| failed_login_attempts INTEGER DEFAULT 0 |
|
| locked_until TIMESTAMP |
|
프로그램 코드 : 사용자 정보를 저장 하는 기능
import customtkinter as ctk
import psycopg2
import bcrypt
from tkinter import messagebox
# --- 데이터베이스 연결 정보 ---
# 실제 환경에서는 이 정보를 환경 변수나 설정 파일에서 안전하게 관리해야 합니다.
DB_NAME = "user_accounts"
DB_USER = "designer"
DB_PASSWORD = "7777"
DB_HOST = "localhost"
DB_PORT = "5432"
# --- 데이터베이스 헬퍼 함수 ---
def get_db_connection():
"""PostgreSQL 데이터베이스 연결을 생성하고 반환합니다."""
try:
conn = psycopg2.connect(
dbname=DB_NAME,
user=DB_USER,
password=DB_PASSWORD,
host=DB_HOST,
port=DB_PORT,
)
return conn
except psycopg2.OperationalError as e:
# 연결 정보에 한글이 있거나 인코딩 문제가 있을 경우 발생하는 오류
messagebox.showerror("Database Connection Error", f"데이터베이스 연결에 실패했습니다. 다음 사항을 확인해주세요:\n- 연결 정보(DB_NAME, DB_USER 등)에 한글이 있는지\n- 스크립트 파일이 UTF-8로 저장되었는지\n\n오류 내용: {e}")
return None
# --- 메인 애플리케이션 클래스 ---
class UserRegistrationApp(ctk.CTk):
def __init__(self):
super().__init__()
# --- 창 설정 ---
self.title("Join The Membership")
self.geometry("400x480")
self.resizable(False, False)
ctk.set_appearance_mode("Dark") # "Dark", "Light" 또는 "System"
ctk.set_default_color_theme("blue")
# --- 메인 프레임 ---
self.main_frame = ctk.CTkFrame(self, corner_radius=15)
self.main_frame.pack(pady=40, padx=40, fill="both", expand=True)
# --- 위젯 생성 ---
self.create_widgets()
def create_widgets(self):
"""회원가입 UI에 필요한 위젯들을 생성하고 배치합니다."""
# 제목 레이블
title_label = ctk.CTkLabel(self.main_frame, text="Create a user account", font=ctk.CTkFont(size=24, weight="bold"))
title_label.pack(pady=(30, 20))
# 사용자 이름 입력 필드
self.username_entry = ctk.CTkEntry(self.main_frame, width=250, placeholder_text="사용자 이름")
self.username_entry.pack(pady=12, padx=10)
# 이메일 입력 필드
self.email_entry = ctk.CTkEntry(self.main_frame, width=250, placeholder_text="이메일 주소")
self.email_entry.pack(pady=12, padx=10)
# 비밀번호 입력 필드
self.password_entry = ctk.CTkEntry(self.main_frame, width=250, placeholder_text="비밀번호", show="*")
self.password_entry.pack(pady=12, padx=10)
# 비밀번호 확인 입력 필드
self.confirm_password_entry = ctk.CTkEntry(self.main_frame, width=250, placeholder_text="비밀번호 확인", show="*")
self.confirm_password_entry.pack(pady=12, padx=10)
# 회원가입 버튼
register_button = ctk.CTkButton(self.main_frame, text="회원가입", command=self.register_event, width=250, height=40)
register_button.pack(pady=20, padx=10)
def register_event(self):
"""회원가입 버튼 클릭 시 호출되는 함수."""
username = self.username_entry.get()
email = self.email_entry.get()
password = self.password_entry.get()
confirm_password = self.confirm_password_entry.get()
if not username or not email or not password or not confirm_password:
messagebox.showwarning("입력 오류", "모든 필드를 입력해주세요.")
return
if password != confirm_password:
messagebox.showerror("비밀번호 불일치", "비밀번호가 일치하지 않습니다. 다시 확인해주세요.")
return
conn = get_db_connection()
if conn is None:
return
try:
with conn.cursor() as cur:
# 비밀번호 해싱
salt = bcrypt.gensalt()
hashed_password = bcrypt.hashpw(password.encode('utf-8'), salt)
# 사용자 정보 저장
cur.execute(
"""
INSERT INTO users (username, email, password_hash, salt)
VALUES (%s, %s, %s, %s)
ON CONFLICT (username) DO NOTHING
""",
(username, email, hashed_password, salt)
)
conn.commit()
if cur.rowcount > 0:
messagebox.showinfo("회원가입 성공", f"'{username}'님, 회원가입이 완료되었습니다!")
self.destroy() # 성공 시 창 닫기
else:
messagebox.showwarning("회원가입 실패", "이미 존재하는 사용자 이름입니다.")
except Exception as e:
conn.rollback()
messagebox.showerror("오류", f"회원가입 중 오류가 발생했습니다: {e}")
finally:
if conn:
conn.close()
# --- 애플리케이션 실행 ---
if __name__ == "__main__":
# 데이터베이스에 'users' 테이블이 없으면 생성
conn = get_db_connection()
if conn:
try:
with conn.cursor() as cur:
cur.execute("""
CREATE TABLE IF NOT EXISTS users (
user_id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password_hash BYTEA NOT NULL,
salt BYTEA NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_login_at TIMESTAMP,
is_active BOOLEAN DEFAULT TRUE,
failed_login_attempts INTEGER DEFAULT 0,
locked_until TIMESTAMP
);
""")
conn.commit()
print("Users 테이블이 성공적으로 생성되거나 이미 존재합니다.")
except Exception as e:
conn.rollback()
print(f"테이블 생성 중 오류 발생: {e}")
finally:
conn.close()
app = UserRegistrationApp()
app.mainloop()
by korealionkk@gmail.com

반응형
'업무 자동화 > python & CAD' 카테고리의 다른 글
| Python 학습] 주식 종목 이름 가져오기 #1 (1) | 2025.08.18 |
|---|---|
| Python 학습] 로그인 프로그램 - 사용자 로그인 (1) | 2025.08.17 |
| Python 학습] 선택한 폴더안에 있는 asm 파일 이름 가져오기 (1) | 2025.08.16 |
| Python 학습] 초보자를 위한 프로그램 (1) | 2025.08.16 |
| Python 학습] 파이썬 코드를 실행파일 exe로 만드는 방법 (2) | 2025.08.15 |