FastAPI Cookie - 注册登陆小DEMO

1,396 阅读4分钟

引言

HTTP协议本身是无状态的,意味着它并不保留关于用户或会话的持久信息。在HTTP请求的往返过程中,服务器无法直接识别出是哪个具体用户发起的请求。为了解决这个问题,并在多个请求之间维持用户的身份和状态,引入了cookie这一机制。

Cookie通过在客户端(如浏览器)存储小段数据,并在后续的HTTP请求中将这些数据发送给服务器,从而间接地让HTTP请求信息中包含了用户的状态信息。这种方式允许服务器识别出是哪个用户发起的请求,并据此提供个性化的服务或保持会话的连续性。

本文主要讲述 在FastAPI 如何设置cookie与获取cookie信息,以及通过一个注册登陆案例进行讲解如何保存用户登陆状态。

简单使用Cookie参数

from datetime import datetime, timedelta

import uvicorn
from fastapi import FastAPI, Body, Cookie
from fastapi.responses import JSONResponse

app = FastAPI(description="cookie 使用")


@app.post("/set_cookie")
def set_cookie_demo(user_id: int = Body(description="用户ID")):
    resp = JSONResponse(content={"user_id": user_id, "demo": "set_cookie"})
    max_age = int(timedelta(hours=6).total_seconds())  # cookie 有效期
    resp.set_cookie(key="user_id", value=str(user_id), max_age=max_age)
    return resp


@app.get("/get_cookie")
def get_cookie_demo(user_id: int = Cookie(default=0)):
    print("user_id", user_id)
    resp = JSONResponse(content={"user_id": user_id, "demo": "get_cookie"})
    return resp
    

设置 cookie 可以使用 Response 的 set_cookie 方法进行设置,max_age、expires 参数都是用于指定cookie的有效期,max_age 是指多久到期,expires则是具体的过期时间,使用 expires 时需要注意要使用utc时间。

浏览器存储cookie信息如下

访问其他接口时浏览器会在请求头 Cookie 中带上这个网站设置的cookie信息,服务器获取cookie则直接使用fastapi的Cookie对象就可以直接帮助我们自动解析cookie信息。

注册登陆小Demo

#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Author: Hui
# @Desc: { 注册登陆demo }
# @Date: 2023/08/07 16:08
from datetime import datetime, timedelta

import uvicorn
from fastapi import FastAPI, Body, Cookie
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field

app = FastAPI(description="注册登陆demo")

user_infos = [
    {"id": 1, "username": "hui", "password": "123456"},
    {"id": 2, "username": "quan", "password": "123456"},
]

# key user_id value user info and expire_time
user_session = {
    # 1: {"user": {"id": 1, "username": "hui"}, "expire_time": datetime.now() + timedelta(hours=2)}
}


@app.get(path="/index")
def index():
    return "index"


@app.get(path="/users/detail")
def user_detail(user_id: int = Cookie(default=0)):
    print("user_id", user_id)
    
    # 登陆认证
    user_info = user_session.get(user_id)
    if not user_info:
        return JSONResponse(
            content={"code": -1, "message": "未登陆", "data": {}}
        )

    user = user_info.get("user")
    expire_time = user_info.get("expire_time")
    print("user", user)

    if datetime.now() > expire_time:
        return JSONResponse(
            status_code=401,
            content={"code": -1, "message": "登陆过期,请重新登陆", "data": {}}
        )

    return JSONResponse(content=user)


def get_user(username, password):
    print("user_infos", user_infos)
    for user in user_infos:
        if user.get("username") == username and user.get("password") == password:
            return {"id": user.get("id"), "username": user.get("username")}


class LoginModel(BaseModel):
    username: str = Field(min_length=3, max_length=20, description="用户名")
    password: str = Field(min_length=6, description="密码")


@app.post(path="/users/register")
def register(user: LoginModel):
    print("user", user)
    
    # 获取最大用户ID
    max_id = max([user.get("id") for user in user_infos]) or 0
    user_id = max_id + 1
    
    # 注册用户,添加到用户列表
    register_user = {"id": user_id, **user.model_dump()}
    user_infos.append(register_user)
    print("user_infos", user_infos)
    
    return JSONResponse(content={"user_id": user_id, "message": "ok"})


@app.post(path="/users/login")
def login(
        user: LoginModel
):
    user = get_user(user.username, user.password)
    print("user", user)

    if not user:
        return JSONResponse(content={"code": -1, "message": "用户or密码错误", "data": {}})

    # 登陆成功设置cookie
    resp = JSONResponse(content={"code": 0, "message": "ok", "data": {}})
    resp.set_cookie(key="user_id", value=user.get("id"), max_age=int(timedelta(days=1).total_seconds()))

    # 保存用户session
    user_session[user.get("id")] = {"user": user, "expire_time": datetime.now() + timedelta(days=1)}

    return resp


def main():
    uvicorn.run(app)


if __name__ == '__main__':
    main()

这里并没有使用数据库来存储用户信息以及用户状态信息,而是用 user_infos、user_session 两个全局变量来进行演示这样更简单一些。主要逻辑如下

  1. 用户注册保存到用户列表

  2. 用户登陆根据用户列表校验登陆用户信息

    1. 校验成功设置 cookie 信息
    2. 保存用户 session 信息(记住用户登陆状态,设置登陆有效期)
  3. 访问用户详情,根据 cookie 信息校验用户是否登陆

注意:这里主要是展示cookie的使用,真正在业务系统中的一些cookie的设置、密码的注册与校验都会进行加密处理,以保证安全性。

源代码

Github:FastAPI 实战手册

从FastAPI的安装、请求处理、响应返回等基础知识入手,到中间件、数据库操作、身份认证等核心组件的应用,再到实战项目的开发,以及源码分析,循序渐进地介绍FastAPI的使用,旨在让读者全面掌握这个框架。