feat: 添加登录认证,防止未授权访问
- 访问任何页面需先输入密码登录 - session有效期7天,无需频繁登录 - 密码通过环境变量ADMIN_PASSWORD配置,默认admin123 - 导航栏添加退出按钮 - 放行/login和/static及socket.io路径
This commit is contained in:
@@ -19,6 +19,7 @@ VOLUME ["/app/data"]
|
||||
|
||||
ENV FLASK_DEBUG=0
|
||||
ENV TZ=Asia/Shanghai
|
||||
ENV ADMIN_PASSWORD=admin123
|
||||
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||
|
||||
CMD ["python", "run.py"]
|
||||
|
||||
@@ -6,4 +6,6 @@ services:
|
||||
- "9000:9000"
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
environment:
|
||||
- ADMIN_PASSWORD=${ADMIN_PASSWORD:-admin123}
|
||||
restart: unless-stopped
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import os
|
||||
import sys
|
||||
import hashlib
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||
|
||||
from flask import Flask, redirect, url_for
|
||||
from flask import Flask, redirect, url_for, request, session, render_template
|
||||
from flask_socketio import SocketIO
|
||||
from server.database import init_db
|
||||
from server.routers import accounts, tasks, orders
|
||||
|
||||
socketio = SocketIO(cors_allowed_origins="*")
|
||||
|
||||
# 访问密码,可通过环境变量 ADMIN_PASSWORD 设置,默认 admin123
|
||||
ADMIN_PASSWORD = os.environ.get('ADMIN_PASSWORD', 'admin123')
|
||||
|
||||
|
||||
def create_app():
|
||||
app = Flask(
|
||||
@@ -17,10 +21,41 @@ def create_app():
|
||||
template_folder=os.path.join(os.path.dirname(__file__), '..', 'templates'),
|
||||
static_folder=os.path.join(os.path.dirname(__file__), '..', 'static'),
|
||||
)
|
||||
app.secret_key = 'snatcher-secret-key-change-me'
|
||||
app.secret_key = os.environ.get(
|
||||
'SECRET_KEY', 'snatcher-secret-key-change-me')
|
||||
|
||||
init_db()
|
||||
|
||||
# ── 登录认证 ──
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if request.method == 'POST':
|
||||
pwd = request.form.get('password', '')
|
||||
if pwd == ADMIN_PASSWORD:
|
||||
session['authenticated'] = _hash(pwd)
|
||||
session.permanent = True
|
||||
app.permanent_session_lifetime = __import__(
|
||||
'datetime').timedelta(days=7)
|
||||
return redirect(request.args.get('next', '/'))
|
||||
return render_template('login.html', error='密码错误')
|
||||
return render_template('login.html')
|
||||
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
session.clear()
|
||||
return redirect('/login')
|
||||
|
||||
@app.before_request
|
||||
def require_auth():
|
||||
# 放行登录页和静态资源
|
||||
if request.path in ('/login',) or request.path.startswith('/static'):
|
||||
return
|
||||
# 放行 SocketIO 的内部路径
|
||||
if request.path.startswith('/socket.io'):
|
||||
return
|
||||
if session.get('authenticated') != _hash(ADMIN_PASSWORD):
|
||||
return redirect(url_for('login', next=request.path))
|
||||
|
||||
app.register_blueprint(accounts.bp)
|
||||
app.register_blueprint(tasks.bp)
|
||||
app.register_blueprint(orders.bp)
|
||||
@@ -35,6 +70,10 @@ def create_app():
|
||||
return app
|
||||
|
||||
|
||||
def _hash(s):
|
||||
return hashlib.sha256(s.encode()).hexdigest()[:16]
|
||||
|
||||
|
||||
def _register_socketio_events():
|
||||
import asyncio
|
||||
from server.services.remote_browser import get_session
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
<a class="nav-link" href="/tasks/"><i class="bi bi-list-task"></i> 任务</a>
|
||||
<a class="nav-link" href="/accounts/"><i class="bi bi-people"></i> 账号</a>
|
||||
<a class="nav-link" href="/orders/"><i class="bi bi-receipt"></i> 订单</a>
|
||||
<a class="nav-link text-warning-emphasis" href="/logout"><i class="bi bi-box-arrow-right"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
47
templates/login.html
Normal file
47
templates/login.html
Normal file
@@ -0,0 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>登录 - 微店抢购管理</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
|
||||
<style>
|
||||
body { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
|
||||
.login-card { width: 360px; border-radius: 18px; border: none;
|
||||
box-shadow: 0 8px 40px rgba(0,0,0,.2); }
|
||||
.login-header { text-align: center; padding: 2rem 2rem 1rem; }
|
||||
.login-header i { font-size: 2.5rem; color: #667eea; }
|
||||
.login-header h4 { margin-top: .8rem; font-weight: 600; color: #333; }
|
||||
.login-body { padding: 0 2rem 2rem; }
|
||||
.form-control { border-radius: 10px; padding: .6rem 1rem; }
|
||||
.form-control:focus { border-color: #667eea; box-shadow: 0 0 0 .2rem rgba(102,126,234,.15); }
|
||||
.btn-login { background: linear-gradient(135deg, #667eea, #764ba2); border: none;
|
||||
border-radius: 10px; padding: .6rem; font-weight: 500; width: 100%; }
|
||||
.btn-login:hover { opacity: .9; }
|
||||
.alert { border-radius: 10px; font-size: .85rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card login-card">
|
||||
<div class="login-header">
|
||||
<i class="bi bi-lightning-charge-fill"></i>
|
||||
<h4>微店抢购管理</h4>
|
||||
</div>
|
||||
<div class="login-body">
|
||||
{% if error %}
|
||||
<div class="alert alert-danger py-2">{{ error }}</div>
|
||||
{% endif %}
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<input type="password" class="form-control" name="password"
|
||||
placeholder="请输入访问密码" autofocus required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-login">登 录</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user