0bin-label-app 프로젝트의 auth 폴더에서 별도 리포지토리로 분리. Flask 기반 로그인 인증 서버: - POST /api/login: 클라이언트 로그인 API - GET /api/health: 서버 상태 확인 - /admin: 관리자 웹 페이지 - SQLite 기반 사용자 및 로그인 기록 저장 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
198 lines
5.9 KiB
Python
198 lines
5.9 KiB
Python
# app.py
|
|
# 0bin Label App 인증 서버 (Flask)
|
|
|
|
from flask import Flask, request, jsonify, render_template, redirect, url_for, session
|
|
from models import db, User, LoginLog
|
|
from datetime import datetime
|
|
import os
|
|
|
|
app = Flask(__name__)
|
|
app.config['SECRET_KEY'] = 'your-secret-key-change-this-in-production'
|
|
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///auth_db.sqlite'
|
|
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
|
|
|
# DB 초기화
|
|
db.init_app(app)
|
|
|
|
|
|
# ============================================================
|
|
# API 엔드포인트 (클라이언트용)
|
|
# ============================================================
|
|
|
|
@app.route('/api/login', methods=['POST'])
|
|
def api_login():
|
|
"""클라이언트 로그인 API"""
|
|
try:
|
|
data = request.get_json()
|
|
username = data.get('username', '').strip()
|
|
password = data.get('password', '').strip()
|
|
|
|
if not username or not password:
|
|
return jsonify({
|
|
'success': False,
|
|
'message': '아이디와 비밀번호를 입력하세요'
|
|
}), 400
|
|
|
|
# 사용자 조회
|
|
user = User.query.filter_by(username=username).first()
|
|
|
|
# 로그인 시도 기록
|
|
ip_address = request.remote_addr
|
|
user_agent = request.headers.get('User-Agent', '')
|
|
|
|
if user and user.is_active and user.check_password(password):
|
|
# 로그인 성공
|
|
log = LoginLog(
|
|
user_id=user.id,
|
|
ip_address=ip_address,
|
|
user_agent=user_agent,
|
|
success=True
|
|
)
|
|
db.session.add(log)
|
|
db.session.commit()
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'message': '로그인 성공',
|
|
'username': user.username
|
|
}), 200
|
|
else:
|
|
# 로그인 실패
|
|
if user:
|
|
log = LoginLog(
|
|
user_id=user.id,
|
|
ip_address=ip_address,
|
|
user_agent=user_agent,
|
|
success=False
|
|
)
|
|
db.session.add(log)
|
|
db.session.commit()
|
|
|
|
return jsonify({
|
|
'success': False,
|
|
'message': '아이디 또는 비밀번호가 올바르지 않습니다'
|
|
}), 401
|
|
|
|
except Exception as e:
|
|
return jsonify({
|
|
'success': False,
|
|
'message': f'서버 오류: {str(e)}'
|
|
}), 500
|
|
|
|
|
|
@app.route('/api/health', methods=['GET'])
|
|
def api_health():
|
|
"""서버 상태 확인"""
|
|
return jsonify({
|
|
'status': 'ok',
|
|
'timestamp': datetime.utcnow().isoformat()
|
|
}), 200
|
|
|
|
|
|
# ============================================================
|
|
# Admin 페이지 (웹 UI)
|
|
# ============================================================
|
|
|
|
@app.route('/')
|
|
def index():
|
|
"""메인 페이지 - Admin 로그인으로 리다이렉트"""
|
|
return redirect(url_for('admin_login'))
|
|
|
|
|
|
@app.route('/admin', methods=['GET'])
|
|
def admin_login():
|
|
"""Admin 로그인 페이지"""
|
|
if 'admin_logged_in' in session:
|
|
return redirect(url_for('admin_dashboard'))
|
|
return render_template('login.html')
|
|
|
|
|
|
@app.route('/admin/login', methods=['POST'])
|
|
def admin_login_post():
|
|
"""Admin 로그인 처리"""
|
|
username = request.form.get('username', '').strip()
|
|
password = request.form.get('password', '').strip()
|
|
|
|
# 간단한 admin 계정 (하드코딩)
|
|
# 실제 운영 시에는 별도 Admin 테이블 사용 권장
|
|
if username == 'admin' and password == 'admin1234':
|
|
session['admin_logged_in'] = True
|
|
session['admin_username'] = username
|
|
return redirect(url_for('admin_dashboard'))
|
|
else:
|
|
return render_template('login.html', error='잘못된 관리자 계정입니다')
|
|
|
|
|
|
@app.route('/admin/logout')
|
|
def admin_logout():
|
|
"""Admin 로그아웃"""
|
|
session.pop('admin_logged_in', None)
|
|
session.pop('admin_username', None)
|
|
return redirect(url_for('admin_login'))
|
|
|
|
|
|
@app.route('/admin/dashboard')
|
|
def admin_dashboard():
|
|
"""Admin 대시보드"""
|
|
if 'admin_logged_in' not in session:
|
|
return redirect(url_for('admin_login'))
|
|
|
|
# 사용자 목록
|
|
users = User.query.all()
|
|
|
|
# 최근 로그인 기록 (최대 50개)
|
|
recent_logs = LoginLog.query.order_by(
|
|
LoginLog.login_time.desc()
|
|
).limit(50).all()
|
|
|
|
# 통계
|
|
total_users = User.query.count()
|
|
total_logins = LoginLog.query.filter_by(success=True).count()
|
|
failed_logins = LoginLog.query.filter_by(success=False).count()
|
|
|
|
return render_template(
|
|
'dashboard.html',
|
|
users=users,
|
|
recent_logs=recent_logs,
|
|
total_users=total_users,
|
|
total_logins=total_logins,
|
|
failed_logins=failed_logins
|
|
)
|
|
|
|
|
|
# ============================================================
|
|
# DB 초기화
|
|
# ============================================================
|
|
|
|
def init_db():
|
|
"""데이터베이스 초기화 및 test 계정 생성"""
|
|
with app.app_context():
|
|
# 테이블 생성
|
|
db.create_all()
|
|
|
|
# test 계정이 없으면 생성
|
|
test_user = User.query.filter_by(username='test').first()
|
|
if not test_user:
|
|
test_user = User(username='test')
|
|
test_user.set_password('test')
|
|
db.session.add(test_user)
|
|
db.session.commit()
|
|
print('[DB] test/test 계정 생성 완료')
|
|
else:
|
|
print('[DB] test 계정 이미 존재')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
# DB 초기화
|
|
if not os.path.exists('auth_db.sqlite'):
|
|
print('[DB] 데이터베이스 생성 중...')
|
|
init_db()
|
|
else:
|
|
print('[DB] 기존 데이터베이스 사용')
|
|
|
|
# Flask 서버 실행 (8898 포트)
|
|
print('[서버] 인증 서버 시작: http://0.0.0.0:8898')
|
|
print('[서버] Admin 페이지: http://localhost:8898/admin')
|
|
print('[서버] API 엔드포인트: http://localhost:8898/api/login')
|
|
app.run(host='0.0.0.0', port=8898, debug=True)
|