跳转至

用户认证

📖 章节简介

本章将介绍Flask-Login扩展,学习如何实现用户认证系统,包括登录、注册、会话管理和权限控制。

🔐 Flask-Login基础

1. 安装和配置

Bash
# 安装Flask-Login
pip install flask-login

# requirements.txt
flask-login==0.6.3
Python
# app/__init__.py
from flask_login import LoginManager

login_manager = LoginManager()

def create_app():
    app = Flask(__name__)

    # 配置Flask-Login
    login_manager.init_app(app)
    login_manager.login_view = 'auth.login'
    login_manager.login_message = '请先登录'
    login_manager.login_message_category = 'info'

    return app

# 用户加载回调
@login_manager.user_loader
def load_user(user_id):
    return db.session.get(User, int(user_id))

2. 用户模型

Python
# app/models.py
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash

class User(UserMixin, db.Model):
    """用户模型"""
    __tablename__ = 'users'

    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(20), unique=True, nullable=False, index=True)
    email = db.Column(db.String(120), unique=True, nullable=False, index=True)
    password_hash = db.Column(db.String(128))
    created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))  # lambda匿名函数:简洁的单行函数
    is_admin = db.Column(db.Boolean, default=False)
    is_active = db.Column(db.Boolean, default=True)

    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}>'

🚪 登录注册

1. 登录功能

Python
# app/auth/routes.py
from flask import render_template, redirect, url_for, flash, request
from flask_login import login_user, logout_user, current_user
from app.auth.forms import LoginForm
from app.models import User

@auth_bp.route('/login', methods=['GET', 'POST'])
def login():
    if current_user.is_authenticated:
        return redirect(url_for('main.index'))

    form = LoginForm()

    if form.validate_on_submit():
        user = User.query.filter_by(username=form.username.data).first()

        if user and user.check_password(form.password.data):
            if not user.is_active:
                flash('账号已被禁用', 'error')
                return render_template('auth/login.html', form=form)

            login_user(user, remember=form.remember_me.data)
            flash('登录成功!', 'success')

            next_page = request.args.get('next')
            if not next_page or not next_page.startswith('/'):
                next_page = url_for('main.index')

            return redirect(next_page)
        else:
            flash('用户名或密码错误', 'error')

    return render_template('auth/login.html', form=form)

2. 注册功能

Python
@auth_bp.route('/register', methods=['GET', 'POST'])
def register():
    if current_user.is_authenticated:
        return redirect(url_for('main.index'))

    form = RegistrationForm()

    if form.validate_on_submit():
        user = User(
            username=form.username.data,
            email=form.email.data
        )
        user.set_password(form.password.data)

        db.session.add(user)
        db.session.commit()

        flash('注册成功!请登录', 'success')
        return redirect(url_for('auth.login'))

    return render_template('auth/register.html', form=form)

3. 登出功能

Python
@auth_bp.route('/logout')
@login_required
def logout():
    logout_user()
    flash('您已退出登录', 'info')
    return redirect(url_for('main.index'))

🔑 会话管理

1. 会话配置

Python
# config.py
from datetime import timedelta

class Config:
    # 会话配置
    SESSION_COOKIE_SECURE = True  # 仅HTTPS
    SESSION_COOKIE_HTTPONLY = True  # 禁止JavaScript访问
    SESSION_COOKIE_SAMESITE = 'Lax'  # 防止CSRF
    PERMANENT_SESSION_LIFETIME = timedelta(days=7)  # 会话有效期

2. 记住我功能

Python
# app/auth/forms.py
class LoginForm(FlaskForm):
    username = StringField('用户名',
                       validators=[DataRequired()])
    password = PasswordField('密码',
                          validators=[DataRequired()])
    remember_me = BooleanField('记住我')
    submit = SubmitField('登录')

# 登录时使用remember_me
login_user(user, remember=form.remember_me.data)

🛡️ 权限控制

1. 装饰器

Python
# app/decorators.py
from functools import wraps
from flask import abort
from flask_login import current_user

def admin_required(f):
    """管理员权限装饰器"""
    @wraps(f)
    def decorated_function(*args, **kwargs):  # *args接收任意位置参数;**kwargs接收任意关键字参数
        if not current_user.is_authenticated:
            abort(403)
        if not current_user.is_admin:
            abort(403)
        return f(*args, **kwargs)
    return decorated_function

def permission_required(permission):
    """权限检查装饰器"""
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            if not current_user.is_authenticated:
                abort(403)
            if not current_user.has_permission(permission):
                abort(403)
            return f(*args, **kwargs)
        return decorated_function
    return decorator

2. 权限检查

Python
# app/models.py
class User(UserMixin, db.Model):
    # ... 其他字段 ...

    def has_permission(self, permission):
        """检查用户是否有指定权限"""
        return self.is_admin or permission in self.permissions

    def can_edit_post(self, post):
        """检查是否可以编辑文章"""
        return self.id == post.author_id or self.is_admin

    def can_delete_post(self, post):
        """检查是否可以删除文章"""
        return self.id == post.author_id or self.is_admin

📧 密码重置

1. 发送重置邮件

Python
# app/auth/routes.py
from flask_mail import Message
from app import mail

def send_password_reset_email(user):
    """发送密码重置邮件"""
    token = user.get_reset_token()
    msg = Message('重置密码',
                 recipients=[user.email])
    msg.body = f'''重置密码的链接:
{url_for('auth.reset_password', token=token, _external=True)}

如果你没有请求重置密码,请忽略此邮件。
'''
    mail.send(msg)

@auth_bp.route('/reset_password_request', methods=['GET', 'POST'])
def reset_password_request():
    if current_user.is_authenticated:
        return redirect(url_for('main.index'))

    form = ResetPasswordRequestForm()

    if form.validate_on_submit():
        user = User.query.filter_by(email=form.email.data).first()
        if user:
            send_password_reset_email(user)
        flash('如果邮箱存在,重置链接已发送', 'info')
        return redirect(url_for('auth.login'))

    return render_template('auth/reset_password_request.html', form=form)

2. 重置密码

Python
@auth_bp.route('/reset_password/<token>', methods=['GET', 'POST'])
def reset_password(token):
    if current_user.is_authenticated:
        return redirect(url_for('main.index'))

    user = User.verify_reset_token(token)
    if not user:
        flash('无效或过期的重置链接', 'error')
        return redirect(url_for('auth.reset_password_request'))

    form = ResetPasswordForm()

    if form.validate_on_submit():
        user.set_password(form.password.data)
        db.session.commit()
        flash('密码已重置', 'success')
        return redirect(url_for('auth.login'))

    return render_template('auth/reset_password.html', form=form)

💡 最佳实践

1. 安全措施

Python
# 认证安全最佳实践
auth_security = {
    '密码哈希': '使用安全的密码哈希算法',
    'HTTPS': '强制使用HTTPS',
    'CSRF保护': '启用CSRF保护',
    '会话安全': '配置安全的会话选项',
    '密码策略': '实施强密码策略'
}

2. 用户体验

Python
# 认证用户体验
auth_ux = {
    '记住我': '提供记住我功能',
    '密码重置': '实现密码重置功能',
    '邮箱验证': '验证用户邮箱',
    '社交登录': '支持第三方登录',
    '双因素认证': '提供2FA选项'
}

📝 练习题

基础题

  1. 什么是Flask-Login?
  2. 如何实现用户登录?
  3. 如何保护路由?

进阶题

  1. 实现权限控制系统。
  2. 添加密码重置功能。
  3. 实现社交登录。

实践题

  1. 创建完整的用户认证系统。
  2. 实现邮箱验证功能。
  3. 构建用户个人中心。

📚 推荐阅读

🔗 下一章

RESTful API - 学习构建RESTful API。