3.4 Flask-Login 用户认证


3.4 Flask-Login 用户认证

问题与直觉:登录态的本质

HTTP 无状态:两次请求互不相识。登录的本质是:第一次请求验证了"你是 alice",之后每个请求都能证明你是 alice——通过 session(服务端签名存 Cookie)携带身份。

直觉类比:登录像"办会员卡"——第一次出示身份证办卡(登录),之后每次出示会员卡(session)即可进入。Flask-Login 就是"会员卡系统":谁发卡、卡里记什么、怎么验卡、没卡的人去哪。

💡 关键直觉:Flask-Login 只管理"已登录是谁"这件事——具体账号密码怎么验证、注册逻辑,由你自己写;它负责把"当前用户"变成全局可访问的 current_user

核心原理:三步接入

2.1 安装与初始化

pip install flask-login
# extensions.py from flask_login import LoginManager login_manager = LoginManager() login_manager.login_view = 'login' # 未登录跳转的视图 login_manager.login_message = '请先登录' # 工厂中 login_manager.init_app(app)

2.2 用户加载回调(user_loader)

每次请求 Flask-Login 从 session 里读到"用户 id"后,需要回调把它变成用户对象:

# models.py from flask_login import UserMixin from .extensions import db, login_manager class User(UserMixin, db.Model): """UserMixin 提供 is_authenticated/is_active 等标准方法""" id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(80), unique=True) password_hash = db.Column(db.String(128)) def set_password(self, password): from werkzeug.security import generate_password_hash self.password_hash = generate_password_hash(password) def check_password(self, password): from werkzeug.security import check_password_hash return check_password_hash(self.password_hash, password) @login_manager.user_loader def load_user(user_id): return db.session.get(User, int(user_id))

2.3 登录与登出视图

from flask_login import login_user, logout_user, login_required, current_user @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': username = request.form['username'] password = request.form['password'] user = User.query.filter_by(username=username).first() if user and user.check_password(password): login_user(user, remember='remember' in request.form) return redirect(url_for('profile')) return '用户名或密码错误', 401 return render_template('login.html') @app.route('/logout') @login_required def logout(): logout_user() return redirect(url_for('login')) @app.route('/profile') @login_required def profile(): return f'你好,{current_user.username}'

核心 API

函数/对象 作用
login_user(user, remember=...) 写入 session,标记已登录
logout_user() 清除登录态
@login_required 未登录则跳转 login_view
current_user 当前用户对象(未登录时是 AnonymousUser)
current_user.is_authenticated 是否已登录

工程实践要点

3.1 密码哈希:绝不能明文存密码

# 正确:哈希后入库 user.set_password('mypassword123') # 校验 if user.check_password('mypassword123'): login_user(user)

为什么必须哈希:数据库一旦泄露,明文密码直接暴露;哈希后即使泄露,也无法反推原文(暴力破解成本极高)。永远不要自己写哈希算法,用 Werkzeug 的 generate_password_hash(默认 pbkdf2,自动加盐)。

3.2 模板中使用

{% if current_user.is_authenticated %} <p>欢迎,{{ current_user.username }}!</p> <a href="{{ url_for('logout') }}">登出</a> {% else %} <a href="{{ url_for('login') }}">登录</a> {% endif %}

3.3 自定义未授权行为

@login_manager.unauthorized_handler def unauthorized(): """未登录访问受保护页面的统一处理""" if request.is_xhr or request.accept_mimetypes.best == 'application/json': return {'error': '未授权'}, 401 return redirect(url_for('login', next=request.url))

3.4 角色与权限扩展

Flask-Login 只管"是否登录",**角色(admin/user)**自己加:

class User(UserMixin, db.Model): role = db.Column(db.String(20), default='user') def admin_required(view): @wraps(view) def wrapped(*args, **kwargs): if not current_user.is_authenticated or current_user.role != 'admin': abort(403) return view(*args, **kwargs) return wrapped @app.route('/admin') @login_required @admin_required def admin_panel(): return '管理后台'

3.5 记住我与会话时长

remember=True 时 Flask-Login 写一个长期 Cookie(默认 365 天,可配置 REMEMBER_COOKIE_DURATION)。不想用记住我,可以配置:

app.config.update( REMEMBER_COOKIE_DURATION=timedelta(days=7), SESSION_COOKIE_SECURE=True, # HTTPS 才发送 SESSION_COOKIE_HTTPONLY=True, # 禁止 JS 读取,防 XSS 偷 Cookie )

常见误区与排查

误区 现象 正解
明文存密码 数据库泄露=全量密码泄露 哈希(pbkdf2/加盐)
忘记 user_loader 请求报"user not found"或匿名 注册 user_loader 回调
未设 login_view 未登录访问 401 而非跳转 配置 login_manager.login_view
自己拼 session 记登录态 安全漏洞、登出不清 用 login_user/logout_user
current_user 当普通变量用 报错或拿到匿名对象 先判 is_authenticated
用 md5/sha1 哈希密码 秒破 Werkzeug generate_password_hash

动手演练:完整登录系统骨架

# extensions.py from flask_login import LoginManager login_manager = LoginManager() login_manager.login_view = 'auth.login' # auth.py(蓝图) from flask import Blueprint, render_template, request, redirect, url_for from flask_login import login_user, logout_user, login_required, current_user from .models import User from .extensions import db auth = Blueprint('auth', __name__, url_prefix='/auth') @auth.route('/login', methods=['GET', 'POST']) def login(): if current_user.is_authenticated: return redirect(url_for('main.index')) if request.method == 'POST': user = User.query.filter_by( username=request.form['username']).first() if user and user.check_password(request.form['password']): login_user(user) return redirect(url_for('main.index')) return render_template('login.html', error='用户名或密码错误') return render_template('login.html') @auth.route('/logout') @login_required def logout(): logout_user() return redirect(url_for('auth.login'))

把这个蓝图注册进应用,登录系统就有了:登录、登出、会话保持、未登录拦截——全部由 Flask-Login 托底。

一节小结

  • 登录态本质:session 携带用户 id,请求间"认识你"。
  • 三件套:LoginManager 初始化 + user_loader 加载用户 + login_user/logout_user 控制会话。
  • current_user:全局访问当前用户,is_authenticated 判登录。
  • login_required:一行装饰器保护任意视图。
  • 密码安全:Werkzeug 哈希+加盐,禁止明文与弱哈希。
  • 权限扩展:角色字段 + 自定义装饰器实现 admin_required。

深入理解:认证系统的完整拼图

Flask-Login 只负责"登录态管理",一个完整的认证系统还有几块拼图。

第一,注册时的"用户输入清洗"。 注册是用户数据的入口,必须校验+清洗:用户名去空格、长度限制、禁止特殊字符(表单层做);邮箱唯一性检查(数据库约束 + 业务校验双保险);密码强度规则(长度、复杂度)。注册接口是攻击者最常试探的入口——第四章 4.2 安全章节会系统讲。

第二,密码哈希的算法演进。 Werkzeug 默认 pbkdf2(加盐、可调迭代次数),已足够安全。了解即可的进阶:bcrypt/scrypt/argon2 是更"慢"的哈希算法(慢=暴力破解成本高)。核心原则不变:用成熟库、自动加盐、绝不自己发明哈希、绝不存明文。数据库泄露时,哈希是你的最后防线——而"加盐+慢哈希"决定这条防线能撑多久。

第三,登录失败的限速与锁定。 无限尝试密码 = 暴力破解的机会。常见措施:同一 IP/账号 5 次失败后锁定一段时间;增加验证码;记录失败日志用于告警。Flask 生态用 Flask-Limiter 或自己实现计数(Redis/数据库)。登录接口默认就该带限速,这是防爆破的第一道闸。

第四,"记住我"的安全权衡。 remember=True 会写入长期有效的令牌 Cookie——方便但风险更高(Cookie 被偷 = 长期登录态被接管)。折中方案:记住我令牌设置合理过期(7-30 天)、绑定设备信息、支持"在所有设备上注销"。敏感应用(支付、后台)不建议开记住我

第五,忘记密码的流程。 标准做法:用户提交邮箱 → 生成一次性重置令牌(带过期时间)→ 发邮件 → 用户点链接重置密码 → 重置后使旧令牌失效并强制重新登录。绝不能在邮件里发密码或"安全问答"——发一次性链接是最安全的模式。Flask 生态有 Flask-Security 等扩展封装这些,但理解流程比套扩展重要。

第六,登录后的授权(Authorization)。 认证(你是谁)之后是授权(你能干什么)。Flask-Login 管认证,授权靠角色/权限体系:简单场景用 is_admin 布尔字段 + 装饰器;复杂场景用角色表 + 权限表(RBAC)。第四章 4.1 的结构里,admin 蓝图配一个"仅管理员"的 before_request 钩子,就是授权的最小实现。认证与授权分开设计,是权限系统的第一原则。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U