Flask作为轻量级Python Web框架,非常适合实现用户认证系统。本教程将手把手教你使用Flask构建完整的用户登录功能,包括注册、登录、会话管理和密码安全等关键环节。
一、项目基础设置
1. 创建Flask项目结构
/flask-login-tutorial
/static
/css
/js
/templates
base.html
login.html
register.html
dashboard.html
app.py
requirements.txt
2. 安装必要依赖
pip install flask flask-sqlalchemy flask-login flask-bcrypt flask-wtf
将依赖保存到requirements.txt:
pip freeze > requirements.txt
二、用户模型与数据库配置
1. 配置SQLite数据库
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key-here'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
2. 创建用户模型
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(128))
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
def __repr__(self):
return f"User('{self.username}', '{self.email}')"
3. 初始化数据库
在Python shell中执行:
from app import db
db.create_all()
三、Flask-Login配置
1. 初始化Flask-Login
from flask_login import LoginManager
login_manager = LoginManager(app)
login_manager.login_view = 'login'
login_manager.login_message_category = 'info'
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
2. 登录路由保护
from flask_login import login_required, current_user
@app.route('/dashboard')
@login_required
def dashboard():
return render_template('dashboard.html', user=current_user)
四、表单处理
1. 创建登录和注册表单
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Length, Email, EqualTo
class RegistrationForm(FlaskForm):
username = StringField('用户名',
validators=[DataRequired(), Length(min=2, max=20)])
email = StringField('邮箱',
validators=[DataRequired(), Email()])
password = PasswordField('密码',
validators=[DataRequired()])
confirm_password = PasswordField('确认密码',
validators=[DataRequired(), EqualTo('password')])
submit = SubmitField('注册')
class LoginForm(FlaskForm):
email = StringField('邮箱',
validators=[DataRequired(), Email()])
password = PasswordField('密码',
validators=[DataRequired()])
remember = BooleanField('记住我')
submit = SubmitField('登录')
五、视图函数实现
1. 用户注册路由
from flask import render_template, url_for, flash, redirect
from flask_login import login_user
@app.route('/register', methods=['GET', 'POST'])
def register():
if current_user.is_authenticated:
return redirect(url_for('dashboard'))
form = RegistrationForm()
if form.validate_on_submit():
hashed_password = bcrypt.generate_password_hash(form.password.data).decode('utf-8')
user = User(username=form.username.data,
email=form.email.data,
password_hash=hashed_password)
db.session.add(user)
db.session.commit()
flash('账号注册成功!现在可以登录了', 'success')
return redirect(url_for('login'))
return render_template('register.html', title='注册', form=form)
2. 用户登录路由
from flask_login import login_user, logout_user
@app.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('dashboard'))
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user and bcrypt.check_password_hash(user.password_hash, form.password.data):
login_user(user, remember=form.remember.data)
next_page = request.args.get('next')
return redirect(next_page) if next_page else redirect(url_for('dashboard'))
else:
flash('登录失败,请检查邮箱和密码', 'danger')
return render_template('login.html', title='登录', form=form)
3. 用户登出路由
@app.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('home'))
六、模板设计
1. 基础模板 (base.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{% block title %}{% endblock %} - Flask登录系统</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<nav>
<div class="container">
<a href="{{ url_for('home') }}">首页</a>
{% if current_user.is_authenticated %}
<a href="{{ url_for('dashboard') }}">控制台</a>
<a href="{{ url_for('logout') }}">退出</a>
{% else %}
<a href="{{ url_for('login') }}">登录</a>
<a href="{{ url_for('register') }}">注册</a>
{% endif %}
</div>
</nav>
<div class="container">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div>
</body>
</html>
2. 登录模板 (login.html)
{% extends "base.html" %}
{% block title %}登录{% endblock %}
{% block content %}
<div class="form-container">
<h2>用户登录</h2>
<form method="POST" action="{{ url_for('login') }}">
{{ form.hidden_tag() }}
<div class="form-group">
{{ form.email.label }}
{{ form.email(class="form-control") }}
{% for error in form.email.errors %}
<span class="error">{{ error }}</span>
{% endfor %}
</div>
<div class="form-group">
{{ form.password.label }}
{{ form.password(class="form-control") }}
{% for error in form.password.errors %}
<span class="error">{{ error }}</span>
{% endfor %}
</div>
<div class="form-check">
{{ form.remember(class="form-check-input") }}
{{ form.remember.label(class="form-check-label") }}
</div>
<div class="form-group">
{{ form.submit(class="btn btn-primary") }}
</div>
</form>
<p>还没有账号?<a href="{{ url_for('register') }}">立即注册</a></p>
</div>
{% endblock %}
七、密码安全增强
1. 使用Flask-Bcrypt
from flask_bcrypt import Bcrypt
bcrypt = Bcrypt(app)
2. 密码重置功能(可选)
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
def get_reset_token(self, expires_sec=1800):
s = Serializer(app.config['SECRET_KEY'], expires_sec)
return s.dumps({'user_id': self.id}).decode('utf-8')
@staticmethod
def verify_reset_token(token):
s = Serializer(app.config['SECRET_KEY'])
try:
user_id = s.loads(token)['user_id']
except:
return None
return User.query.get(user_id)
八、进阶功能实现
1. 记住我功能
已在登录表单和视图函数中实现,Flask-Login会自动处理remember me cookie。
2. 账户激活邮件
import smtplib
from email.mime.text import MIMEText
def send_activation_email(user):
token = user.get_reset_token()
msg = MIMEText(f'''请点击以下链接激活您的账户:
{url_for('activate_account', token=token, _external=True)}
''')
msg['Subject'] = '账户激活'
msg['From'] = app.config['MAIL_USERNAME']
msg['To'] = user.email
with smtplib.SMTP('smtp.example.com', 587) as server:
server.starttls()
server.login(app.config['MAIL_USERNAME'], app.config['MAIL_PASSWORD'])
server.send_message(msg)
3. 登录尝试限制
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
limiter = Limiter(
app,
key_func=get_remote_address,
default_limits=["200 per day", "50 per hour"]
)
@app.route('/login', methods=['GET', 'POST'])
@limiter.limit("5 per minute")
def login():
# 原有登录逻辑
九、测试与调试
1. 单元测试示例
import unittest
from app import app, db
class UserModelCase(unittest.TestCase):
def setUp(self):
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://'
db.create_all()
def tearDown(self):
db.session.remove()
db.drop_all()
def test_password_hashing(self):
u = User(username='test')
u.set_password('cat')
self.assertFalse(u.check_password('dog'))
self.assertTrue(u.check_password('cat'))
2. 常见问题解决
问题1:CSRF token missing
- 确保所有表单包含
form.hidden_tag()
- 检查SECRET_KEY配置
问题2:用户加载失败
- 确认
user_loader
装饰器正确设置 - 检查用户ID是否为整数
问题3:密码验证失败
- 确认密码哈希使用相同方法生成和验证
- 检查密码是否包含特殊字符
十、部署注意事项
- 生产环境安全:
- 使用环境变量存储SECRET_KEY
- 启用HTTPS
- 限制登录尝试次数
- 数据库选择:
- 小型应用:SQLite
- 中型应用:PostgreSQL/MySQL
- 大型应用:考虑添加Redis缓存会话
- 性能优化:
- 使用Gunicorn或uWSGI作为WSGI服务器
- 启用数据库连接池
- 考虑使用Flask-Caching缓存常用数据
通过本教程,你已经掌握了使用Flask构建完整用户登录系统的所有关键步骤。从基础的用户模型设计到安全的密码处理,再到会话管理和进阶功能,这些知识将帮助你创建安全可靠的Web应用认证系统。