Flask实现用户登录功能全面教程


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:密码验证失败

  • 确认密码哈希使用相同方法生成和验证
  • 检查密码是否包含特殊字符

十、部署注意事项

  1. 生产环境安全
  • 使用环境变量存储SECRET_KEY
  • 启用HTTPS
  • 限制登录尝试次数
  1. 数据库选择
  • 小型应用:SQLite
  • 中型应用:PostgreSQL/MySQL
  • 大型应用:考虑添加Redis缓存会话
  1. 性能优化
  • 使用Gunicorn或uWSGI作为WSGI服务器
  • 启用数据库连接池
  • 考虑使用Flask-Caching缓存常用数据

通过本教程,你已经掌握了使用Flask构建完整用户登录系统的所有关键步骤。从基础的用户模型设计到安全的密码处理,再到会话管理和进阶功能,这些知识将帮助你创建安全可靠的Web应用认证系统。

,

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注