microdot.login --- 用户登录流程

围绕 microdot.session 的更高层封装,实现了常规的用户名/密码登录流程:用户加载器 回调将会话中存储的用户 id 映射回应用程序的用户对象,路由装饰器将未经身份验证的请求重定向到可配置的登录 URL,可选的“记住我”cookie 让回访用户跨浏览器会话保持登录状态。

要求在构造登录对象 之前 已在应用程序上初始化 microdot.session——会话正是存储用户 id 的地方。

class Login

class microdot.login.Login(login_url: str = '/login')
login_url

装饰器将未经身份验证的请求重定向到的 URL。应用程序在此路由上提供实际的登录表单/处理程序。默认为 '/login'

user_loader(f)

用于注册用户解析回调的装饰器。f 接收存储在会话中的用户 id,并返回用户对象(如果该 id 不再有效——账户已删除、会话已撤销等——则返回 None)。

login = Login()

@login.user_loader
async def load_user(user_id):
    return users.get(user_id)
__call__(f)

Login 实例装饰一个路由即可将其置于身份验证保护之后:

@app.get('/dashboard')
@login
async def dashboard(request):
    user = request.g.current_user
    # ...

未经身份验证的请求会被重定向到 login_url,并带有 ?next=<original-url> 查询参数,以便登录处理程序能将用户送回其原先所在之处。

fresh(f)

__call__() 类似,但拒绝从“记住我”cookie 恢复的会话——用户必须在上次完整登录之后显式重新登录过。用于保护敏感路由(修改密码、删除账户),使被盗的记住我 cookie 无法访问它们。

async login_user(request, user, remember: bool | int = False, redirect_url: str = '/')

user 标记为已为 request 登录。在会话中存储用户 id 并返回一个重定向响应。

user

用户加载器返回的用户对象。必须具有 id 属性。

remember

如果为真值,则还会设置一个长期有效的 _remember cookie。传入 True 表示默认的 30 天,或传入一个整数表示 cookie 应持续的天数。

redirect_url

登录后重定向到何处。如果原始请求中的 ?next=<url> 指向同站路径,则会被其覆盖。

返回重定向响应——从登录路由处理程序中返回其值。

async logout_user(request)

从会话中清除用户 id 并移除任何 _remember cookie。在 /logout 路由中使用:

@app.post('/logout')
async def logout(request):
    await login.logout_user(request)
    return redirect('/')
async get_current_user(request)

返回当前已登录的用户对象(或 None)。会在 request.g.current_user 上进行记忆化,因此同一请求内的重复调用只会命中加载器一次。

示例

一个最小化的登录流程:

from microdot import Microdot, redirect
from microdot.session import Session
from microdot.login import Login

app = Microdot()
Session(app, secret_key=load_secret())
login = Login()

@login.user_loader
async def load_user(user_id):
    return users.get(user_id)

@app.get('/login')
async def login_page(request):
    return Response.send_file('static/login.html')

@app.post('/login')
async def do_login(request):
    user = authenticate(request.form['user'], request.form['pass'])
    if not user:
        return redirect('/login?error=1')
    return await login.login_user(request, user, remember=True)

@app.get('/dashboard')
@login
async def dashboard(request):
    return 'hi ' + request.g.current_user.name

@app.post('/logout')
async def logout(request):
    await login.logout_user(request)
    return redirect('/')

_remember cookie 本身是一个签名的 JWT(使用会话的密钥),因此被盗的 cookie 无法在不同的应用程序上重用,也无法在密钥轮换之后重用。