Cookie
HTTP Cookie(通常简称为 Cookie)是由
服务器通过 HTTP 响应头 Set-Cookie
发送给用户代理(如浏览器)的一小段数据,用户代理随后会在后续的、满足条件的
HTTP 请求中,通过 Cookie 请求头自动将其发送回服务器。
- 核心目的:在无状态的 HTTP 协议之上,实现
状态管理(State Management) 和
客户端数据持久化。
浏览器会:
- 保存它
- 在后续同域名的请求中,自动通过
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 中的:
通过 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 2 3 4 5 6
| function App() { // 组件逻辑 return ( // JSX - 描述UI结构 ) }
|
- App是一个 JavaScript函数
- 函数名就是 组件名 (必须大写开头)
- 返回 JSX (类似HTML的语法)
- 状态管理(State)
1
| const [isLoggedIn, setIsLoggedIn] = useState(false)
|
isLoggedIn - 状态变量 - 存储 当前的登录状态 - 初始值是 false
(未登录) - 只能 读取 ,不能直接修改
setIsLoggedIn - 状态更新函数
- 用来 更新 isLoggedIn 的值
- 调用时会 触发组件重新渲染
- 是修改状态的 唯一正确方式
useState(false) - Hook调用
- false 是 初始值 ,表示默认未登录
- 返回一个 数组 : [当前值, 更新函数]
- 事件处理
1 2 3
| const handleLoginSuccess = () => { setIsLoggedIn(true) }
|
- 组件可以 响应用户交互
- 处理点击、输入等事件
- 更新状态,触发UI更新
- 条件渲染
1 2 3 4 5 6
| {!isLoggedIn ? ( <Login onLoginSuccess= {handleLoginSuccess} /> ) : ( <Profile isLoggedIn={isLoggedIn} /> )}
|
- 根据 状态条件 显示不同内容
- 动态决定渲染哪些子组件
- 组件组合
1 2 3
| <Login onLoginSuccess={handleLoginSuccess} / > <Profile isLoggedIn={isLoggedIn} />
|
- 大组件由 小组件组合 而成
- 通过 Props 传递数据给子组件
组件组合完整的数据流
- 父组件(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} /> }
|
- 子组件(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机制
POST /login:登录成功,服务器“记住”用户
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"}
|
🌐 浏览器收到后会:
- 解析
Set-Cookie 头
- 在本地(内存或磁盘)保存这个 Cookie:
- 名字:
user_id
- 值:
123
- 属性:
HttpOnly(JS 无法读取)
- 以后每次向该域名发请求,自动带上这个 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) 中加上:
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 由三部分组成,用 . 连接:
| 部分 |
说明 |
内容示例 |
| 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) 中,sub 是
Subject(主题)的缩写。
它是 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优势在哪
- 无状态(Stateless) → 天然支持水平扩展
- Session:服务器必须存储会话数据(内存/Redis),所有实例需共享存储。
- JWT:服务器不存储任何状态,任意实例均可独立验证
Token。
🌐 适用场景:微服务架构、Serverless、高并发 API
网关
💡 优势:去中心化、无共享存储依赖、部署简单
- 跨域与跨平台原生支持
- Session:依赖 Cookie,受同源策略限制,跨域需复杂
CORS 配置。
- JWT:通过
Authorization: Bearer <token> 传输,无 Cookie
依赖。
📱 适用场景: - 移动 App(iOS/Android) -
多前端(Web + 小程序 + 桌面端) - 第三方集成(如开放平台 API)
💡 优势:一套 API 服务所有客户端
- 自包含(Self-contained) → 减少数据库查询
- Session:每次请求需查存储(如
Redis)获取用户信息。
- JWT:Payload 可直接携带用户
ID、角色、权限等信息。
1 2 3 4 5 6
| { "sub": "user123", "role": "admin", "permissions": ["read", "write"], "exp": 1735689600 }
|
⚡ 优势:减少
I/O,提升性能(尤其对权限频繁校验的系统)
- 标准化 & 生态成熟
- 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的缺点
- 无法主动撤销:一旦签发,在过期前始终有效,难以实现“立即登出”或“账号禁用”。
- XSS 风险高:通常需存于前端(如内存或
localStorage),若存在 XSS 漏洞,Token 易被窃取。
- Payload 可解码:Header 和 Payload 仅 Base64
编码,不是加密,不能存敏感信息。
- 体积较大:每次请求都要携带完整
Token,增加带宽开销(相比 Session ID)。
jwt三种签名算法
1. HS256 (HMAC with SHA-256)
类型:对称加密 (Symmetric)
这是最简单、最常见的算法,适合单体应用或内部受信任的服务之间通信。
具体原理
“共享密钥”模式。
签发 Token 的一方(认证服务器)和验证 Token
的一方(应用服务器)必须持有完全相同的密钥(Secret)。
- 签名过程: 将 Header 和 Payload 进行 Base64Url
编码,用
. 连接。然后使用 Secret
对这个字符串进行 SHA-256 哈希计算。
- 验证过程: 接收方收到 Token 后,用同一个
Secret 对 Header 和 Payload
再次进行同样的哈希计算。如果计算出的结果与 Token
中的签名一致,则验证通过。
核心公式
$$Signature = HMACSHA256(base64Url(Header)
+ "." + base64Url(Payload), secret)$$
举例说明
场景: 你是一个独自开发的网站站长。
Secret:
"my_super_secret_password"(只有你的服务器知道)。
流程:
- 用户登录,你的代码生成 Payload
{"user": "admin"}。
- 代码混合 Secret 进行哈希,生成签名
abc123...。
- 当用户下次带着 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
可以随时下载。
- 流程:
- Google 用私钥给 Payload
{"sub": "123"}
盖了一个“数字章”(签名)。
- Notion 收到 Token,去 Google 下载公钥。
- 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
一样(私钥签、公钥验),但数学计算过程不同。
优点:
- Token 更短:节省带宽,HTTP Header 更小。
- 生成速度快:在某些硬件上,签名速度比 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