1. 사용자 인증의 기본 개념(SESSION)
세션(Session)의 개념
웹 애플리케이션에서 세션(Session)은 클라이언트와 서버 간의 상태를 유지하기 위한 메커니즘입니다. 웹은 본래 상태를 유지하지 않는(stateless) 프로토콜인 HTTP를 사용하기 때문에, 세션을 이용해 사용자의 상태를 저장하고 유지할 수 있습니다. 이를 통해 사용자는 웹사이트를 탐색하는 동안 로그인 상태를 유지하거나, 쇼핑 카트의 항목을 유지하는 등의 작업을 할 수 있습니다.
세션의 주요 개념
- 세션 식별자(Session ID):
- 각 클라이언트에게 고유한 세션 ID가 할당됩니다.
- 이 세션 ID는 클라이언트가 웹 서버에 요청을 보낼 때마다 함께 전송되며, 서버는 이 ID를 사용해 클라이언트의 세션 데이터를 식별하고 접근합니다.
- 세션 데이터:
- 세션 데이터는 서버에 저장되며, 세션 ID를 통해 참조됩니다.
- 일반적으로 사용자 ID, 인증 상태, 장바구니 정보 등의 중요한 상태 정보가 포함됩니다.
- 세션 저장소:
- 세션 데이터는 서버 측에 저장됩니다. 이는 메모리, 파일 시스템, 데이터베이스 등 다양한 방식으로 구현될 수 있습니다.
- Flask에서는 기본적으로 서버의 파일 시스템에 세션 데이터를 저장할 수 있지만, 확장 가능한 저장소(예: Redis, Memcached, 데이터베이스)를 사용할 수도 있습니다.
- 세션 관리:
- 서버는 세션의 생성, 유지, 만료를 관리합니다.
- 일정 시간 동안 활동이 없으면 세션이 자동으로 만료될 수 있습니다(타임아웃)
Flask에서의 세션 관리
Flask는 flask.Session 모듈을 사용하여 세션을 쉽게 관리할 수 있습니다. 기본적으로 Flask 세션은 서명된 쿠키를 사용하여 클라이언트 측에 세션 데이터를 저장합니다. 그러나 이는 안전하지 않기 때문에 실제 애플리케이션에서는 서버 측에 세션 데이터를 저장하는 것이 일반적입니다.
세션 설정 예시
Flask 세션 설정:
from flask import Flask, session
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your_secret_key'
app.config['SESSION_TYPE'] = 'filesystem' # 기본 파일 시스템을 세션 저장소로 사용
# 세션을 설정하는 데 사용할 수 있는 다양한 옵션:
# SESSION_TYPE: 세션 데이터를 저장할 저장소 타입 (기본값: null)
# SESSION_PERMANENT: 영구 세션 사용 여부 (기본값: True)
# SESSION_USE_SIGNER: 세션 쿠키에 서명을 사용할지 여부 (기본값: False)
# SESSION_KEY_PREFIX: 세션 키에 추가할 접두사 (기본값: None
세션 데이터 설정 및 접근:
@app.route('/login', methods=['POST'])
def login():
# 로그인 로직 (유효성 검사 등)
session['user_id'] = user_id # 세션에 사용자 ID 저장
return 'Logged in successfully'
@app.route('/dashboard')
def dashboard():
if 'user_id' in session:
user_id = session['user_id']
return f'Welcome back, user {user_id}'
else:
return 'You are not logged in'
@app.route('/logout')
def logout():
session.pop('user_id', None) # 세션에서 사용자 ID 제거
return 'Logged out successfully'
- 세션(Session): 웹 애플리케이션에서 클라이언트와 서버 간의 상태를 유지하는 메커니즘.
- 세션 ID: 각 클라이언트에게 고유하게 할당되는 식별자.
- 세션 데이터: 사용자 인증 상태와 같은 중요한 상태 정보를 포함.
- 세션 저장소: 서버 측에 세션 데이터를 저장하는 위치 (메모리, 파일 시스템, 데이터베이스 등).
- Flask에서 세션 관리: flask.Session 모듈을 사용하여 세션 설정, 데이터 저장 및 접근.
2. Flask-Login 라이브러리 소개 및 사용법
Flask-Login은 Flask 애플리케이션에서 사용자 세션을 관리하고 인증을 쉽게 구현할 수 있도록 도와주는 라이브러리입니다. 이 라이브러리는 사용자가 로그인, 로그아웃, 세션 유지 등의 기능을 간단하게 구현할 수 있게 해 줍니다.
주요 기능
- 사용자 로그인 상태 유지
- 로그인된 사용자와 관련된 정보 접근
- 세션 타임아웃 설정
- "기억하기" 기능 (persistent session)
- 로그인되지 않은 사용자 접근 제한
설치
먼저 Flask-Login을 설치합니다.
pip install flask-login
기본 설정 및 사용법
app.py
from flask import Flask, render_template, redirect, url_for, request
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
app = Flask(__name__)
app.config['SECRET_KEY'] = 'joon_key'
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'
class User(UserMixin):
def __init__(self, id, username, password):
self.id = id
self.username = username
self.password = password
# 가상 사용자 데이터베이스 (on the memory)
users = {
'user1': User('1', 'user1', 'password1'),
'user2': User('2', 'user2', 'password2'),
}
@login_manager.user_loader
def load_user(user_id):
for user in users.values():
if user.id == user_id:
return user
return None
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
user = users.get(username)
if user and user.password == password:
login_user(user)
return redirect(url_for('dashboard'))
return '등록되지 않은 사용자입니다.'
return render_template('login.html')
@app.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('login'))
@app.route('/dashboard')
@login_required
def dashboard():
return f'Hello, {current_user.username}!'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5001, debug=True)
templates/login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login</title>
</head>
<body>
<h2>Login</h2>
<form method="POST">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
<button type="submit">Login</button>
</form>
</body>
</html>
3. 로그인, 로그아웃, 회원가입 기능 구현
디렉토리 구조
site1/
│
├── app.py
├── templates/
│ ├── index.html
│ ├── signup.html
│ ├── login.html
│ └── dashboard.html
│
└── static/
├── css/
│ └── style.css
└── images/
pip install flask_session
MariaDB 테이블 생성
CREATE TABLE User (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
password VARCHAR(512) NOT NULL
);
app.py
from flask import Flask, session, request, redirect, url_for, render_template
from flask_session import Session
import mysql.connector
from werkzeug.security import generate_password_hash, check_password_hash
app = Flask(__name__)
app.config['SECRET_KEY'] = 'joon'
app.config['SESSION_TYPE'] = 'filesystem'
Session(app)
# MySQL 연결 설정
db_config = {
'user': 'joon',
'password': '1234',
'host': 'localhost',
'database': 'backend'
}
def get_db_connection():
return mysql.connector.connect(**db_config)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/signup', methods=['GET', 'POST'])
def signup():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
hashed_password = generate_password_hash(password, method='pbkdf2:sha256', salt_length=8)
db = get_db_connection()
cursor = db.cursor()
cursor.execute("INSERT INTO User (username, password) VALUES (%s, %s)", (username, hashed_password))
db.commit()
cursor.close()
db.close()
return redirect(url_for('login'))
return render_template('signup.html')
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
db = get_db_connection()
cursor = db.cursor()
cursor.execute("SELECT * FROM User WHERE username = %s", (username,))
user = cursor.fetchone()
cursor.close()
db.close()
if user and check_password_hash(user[2], password):
session['user_id'] = user[0]
session['username'] = user[1]
return redirect(url_for('dashboard'))
else:
return 'Invalid credentials'
return render_template('login.html')
@app.route('/dashboard')
def dashboard():
if 'user_id' in session:
user_id = session['user_id']
return render_template('dashboard.html', username=session['username'])
return redirect(url_for('login'))
@app.route('/logout')
def logout():
session.pop('user_id', None)
session.pop('username', None)
return redirect(url_for('login'))
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5001, debug=True)
templates/index.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<title>Home</title>
</head>
<body>
<h2>Welcome to the Application</h2>
<p><a href="{{ url_for('login') }}">Log In</a></p>
<p><a href="{{ url_for('signup') }}">Sign Up</a></p>
</body>
</html>
templates/login.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<title>Log In</title>
</head>
<body>
<h2>Log In</h2>
<form method="POST">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
<button type="submit">Log In</button>
</form>
<p>Don't have an account? <a href="{{ url_for('signup') }}">Sign Up</a></p>
</body>
</html>
templates/signup.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<title>Sign Up</title>
</head>
<body>
<div class="container">
<h2>Sign Up</h2>
<form method="POST">
<div class="form-group">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
</div>
<button type="submit">Sign Up</button>
</form>
<p>Already have an account? <a href="{{ url_for('login') }}">Log In</a></p>
</div>
</body>
</html>
templates/dashboard.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<title>Dashboard</title>
</head>
<body>
<h2>Welcome, {{ username }}!</h2>
<a href="{{ url_for('logout') }}">Log Out</a>
</body>
</html>
추가기능) 데이터베이스에 로그인 시간, 로그아웃 시간, 전체 웹사이트 사용시간 컬럼을 만들어 관리하기!
1. User, Time 테이블 생성하기
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
db = get_db_connection()
cursor = db.cursor()
cursor.execute("SELECT * FROM User WHERE username = %s", (username,))
user = cursor.fetchone()
if user and check_password_hash(user[2], password):
session['user_id'] = user[0]
session['username'] = user[1]
# 로그인 시간 기록
login_time = datetime.now()
# Time 테이블에 로그인 시간 업데이트
cursor.execute("""
INSERT INTO Time (username, login_time)
VALUES (%s, %s)
ON DUPLICATE KEY UPDATE login_time = %s
""", (username, login_time, login_time))
db.commit()
cursor.close()
db.close()
return redirect(url_for('dashboard'))
else:
cursor.close()
db.close()
return 'Invalid credentials'
return render_template('login.html')
@app.route('/logout')
def logout():
if 'username' in session:
db = get_db_connection()
cursor = db.cursor()
username = session['username']
logout_time = datetime.now()
# 로그인 시간 가져오기
cursor.execute("SELECT login_time FROM Time WHERE username = %s ORDER BY login_time DESC LIMIT 1", (username,))
result = cursor.fetchone()
if result:
login_time = result[0]
if login_time:
# 총 사용 시간 계산
total_time = logout_time - login_time
total_seconds = int(total_time.total_seconds())
# 총 사용 시간 업데이트
cursor.execute("""
UPDATE Time
SET logout_time = %s, total_usage_time = total_usage_time + %s
WHERE username = %s AND login_time = %s
""", (logout_time, total_seconds, username, login_time))
db.commit()
cursor.close()
db.close()
session.pop('user_id', None)
session.pop('username', None)
return redirect(url_for('login'))
@app.route('/list')
def list():
if 'username' in session:
username = session['username']
db = get_db_connection()
cursor = db.cursor()
try:
cursor.execute("SELECT * FROM Time WHERE username = %s", (username,))
time_data = cursor.fetchall()
# 모든 결과를 소모하여 커서 닫기
cursor.fetchall() # 필요한 경우 호출
except mysql.connector.Error as err:
print(f"Error: {err}")
time_data = []
finally:
cursor.close()
db.close()
return render_template('list.html', username=username, Time=time_data)
return redirect(url_for('login'))
2. app.py 수정하기(login, logout, list), list.html 만들기
3. 로그인 시간, 로그아웃 시간, 전체 웹사이트 사용시간 추가 완성!
4. 여러 기능 추가하기
기능 1: 사용자 등록 시 이메일 추가
현재 사용자 등록은 username과 password만 입력받습니다. 여기에 email 필드를 추가하고 데이터베이스에 저장하세요. 또한, 사용자가 회원가입할 때 이메일 주소도 입력받을 수 있도록 폼을 수정하세요.
기능 2: 로그인 실패 시 사용자에게 메시지 출력
로그인 실패 시 사용자에게 "Invalid username or password" 메시지를 출력하세요.
기능 3: 로그인 유지 시간 설정
사용자가 로그인한 후 세션이 30분 동안 유지되도록 설정하세요. 사용자가 30분 동안 활동하지 않으면 자동으로 로그아웃됩니다.
from datetime import timedelta
app.config['REMEMBER_COOKIE_DURATION'] = timedelta(minutes=30)
app.py에 위 코드 추가
기능 4: 로그인된 사용자에게만 회원가입 페이지 접근 제한
로그인된 사용자는 회원가입 페이지(signup)에 접근할 수 없도록 제한하세요. 로그인된 사용자가 회원가입 페이지에 접근하려고 하면 대시보드로 리디렉션 됩니다.
@app.route('/signup', methods=['GET', 'POST'])
def signup():
if current_user.is_authenticated: // 추가
print('User already authenticated, redirecting to dashboard.') // 추가
return redirect(url_for('dashboard')) // 추가
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
email = request.form['email']
hashed_password = generate_password_hash(password, method='pbkdf2:sha256', salt_length=8)
기능 5: 비밀번호 변경 기능 추가
사용자가 자신의 비밀번호를 변경할 수 있는 기능을 추가하세요. 새로운 라우트 chang-password를 만들고, 사용자가 현재 비밀번호와 새 비밀번호를 입력하여 비밀번호를 변경할 수 있도록 합니다.
@app.route('/change_password', methods=['GET', 'POST'])
@login_required
def change_password():
if request.method == 'POST':
current_password = request.form['current_password']
new_password = request.form['new_password']
db = get_db_connection()
cursor = db.cursor()
cursor.execute("SELECT password FROM User WHERE id = %s", (current_user.id,))
user = cursor.fetchone()
if user and check_password_hash(user[0], current_password):
new_hashed_password = generate_password_hash(new_password, method='pbkdf2:sha256', salt_length=8)
cursor.execute("UPDATE User SET password = %s WHERE id = %s", (new_hashed_password, current_user.id))
db.commit()
return 'Password updated successfully'
else:
return 'Current password is incorrect'
cursor.close()
db.close()
return render_template('change_password.html')
app.py에 추가
templates/change_password.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<title>Change Password</title>
</head>
<body>
<div class="form-container">
<h2>Change Password</h2>
<form method="POST">
<div class="form-group">
<label for="current_password">Current Password:</label>
<input type="password" id="current_password" name="current_password" required>
</div>
<div class="form-group">
<label for="new_password">New Password:</label>
<input type="password" id="new_password" name="new_password" required>
</div>
<button type="submit">Change Password</button>
</form>
</div>
</body>
</html>
오늘은 다른 날에 비해 많은 양을 배웠다. 항상 CRUD를 공부하면 세션까지는 못 배웠는데 오늘 처음 Session에 대해 배우게 되었다. 오늘 배우면 시행착오가 많았지만 제일 해결하기 어려웠던 것은 "로그인시간, 로그아웃시간, 전체 웹사이트 사용 시간"을 추가하는 것이었다. 거기서도 특히 로그아웃 버튼을 눌렀을때, 에러 메세지와 함께 로그아웃 시간과 전체 웹사이트 사용시간이 저장이 안될때가 있고 안될때가 있어서 애를 먹었다. 문제를 해결해보니 답은 sql문에 있었다. 저장이 될때는 계정이 처음 로그인 했을때고 안될때는 똑같은 계정이 로그인 시에 안되는 것이였다.
cursor.execute("SELECT login_time FROM Time WHERE username = %s", (username,))
-> cursor.execute("SELECT login_time FROM Time WHERE username = %s ORDER BY login_time DESC LIMIT 1", (username,))
// 'ORDER BY login_time DESC LIMIT 1'을 추가하여 최신 로그인 세션의 로그인 시간을 가지고 온다.
cursor.execute("UPDATE Time SET logout_time = %s, total_usage_time = total_usage_time + %s WHERE username = %s", (logout_time, total_seconds, username))
-> cursor.execute("UPDATE Time SET logout_time = %s, total_usage_time = total_usage_time + %s WHERE username = %s AND login_time = %s", (logout_time, total_seconds, username, login_time))
// UPDATE 쿼리에서 WHERE 조건에 login_time을 추가하여 해당 로그인 세션의 로그아웃 시간을 업데이트한다.
app.py에서 logout 라우터를 수정하였더니 정상적으로 작동하였다. 끝!
'Python > Flask' 카테고리의 다른 글
[Day 7] WMS(Warehouse Management System) 구성 및 요구 사항 정의 2 (2) | 2024.07.23 |
---|---|
[Day 6] WMS(Warehouse Management System) 구성 및 요구 사항 정의 1 (2) | 2024.07.23 |
[Day 4] Database 완성 (2) | 2024.07.18 |
[Day 3] 템플릿 엔진과 HTML 렌더링 (0) | 2024.07.17 |
[Day 2] Flask Routing과 URL 설정 (0) | 2024.07.17 |