Cookie、Session、JWT

HTTP Cookie(通常简称为 Cookie)是由 服务器通过 HTTP 响应头 Set-Cookie 发送给用户代理(如浏览器)的一小段数据,用户代理随后会在后续的、满足条件的 HTTP 请求中,通过 Cookie 请求头自动将其发送回服务器。

  • 核心目的:在无状态的 HTTP 协议之上,实现 状态管理(State Management)客户端数据持久化

浏览器会:

  1. 保存它
  2. 在后续同域名的请求中,自动通过 Cookie 请求头发回给服务器

🔑 Cookie 的核心作用:在无状态的 HTTP 协议上实现“会话跟踪”

前端构建

使用 Vite 快速创建 React 项目

1
2
#用最新版 Vite 脚手架,在名为 react-app 的文件夹中,创建一个基于 React 的项目。
npm create vite@latest react-app -- --template react

Vite(发音同 “veet”,法语“快”)是一个现代化的前端构建工具,由 Vue.js 作者尤雨溪(Evan You)开发,但不仅限于 Vue——它对 React、Svelte、Lit 等主流框架都有官方支持。

1
2
3
4
#安装项目依赖
npm install
#启动本地开发服务器
npm run dev

项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
react-app/
├── public/ # 静态资源目录
├── src/ # 源代码目录
│ ├── components/ # 组件目录
├── Login.jsx # 登录组件
├── Login.css # 登录样式
├── Profile.jsx # 用户信息组件
└── Profile.css # 用户信息样式
│ ├── App.jsx # 主应用组件
│ ├── main.jsx # 应用入口文件
│ └── *.css # 样式文件
├── package.json # 项目配置和依赖
├── index.html
└── vite.config.js # 构建工具配置

启动链条

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1. 浏览器加载 index.html

2. 解析到 <script src="/src/main.jsx">

3. 加载并执行 main.jsx

4. main.jsx 中的 createRoot(document.getElementById('root'))

5. 找到 <div id="root"></div>

6. 将 <App /> 组件渲染到 root 容器中

index.html (HTML容器)

main.jsx (入口文件) ← 你当前查看的文件

App.jsx (主应用组件)

Login.jsx, Profile.jsx (子组件)

index.html

脚本标签指定入口

1
2
<script type="module" src="/src/main.jsx"></
script>
  • src=“/src/main.jsx” 明确指定 了入口文件路径
  • type=“module” 表示这是ES6模块,支持import/export语法
  • 浏览器加载这个脚本时,就会执行main.jsx中的代码

main.jsx 中的这行代码:

1
createRoot(document.getElementById('root'))

index.html 中的:

1
<div id="root"></div>

通过 id=“root” 建立了连接!

main.jsx

main.jsx 是整个React应用的 入口文件

1
2
3
4
5
6
7
8
9
10
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'

createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)

创建React根节点

1
createRoot(document.getElementById('root'))
- 在HTML页面中找到id为’root’的DOM元素 - 创建一个React根渲染器,这是React应用挂载的地方

渲染应用

1
2
3
4
5
.render(
<StrictMode>
<App />
</StrictMode>,
)
  • 将整个React应用渲染到根节点
  • StrictMode 包裹应用,提供开发时的额外检查和警告
  • 是你的主应用组件

App.jsx

组件(Component) 是React的核心概念,可以理解为:

  • 可复用的UI构建块
  • 独立的功能单元
  • 像乐高积木一样可以组合的代码片段

以App组件为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function App() {
// 1. 状态管理
const [isLoggedIn, setIsLoggedIn] =
useState(false)

// 2. 事件处理函数
const handleLoginSuccess = () => {
setIsLoggedIn(true)
}

// 3. 渲染UI
return (
<div className="app">
<header>...</header>
<main>
{!isLoggedIn ? (
<Login onLoginSuccess=
{handleLoginSuccess} />
) : (
<Profile isLoggedIn={isLoggedIn} /
>
)}
</main>
<footer>...</footer>
</div>
)
}

export default App

🔍 组件的核心特征

  1. 函数式组件
1
2
3
4
5
6
function App() {
// 组件逻辑
return (
// JSX - 描述UI结构
)
}
  • App是一个 JavaScript函数
  • 函数名就是 组件名 (必须大写开头)
  • 返回 JSX (类似HTML的语法)
  1. 状态管理(State)
1
const [isLoggedIn, setIsLoggedIn] = useState(false)

isLoggedIn - 状态变量 - 存储 当前的登录状态 - 初始值是 false (未登录) - 只能 读取 ,不能直接修改

setIsLoggedIn - 状态更新函数

  • 用来 更新 isLoggedIn 的值
  • 调用时会 触发组件重新渲染
  • 是修改状态的 唯一正确方式

useState(false) - Hook调用

  • false 是 初始值 ,表示默认未登录
  • 返回一个 数组 : [当前值, 更新函数]
  1. 事件处理
1
2
3
const handleLoginSuccess = () => {
setIsLoggedIn(true)
}
  • 组件可以 响应用户交互
  • 处理点击、输入等事件
  • 更新状态,触发UI更新
  1. 条件渲染
1
2
3
4
5
6
{!isLoggedIn ? (
<Login onLoginSuccess=
{handleLoginSuccess} />
) : (
<Profile isLoggedIn={isLoggedIn} />
)}
  • 根据 状态条件 显示不同内容
  • 动态决定渲染哪些子组件
  1. 组件组合
1
2
3
<Login onLoginSuccess={handleLoginSuccess} /
>
<Profile isLoggedIn={isLoggedIn} />
  • 大组件由 小组件组合 而成
  • 通过 Props 传递数据给子组件

组件组合完整的数据流

  1. 父组件(App.jsx)定义函数
1
2
3
4
5
6
7
8
9
10
11
12
13
function App() {
const [isLoggedIn, setIsLoggedIn] =
useState(false)

// 定义回调函数
const handleLoginSuccess = () => {
setIsLoggedIn(true) // 更新父组件的状态
}

// 将函数传递给子组件
return <Login onLoginSuccess=
{handleLoginSuccess} />
}
  1. 子组件(Login.jsx)接收并使用
1
2
3
4
5
6
7
8
9
10
11
12
function Login({ onLoginSuccess }) {  // 通过Props接收函数
const handleLogin = async () => {
// ... 登录逻辑

if (response.ok) {
// 登录成功时调用父组件传来的函数
onLoginSuccess() // 🔥 关键:通知父组件
}
}

return <button onClick={handleLogin}>登录</button>
}

Login.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function Login({ onLoginSuccess }) {
// 1. 管理登录状态
const [isLoading, setIsLoading] = useState(false)
const [message, setMessage] = useState('')

// 2. 处理登录逻辑
const handleLogin = async () => {
// 调用后端API
const response = await fetch('http://localhost:8000/login', {
method: 'POST',
credentials: 'include', // Cookie认证
})

// 成功后通知父组件
if (response.ok) {
onLoginSuccess() // 调用父组件传来的回调函数
}
}

// 3. 渲染登录界面
return (
<div className="login-container">
<button onClick={handleLogin}>
{isLoading ? '登录中...' : '登录'}
</button>
</div>
)
}

Profile.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function Profile({ isLoggedIn }) {
// 1. 管理用户数据状态
const [userInfo, setUserInfo] = useState(null)
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState('')

// 2. 获取用户信息
const fetchProfile = async () => {
const response = await fetch('http://localhost:8000/profile', {
credentials: 'include', // 使用Cookie认证
})
const data = await response.json()
setUserInfo(data)
}

// 3. 生命周期管理
useEffect(() => {
fetchProfile() // 组件加载时自动获取数据
}, [isLoggedIn])

// 4. 条件渲染
if (!isLoggedIn) {
return <p>请先登录查看用户信息</p>
}

return (
<div className="profile-container">
<h2>用户信息</h2>
<div>用户ID: {userInfo?.user_id}</div>
</div>
)
}

后端构建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
from fastapi import FastAPI, Response, Request
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

# 添加 CORS 中间件
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 允许所有前端地址
allow_credentials=True, # 允许携带 Cookie
allow_methods=["*"],# 允许所有 HTTP 方法
allow_headers=["*"],# 允许所有 HTTP 头
)

@app.post("/login")
def login(response: Response):
response.set_cookie(key="user_id", value="123", httponly=True)
return {"msg": "Logged in"}

@app.get("/profile")
def profile(request: Request):
user_id = request.cookies.get("user_id")
return {"user_id": user_id}


def main():
import uvicorn
print("Starting FastAPI backend server...")
uvicorn.run(
"backend.app:app",
host="127.0.0.1",
reload=True,#支持热重载
port=8000,
log_level="info"
)


if __name__ == "__main__":
main()

CORS(Cross-Origin Resource Sharing) = 跨域资源共享

否则会出现

1
2
3
Access to fetch at 'http://localhost:8000/login' 
from origin 'http://localhost:5173'
has been blocked by CORS policy
  • 从 http://localhost:5173 (前端)
  • 访问 http://localhost:8000/login (后端)
  • 被CORS策略阻止了

cookie机制

  1. POST /login:登录成功,服务器“记住”用户
  2. GET /profile:获取用户资料,服务器“认出”用户

关键就靠 Cookie 在浏览器和服务器之间传递身份。

🔧 1. response.set_cookie(key="user_id", value="123", httponly=True)

✅ 作用:

让服务器告诉浏览器:“请保存一个叫 user_id 的 Cookie,值是 123

📡 实际发生了什么?

当 FastAPI 执行这行代码时,它会在 HTTP 响应头(Response Headers) 中添加一行:

1
Set-Cookie: user_id=123; HttpOnly

然后整个响应看起来像这样:

1
2
3
4
5
HTTP/1.1 200 OK
Content-Type: application/json
Set-Cookie: user_id=123; HttpOnly

{"msg": "Logged in"}

🌐 浏览器收到后会:

  1. 解析 Set-Cookie
  2. 在本地(内存或磁盘)保存这个 Cookie:
    • 名字:user_id
    • 值:123
    • 属性:HttpOnly(JS 无法读取)
  3. 以后每次向该域名发请求,自动带上这个 Cookie

🔐 参数详解(你用的三个):

参数 作用 安全建议
key="user_id" Cookie 的名字 用语义化名称,如 session_id
value="123" Cookie 的值 不要直接存用户 ID! 应该存随机 session ID(后面讲)
httponly=True 禁止 JavaScript 读取 强烈建议开启,防 XSS 攻击

📥 2. user_id = request.cookies.get("user_id")

✅ 作用:

从当前请求中读取浏览器自动发送的 Cookie

📤 实际发生了什么?

当浏览器访问 /profile 时,它会自动在 HTTP 请求头(Request Headers) 中加上:

1
Cookie: user_id=123

FastAPI 的 request.cookies 是一个字典,.get("user_id") 就是读取这个值。

image-20251026164036642
1
2
3
4
5
6
sequenceDiagram
participant Client as 客户端(React)
participant Server as 服务器(FastAPI)

Client->>Server: 请求(Request)\n- URL: /login\n- Method: POST\n- Body: {}\n- Headers: ...
Server-->>Client: 响应(Response)\n- Status: 200\n- Set-Cookie: user_id=123\n- Body: {"msg": "Logged in"}

Session

Session(会话) 是一种 服务器端状态管理机制,用于在无状态的 HTTP 协议之上,维护客户端与服务器之间的持续交互状态

  • 核心思想:为每个客户端分配一个唯一的会话标识符(Session ID),服务器通过该 ID 查找对应的会话数据。
  • 存储位置:会话数据(如用户身份、权限、购物车等)存储在服务器端(内存、数据库、缓存等)。
  • 传输载体:Session ID 通常通过 Cookie(最常见)、URL 重写或 HTTP 头在客户端与服务器之间传递。

为什么需要session

HTTP 协议是无状态的:

  • 每次请求都是独立的,服务器不知道你是谁
  • 如果你登录后访问个人页面,服务器怎么知道你是谁?

解决方案

  • Cookie:浏览器存数据(但不安全,不能存敏感信息)
  • Session:服务器存数据,浏览器只存 ID(安全!)

💡 Session 是实现“登录状态”的标准方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sequenceDiagram
participant Browser
participant Server

Browser->>Server: POST /login (用户名+密码)
Server->>Server: 验证成功,创建 Session
Note right of Server: sessions["abc123"] = {"user_id": "123"}
Server-->>Browser: Set-Cookie: session_id=abc123;

Note over Browser: 浏览器保存 session_id

Browser->>Server: GET /profile<br/>Cookie: session_id=abc123
Server->>Server: 查 sessions["abc123"] → user_id=123
Server-->>Browser: {"user_id": "123"}

后端构建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 简化的内存session存储
sessions = {}

@app.post("/login")
def login(response: Response):
# 创建新的session
session_id = str(uuid.uuid4())
session_data = {"user_id": "123", "username": "admin"}

# 将session数据存储到内存字典
sessions[session_id] = session_data

# 设置session cookie
response.set_cookie(
key="session_id",
value=session_id,
httponly=True
)

logger.info(f"用户登录成功,session_id: {session_id}")
return {"msg": "Logged in", "session_id": session_id}

@app.get("/profile")
def profile(request: Request):
# 从cookie中获取session_id
session_id = request.cookies.get("session_id")

if session_id is None:
logger.warning("未找到session cookie")
return {"error": "请先登录", "user_id": None}

# 从内存字典获取session数据
session_data = sessions.get(session_id)

if session_data is None:
logger.error(f"Session不存在: {session_id}")
return {"error": "Session已过期", "user_id": None}

logger.info(f"获取session数据成功: {session_data}")
return {"user_id": session_data.get("user_id"), "username": session_data.get("username")}

重复部分进行省略

JWT

jwt是什么

JWT(JSON Web Token) 是一种 开放标准(RFC 7519),用于在各方之间安全地传输声明(claims)。它是一种紧凑、自包含(self-contained) 的令牌格式,通常用于 身份认证(Authentication)信息交换(Information Exchange)

  • 核心特点
    • 无状态(Stateless):服务器无需存储令牌
    • 自包含:令牌本身包含用户身份和元数据
    • 可验证:通过数字签名确保完整性与真实性

✅ JWT 的本质是 “签名的用户声明”,而非会话 ID。

JWT 的结构

JWT 由三部分组成,用 . 连接:

1
xxxxx.yyyyy.zzzzz
部分 说明 内容示例
Header 令牌类型 + 签名算法 {"alg": "HS256", "typ": "JWT"}
Payload 声明(Claims) {"sub": "123", "name": "Alice", "exp": 1735689600}
Signature 签名(防篡改) HMACSHA256(base64UrlEncode(header)+'.'+base64UrlEncode(payload), secret)

🔐 只有签名部分能防止篡改,Header 和 Payload 只是 Base64Url 编码(可解码!勿存敏感信息)。

JWT 的 sub是什么

在 JSON Web Token (JWT) 中,subSubject(主题)的缩写。

它是 JWT 规范(RFC 7519)中定义的一个注册声明(Registered Claim)。简单来说,sub 用于回答这个问题:“这个 Token 是代表谁的?”

含义:它标识了该 JWT 所面向的主体(Principal)。在绝大多数应用场景中,这个主体就是用户

作用:当服务器收到一个 JWT 时,它通过读取 sub 字段来知道“当前请求是谁发起的”或“当前登录的是哪个用户 ID”。

在 JWT 的 Payload(载荷)部分,sub 通常是这样的:

1
2
3
4
5
6
{
"iss": "https://auth.example.com",
"sub": "1234567890", <-- 这里是 User ID
"name": "John Doe",
"iat": 1516239022
}

为什么用 JWT,相比较session优势在哪

  1. 无状态(Stateless) → 天然支持水平扩展
  • Session:服务器必须存储会话数据(内存/Redis),所有实例需共享存储。
  • JWT:服务器不存储任何状态,任意实例均可独立验证 Token。

🌐 适用场景:微服务架构、Serverless、高并发 API 网关
💡 优势:去中心化、无共享存储依赖、部署简单

  1. 跨域与跨平台原生支持
  • Session:依赖 Cookie,受同源策略限制,跨域需复杂 CORS 配置。
  • JWT:通过 Authorization: Bearer <token> 传输,无 Cookie 依赖

📱 适用场景: - 移动 App(iOS/Android) - 多前端(Web + 小程序 + 桌面端) - 第三方集成(如开放平台 API)

💡 优势:一套 API 服务所有客户端

  1. 自包含(Self-contained) → 减少数据库查询
  • Session:每次请求需查存储(如 Redis)获取用户信息。
  • JWT:Payload 可直接携带用户 ID、角色、权限等信息。
1
2
3
4
5
6
{
"sub": "user123",
"role": "admin",
"permissions": ["read", "write"],
"exp": 1735689600
}

优势减少 I/O,提升性能(尤其对权限频繁校验的系统)

  1. 标准化 & 生态成熟
  • JWT 是 IETF 标准(RFC 7519),各语言均有高质量库。
  • OAuth 2.0、OpenID Connect 深度集成,适合现代身份认证体系。

🔌 优势无缝对接 Auth0、Keycloak、Firebase 等身份提供商

1
2
3
4
5
6
7
8
9
10
11
12
13
sequenceDiagram
participant Client
participant Server

Client->>Server: 1. POST /login (用户名+密码)
Server->>Server: 2. 验证凭证
Server-->>Client: 3. 返回 JWT: {token: "xxxx.yyyy.zzzz"}

Note over Client: 4. 客户端保存 Token(内存/LocalStorage)

Client->>Server: 5. GET /profile<br/>Authorization: Bearer xxxx.yyyy.zzzz
Server->>Server: 6. 验证签名 + 检查 exp
Server-->>Client: 7. 返回受保护资源

JWT的缺点

  1. 无法主动撤销:一旦签发,在过期前始终有效,难以实现“立即登出”或“账号禁用”。
  2. XSS 风险高:通常需存于前端(如内存或 localStorage),若存在 XSS 漏洞,Token 易被窃取。
  3. Payload 可解码:Header 和 Payload 仅 Base64 编码,不是加密,不能存敏感信息。
  4. 体积较大:每次请求都要携带完整 Token,增加带宽开销(相比 Session ID)。

jwt三种签名算法

1. HS256 (HMAC with SHA-256)

类型:对称加密 (Symmetric)

这是最简单、最常见的算法,适合单体应用或内部受信任的服务之间通信。

具体原理

“共享密钥”模式。

签发 Token 的一方(认证服务器)和验证 Token 的一方(应用服务器)必须持有完全相同的密钥(Secret)。

  1. 签名过程: 将 Header 和 Payload 进行 Base64Url 编码,用 . 连接。然后使用 Secret 对这个字符串进行 SHA-256 哈希计算。
  2. 验证过程: 接收方收到 Token 后,用同一个 Secret 对 Header 和 Payload 再次进行同样的哈希计算。如果计算出的结果与 Token 中的签名一致,则验证通过。

核心公式

$$Signature = HMACSHA256(base64Url(Header) + "." + base64Url(Payload), secret)$$

举例说明

场景: 你是一个独自开发的网站站长。

Secret: "my_super_secret_password"(只有你的服务器知道)。

流程:

  1. 用户登录,你的代码生成 Payload {"user": "admin"}
  2. 代码混合 Secret 进行哈希,生成签名 abc123...
  3. 当用户下次带着 Token 请求时,你的代码再次用 "my_super_secret_password" 算一遍。如果算出来也是 abc123...,说明 Token 没被黑客改过。

优点: 速度极快,生成签名极其简单。

缺点: 密钥一旦泄露,黑客既能验证也能伪造任意 Token。不适合多方系统(因为要把密钥分发给所有验证者,风险扩散)。

2. RS256 (RSA Signature with SHA-256)

类型:非对称加密 (Asymmetric)

这是企业级应用、微服务架构和 OAuth2/OIDC(如 Auth0, Okta)中的行业标准

具体原理

“私钥签名,公钥验证”模式。 使用一对密钥:私钥 (Private Key)公钥 (Public Key)

  • 私钥: 只有认证服务器(Auth Server)拥有,绝不公开。用于生成签名
  • 公钥: 可以公开给任何服务(资源服务器、网关等)。用于验证签名

核心原理

RSA 利用了大数因数分解的数学难题。私钥加密的数据,只能用公钥解密(在签名场景下,这意味着只有公钥能验证是由私钥签名的)。

举例说明

  • 场景: Google 颁发 Token 给第三方 App(如 Notion)。
  • 私钥: Google 只有一把,锁在 Google 的保险柜里。
  • 公钥: 公布在网上(JWKS 端点),Notion 可以随时下载。
  • 流程:
    1. Google 用私钥给 Payload {"sub": "123"} 盖了一个“数字章”(签名)。
    2. Notion 收到 Token,去 Google 下载公钥
    3. Notion 用公钥验证这个“章”。如果验证通过,Notion 就能 100% 确定这个 Token 是 Google 签发的,而不是黑客伪造的。
  • 优点: 安全性高。即使公钥泄露,黑客也只能验证 Token,无法伪造 Token。非常适合微服务(Auth 服务发 Token,其他几十个微服务只拿公钥验 Token)。
  • 缺点: 签名速度比 HS256 慢,生成的签名字符串较长。

3. ES256 (ECDSA using P-256 and SHA-256)

类型:非对称加密 (Asymmetric - Elliptic Curve)

这是目前推荐的新兴标准。它在保持非对称加密安全性的同时,解决了 RSA 的性能和体积问题。

具体原理

“椭圆曲线”模式。 同样使用公钥和私钥,但基于椭圆曲线密码学 (ECC)。 与 RSA 相比,ECC 可以在密钥长度短得多的情况下,提供同等甚至更高的安全性。

核心原理

它利用了椭圆曲线上的离散对数问题。简而言之,在一个特定的数学曲线上做点运算非常容易,但反向推导非常困难。

举例说明

场景: 需要高并发、低带宽的物联网 (IoT) 设备或移动端 App。

对比 RSA:

  • 要达到 128 位安全级别,RS256 需要 3072 位的密钥(生成的 Token 会很长,占用流量)。
  • ES256 只需要 256 位的密钥(Token 非常短,传输快)。

流程: 逻辑与 RS256 一样(私钥签、公钥验),但数学计算过程不同。

优点:

  1. Token 更短:节省带宽,HTTP Header 更小。
  2. 生成速度快:在某些硬件上,签名速度比 RSA 更快。

缺点: 相对较新,极老旧的系统可能不支持;数学原理比 RSA 更复杂,排查问题稍难。

后端构建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
from pydantic import BaseModel
from datetime import datetime, timedelta
import jwt

# JWT配置
SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"

# 简单的用户数据
users = {
"admin": {"user_id": "123", "username": "admin", "password": "admin123"}
}


def create_token(username: str) -> str:
"""创建JWT token"""
# 设置token过期时间为24小时
expire = datetime.utcnow() + timedelta(hours=24)
# 构建JWT载荷,包含用户名和过期时间
payload = {
"sub": username, # subject: 用户名
"exp": expire # expiration: 过期时间
}
# 使用密钥和算法对载荷进行编码生成token
return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)

def verify_token(token: str) -> str:
"""验证JWT token"""
# 解码JWT token获取载荷信息
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
# 从载荷中提取用户名
username = payload.get("sub")
return username

@app.post("/login")
def login(response: Response):
"""用户登录"""
# 简化登录,直接使用默认用户
username = "admin"
# 从用户字典中获取用户信息
user = users.get(username)

# 为用户创建JWT token
token = create_token(username)
# 将token设置为httponly cookie,与现有前端兼容
response.set_cookie(key="session_id", value=token, httponly=True)
# 返回token信息和登录成功消息
return {"access_token": token, "token_type": "bearer", "msg": "Logged in"}

@app.get("/profile")
def profile(request: Request):
"""获取用户信息"""
# 首先尝试从cookie获取token(兼容现有前端)
token = request.cookies.get("session_id")

# 如果cookie中没有token,尝试从Authorization header获取
if not token:
auth_header = request.headers.get("authorization")
# 检查是否为Bearer token格式
if auth_header and auth_header.startswith("Bearer "):
# 提取Bearer后面的token部分
token = auth_header.split(" ")[1]

# 验证token并获取用户名
username = verify_token(token)
# 根据用户名获取用户信息
user = users.get(username)
# 返回用户ID和用户名
return {"user_id": user["user_id"], "username": user["username"]}
image-20251026164113650

参考资料

JWT身份认证算法、落地方案及优缺点_哔哩哔哩_bilibili

Cookie、Session、Token究竟区别在哪?如何进行身份认证,保持用户登录状态?_哔哩哔哩_bilibili

JWT身份认证算法、落地方案及优缺点_哔哩哔哩_bilibili