FastAPI 实现 SSO,从协议原理到生产级落地

FastAPI 本身并不提供一套开箱即用的 SSO 系统,它更像是承载业务接口的地基。真正完成单点登录的,是外部身份提供商、OIDC/OAuth 2.0 协议,以及 FastAPI 应用内部的会话管理。实践中,最稳妥的组合通常是 OpenID Connect + Authorization Code Flow + PKCE,FastAPI 则作为 OIDC 客户端,也叫 Relying Party。

下面从原理、核心概念、代码实现、安全设计和生产部署几个层面,把这条链路完整拆开。

一、SSO 到底是怎样发生的

SSO 的关键不是让多个系统共享同一个 Cookie,而是让它们共同信任同一个身份提供商,例如 Keycloak、Microsoft Entra ID、Okta、Auth0 或企业自建 OIDC 服务。

假设公司有系统 A 和系统 B。用户第一次访问系统 A 时,被重定向到身份提供商登录。之后再访问系统 B,系统 B同样会把浏览器重定向到身份提供商,但身份提供商已经拥有登录会话,因此不再要求输入密码,而是直接签发新的授权码并跳回系统 B。

也就是说:

  • 系统 A 有自己的本地会话 Cookie
  • 系统 B 也有自己的本地会话 Cookie
  • 身份提供商维护统一的登录会话
  • 多个系统通过重定向间接复用这次登录状态

这才是 SSO,而不是粗暴地在多个域名之间共享 Cookie。

sequenceDiagram participant U as 用户浏览器 participant A as FastAPI 应用 A participant I as 身份提供商 IdP participant B as FastAPI 应用 B U->>A: 访问受保护页面 A-->>U: 重定向至 IdP U->>I: 登录并授权 I-->>U: 携带 authorization code 跳回 A U->>A: 请求 callback?code=... A->>I: 后端用 code 换取 Token I-->>A: ID Token / Access Token A-->>U: 创建应用 A 的会话 Cookie U->>B: 访问应用 B B-->>U: 重定向至同一个 IdP U->>I: 发起认证请求 Note over U,I: IdP 已存在登录会话 I-->>U: 无须再次输入密码,直接跳回 B U->>B: 携带新的 authorization code B-->>U: 创建应用 B 的会话 Cookie

OpenID Connect 在 OAuth 2.0 之上增加了用户身份认证能力。OAuth 2.0 更关心客户端是否有权访问资源,OIDC 则回答了用户是谁。Authorization Code Flow 中,浏览器只传递短期授权码,真正的 Token 由 FastAPI 后端通过服务器间通信换取,因此比直接把 Token 暴露在浏览器地址栏中更稳妥。


二、实现 SSO 必须理解的核心概念

协议名不少,但不用被术语吓住。把每个对象在登录链路中的职责理清,整个系统就会变得相当直观。

1. 身份提供商与客户端

概念 常见缩写 承担的角色 典型实例
Identity Provider IdP 登录用户、维护统一会话、签发 Token Keycloak、Okta、Entra ID
Relying Party RP 信任 IdP,并消费身份结果 FastAPI 应用
Resource Server RS 接收 Access Token,提供受保护 API FastAPI API 服务
User Agent UA 承载跳转和 Cookie 浏览器
End User --- 被认证的自然人 系统用户

一个 FastAPI 服务可能同时扮演 RP 和 Resource Server。比如网页登录使用 OIDC,API 接口又负责校验调用方携带的 Access Token。

2. OAuth 2.0 和 OIDC 的关系

OAuth 2.0 是授权框架,不是完整的登录协议。

它主要表达这样的关系:

用户允许某个客户端,在限定范围内访问某些资源。

OIDC 在 OAuth 2.0 之上引入了:

  • openid scope
  • ID Token
  • UserInfo Endpoint
  • 标准用户声明,如 subnameemail
  • 身份提供商发现机制
  • 更明确的身份认证语义

因此,用 GitHub OAuth 获取用户资料可以拼出一套登录功能,但严格意义上的标准化 SSO,通常优先采用 OIDC。

3. Authorization Code Flow

OIDC Web 应用最常见的流程如下:

  1. FastAPI 生成登录请求。
  2. 浏览器跳转到 IdP 的 Authorization Endpoint。
  3. 用户在 IdP 完成登录。
  4. IdP 将浏览器重定向到 FastAPI 的回调地址。
  5. 回调地址收到一个短期、一次性的 code
  6. FastAPI 后端调用 Token Endpoint,用 code 换取 Token。
  7. FastAPI 验证 Token,识别用户并创建本地会话。

授权码不能直接访问业务 API,而且通常只能使用一次,生命周期也很短。

4. 几种 Token 的区别

Token 回答的问题 主要消费者 典型用途
ID Token 用户是谁,本次认证如何完成 OIDC 客户端 建立登录身份
Access Token 持有者可以访问哪些资源 Resource Server 调用受保护 API
Refresh Token 如何获得新的 Access Token 客户端与 IdP 延长访问能力
Authorization Code 如何安全地换取 Token 客户端后端 登录回调中的临时凭证

这里最容易踩坑的一点是,不要拿 ID Token 当作 API Access Token 使用

ID Token 的受众通常是完成登录的客户端,aud 往往对应 OIDC Client ID。Access Token 的受众应当是具体 API。两者都可能长得像 JWT,但用途完全不同。

5. JWT 与 Claims

OIDC 的 ID Token 通常采用 JWT 格式,由三部分组成:

text 复制代码
header.payload.signature

常见声明包括:

  • iss,签发者,也就是 IdP 地址
  • sub,该签发者范围内稳定的用户标识
  • aud,Token 的目标接收方
  • exp,过期时间
  • iat,签发时间
  • nonce,绑定本次浏览器认证请求
  • auth_time,用户实际完成认证的时间
  • acr,认证强度,例如是否经过 MFA
  • azp,被授权的客户端

本地用户应当通过 iss + sub 进行关联,而不是只依赖邮箱。邮箱可能修改、复用,也未必完成验证;sub 才是 IdP 为该用户提供的稳定标识。

6. statenonce 和 PKCE

这三个参数长得都像随机字符串,但防御的风险不同。

  • state

    将登录发起请求与回调绑定,主要防御登录 CSRF,也可承载经过保护的流程上下文。

  • nonce

    将 ID Token 与本次认证请求绑定,用来降低 Token 重放和响应注入风险。

  • PKCE

    客户端先生成 code_verifier,再派生出 code_challenge。回调换取 Token 时必须提交原始 code_verifier,即使授权码被截获,攻击者也很难单独使用它。

PKCE 最初主要解决移动端和桌面端等公共客户端的授权码劫持问题,现在对于 Web 应用也属于推荐的纵深防御措施。


三、FastAPI 中的完整实现

Python 生态中,使用 Authlib 作为 OIDC 客户端是较成熟的做法。它能够读取 OIDC Discovery 文档,完成重定向、授权码交换、签名密钥发现以及 ID Token 解析。

1. 安装依赖

bash 复制代码
pip install fastapi uvicorn authlib httpx itsdangerous

如果后续需要自行验证 JWT,还可以安装:

bash 复制代码
pip install pyjwt cryptography

2. 在身份提供商中注册客户端

需要在 Keycloak、Okta 或其他 IdP 中创建一个 OIDC Client,并配置:

text 复制代码
Client ID: fastapi-web
Client Type: confidential
Redirect URI: https://app.example.com/auth/callback
Post Logout Redirect URI: https://app.example.com/
Allowed Origin: https://app.example.com

生产环境应当精确登记回调地址,避免使用宽泛的通配符。

IdP 通常会提供 Discovery 地址:

text 复制代码
https://id.example.com/realms/company/.well-known/openid-configuration

这个文档会告诉客户端:

  • Authorization Endpoint 在哪里
  • Token Endpoint 在哪里
  • UserInfo Endpoint 在哪里
  • JWKS 公钥地址在哪里
  • 支持哪些签名算法
  • 是否提供注销、撤销等能力

3. 基础代码

下面是一个通用 OIDC 客户端示例,适用于大多数支持标准发现文档的身份提供商。

python 复制代码
import os
from urllib.parse import urlparse

from authlib.integrations.starlette_client import OAuth
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse, RedirectResponse
from starlette.middleware.sessions import SessionMiddleware

app = FastAPI()

app.add_middleware(
    SessionMiddleware
    secret_key=os.environ["SESSION_SECRET"]
    session_cookie="app_session"
    max_age=8 * 60 * 60
    same_site="lax"
    https_only=True
)

oauth = OAuth()

oauth.register(
    name="company_sso"
    client_id=os.environ["OIDC_CLIENT_ID"]
    client_secret=os.environ["OIDC_CLIENT_SECRET"]
    server_metadata_url=os.environ["OIDC_DISCOVERY_URL"]
    client_kwargs={
        "scope": "openid profile email"
        "code_challenge_method": "S256"
    }
)

这里的 SessionMiddleware 不只用于保存登录用户。Authlib 在授权跳转过程中还需要临时保存 state 等上下文,以便在回调阶段完成校验。

4. 登录入口

python 复制代码
def safe_next_url(value: str | None) -> str:
    if not value:
        return "/"

    parsed = urlparse(value)

    # 只允许站内相对地址,防止开放重定向
    if parsed.scheme or parsed.netloc or not value.startswith("/"):
        return "/"

    return value


@app.get("/login")
async def login(request: Request, next: str = "/"):
    request.session["login_next"] = safe_next_url(next)

    redirect_uri = request.url_for("auth_callback")

    return await oauth.company_sso.authorize_redirect(
        request
        redirect_uri
    )

authorize_redirect() 会生成授权请求并把浏览器送往 IdP。具体版本和提供商配置不同,Authlib 可以负责生成及保存 statenonce 和 PKCE 相关参数。

不要不加校验地将 next 放进回调跳转,否则攻击者可能构造:

text 复制代码
/login?next=https://phishing.example.com

用户完成真实登录后,又被送往钓鱼网站。这类问题叫作 Open Redirect

5. 登录回调

python 复制代码
@app.get("/auth/callback", name="auth_callback")
async def auth_callback(request: Request):
    try:
        token = await oauth.company_sso.authorize_access_token(request)
    except Exception as exc:
        raise HTTPException(
            status_code=401
            detail="SSO 登录回调校验失败"
        ) from exc

    userinfo = token.get("userinfo")

    if not userinfo:
        userinfo = await oauth.company_sso.userinfo(token=token)

    issuer = userinfo.get("iss")
    subject = userinfo.get("sub")

    if not subject:
        raise HTTPException(
            status_code=401
            detail="身份提供商没有返回 sub"
        )

    # 实际项目中应查询或创建本地用户
    local_user = {
        "id": f"{issuer}|{subject}"
        "issuer": issuer
        "subject": subject
        "name": userinfo.get("name")
        "email": userinfo.get("email")
        "email_verified": userinfo.get("email_verified", False)
    }

    next_url = safe_next_url(request.session.get("login_next"))

    # 清除授权阶段的临时状态,减少会话固定风险
    request.session.clear()

    # Cookie 中只保存最少量信息
    request.session["user"] = {
        "id": local_user["id"]
        "name": local_user["name"]
    }

    return RedirectResponse(next_url, status_code=302)

生产系统中,回调阶段通常还要完成一系列本地业务处理:

  • iss + sub 查询身份绑定记录
  • 第一次登录时创建本地用户
  • 同步昵称、头像和已验证邮箱
  • 将 IdP Group 或 Role 映射成本地权限
  • 检查用户是否被禁用
  • 记录登录时间、IP、设备和认证方式
  • 创建新的服务端会话
  • 写入安全审计日志

不要把 IdP 返回的所有 Claims 都无条件写入数据库。身份数据会变化,而且其中可能存在应用不需要的敏感字段。

6. 保护页面

python 复制代码
from fastapi import Depends


def require_login(request: Request) -> dict:
    user = request.session.get("user")

    if not user:
        raise HTTPException(
            status_code=401
            detail="尚未登录"
        )

    return user


@app.get("/me")
async def get_current_user(
    user: dict = Depends(require_login)
):
    return {
        "authenticated": True
        "user": user
    }


@app.get("/dashboard")
async def dashboard(
    user: dict = Depends(require_login)
):
    return {
        "message": f"欢迎回来,{user.get('name') or user['id']}"
    }

这种模式适合传统 Web 应用或 BFF 架构。浏览器只保存会话 Cookie,Access Token 和 Refresh Token 不直接暴露给前端 JavaScript。

7. 注销

注销通常分为三个层次:

  1. 本地注销,只删除 FastAPI 应用会话。
  2. Token 撤销,让 Refresh Token 或 Access Token 失效。
  3. IdP 注销,结束身份提供商的统一登录会话。

只做本地注销后,用户再次点击登录,IdP 可能因为统一会话仍然存在而立刻把用户登录回来。这不是注销失败,而是只完成了局部退出。

python 复制代码
@app.post("/logout")
async def logout(request: Request):
    request.session.clear()

    response = JSONResponse(
        {"message": "本地会话已退出"}
    )
    response.delete_cookie(
        "app_session"
        path="/"
    )

    return response

如果 IdP 的 Discovery 文档提供 end_session_endpoint,可以在清除本地会话后把浏览器重定向到该地址。具体参数需要按照提供商要求设置,常见参数包括:

  • id_token_hint
  • post_logout_redirect_uri
  • client_id
  • state

不能假设所有 IdP 都支持完全相同的单点注销机制。特别是在多个系统并存时,全局注销的传播方式和可靠性往往比登录复杂得多。


四、会话模式与 Bearer Token 模式

FastAPI 登录成功后,通常有两种身份维持方式。选哪一种,要看你的系统是服务网页,还是提供纯 API。

浏览器 Web 应用,优先采用 BFF 会话

浏览器登录成功后,只接收:

text 复制代码
Set-Cookie: app_session=...

后端保存:

  • 本地用户 ID
  • IdP 会话关联信息
  • Access Token
  • Refresh Token
  • Token 过期时间

前端 JavaScript 不直接读取 Token。

这种设计的优势很实际:

  • Token 不容易被 XSS 脚本直接窃取
  • 刷新 Token 的逻辑集中在后端
  • 可在服务端主动废除会话
  • 权限检查与审计更统一

不过,只要认证依赖 Cookie,就必须考虑 CSRF。修改数据的接口应使用:

  • CSRF Token
  • SameSite=Lax 或更严格的策略
  • OriginReferer 校验
  • 非幂等操作只接受 POSTPUTPATCHDELETE

纯 API,采用 Bearer Access Token

移动端、CLI、第三方服务或独立 SPA 访问 API 时,通常发送:

http 复制代码
Authorization: Bearer eyJ...

FastAPI API 需要验证:

  • Token 签名
  • iss
  • aud
  • exp
  • nbf
  • 允许的签名算法
  • scope、role 或 permission

下面展示核心结构:

python 复制代码
import os

import jwt
from fastapi import Depends
from fastapi.security import HTTPAuthorizationCredentials
from fastapi.security import HTTPBearer
from jwt import PyJWKClient

security = HTTPBearer()

OIDC_ISSUER = os.environ["OIDC_ISSUER"]
OIDC_JWKS_URI = os.environ["OIDC_JWKS_URI"]
API_AUDIENCE = os.environ["OIDC_API_AUDIENCE"]

jwks_client = PyJWKClient(OIDC_JWKS_URI)


def verify_access_token(
    credentials: HTTPAuthorizationCredentials = Depends(security)
) -> dict:
    encoded_token = credentials.credentials

    try:
        signing_key = jwks_client.get_signing_key_from_jwt(
            encoded_token
        )

        claims = jwt.decode(
            encoded_token
            signing_key.key
            algorithms=["RS256"]
            issuer=OIDC_ISSUER
            audience=API_AUDIENCE
            options={
                "require": ["exp", "iss", "sub"]
            }
        )

        return claims

    except jwt.PyJWTError as exc:
        raise HTTPException(
            status_code=401
            detail="Access Token 无效或已经过期"
            headers={"WWW-Authenticate": "Bearer"}
        ) from exc


@app.get("/api/orders")
async def list_orders(
    claims: dict = Depends(verify_access_token)
):
    return {
        "user": claims["sub"]
        "orders": []
    }

这里不能从 JWT Header 读取任意 alg 后直接接受。服务端应配置明确的算法白名单,例如只允许 RS256 或 IdP 实际使用的非对称算法。

同样不要把客户端的 Client ID 想当然地当成 API Audience。很多身份平台需要单独注册 API Resource,并为它配置独立的 Audience。


五、权限设计不等于登录设计

用户成功登录,只能说明身份通过了认证,并不代表他有权执行所有操作。

可以把认证与授权理解为两个连续问题:

  • Authentication,你是谁
  • Authorization,你能做什么

常见授权模型包括:

RBAC,基于角色

text 复制代码
用户 → 角色 → 权限

例如:

text 复制代码
alice → finance_manager → invoice:approve

适合组织结构和权限边界较稳定的企业系统。

Scope,基于访问范围

Access Token 中可能包含:

json 复制代码
{
  "scope": "openid profile orders:read orders:write"
}

FastAPI 可以检查某个接口是否要求 orders:write

python 复制代码
def require_scope(required_scope: str):
    def checker(
        claims: dict = Depends(verify_access_token)
    ):
        scopes = set(claims.get("scope", "").split())

        if required_scope not in scopes:
            raise HTTPException(
                status_code=403
                detail="权限不足"
            )

        return claims

    return checker


@app.post("/api/orders")
async def create_order(
    claims: dict = Depends(require_scope("orders:write"))
):
    return {"created_by": claims["sub"]}

IdP 权限和本地权限的取舍

完全依赖 IdP 中的角色,管理起来统一,但业务权限可能不断膨胀。完全使用本地权限,业务表达更灵活,却容易出现多个系统各管一套。

比较稳妥的办法是:

  • IdP 管理身份、组织、基础组和粗粒度角色
  • FastAPI 应用管理业务资源与细粒度权限
  • 登录时同步必要的组和角色
  • 每次访问关键资源时执行本地授权
  • 不把 JWT 当成永久有效的权限快照

例如 IdP 只表达用户属于财务部门,而某张具体发票能不能审批,仍由业务系统根据金额、区域和审批链判断。


六、生产环境中最容易出问题的地方

代码跑通只是起点。SSO 真正的难处,往往藏在代理、Cookie、密钥轮换和多实例部署里。

Starlette 的 SessionMiddleware 使用签名保护 Cookie,可以检测篡改,但 Cookie 内容并不等同于加密存储。用户仍可能读取其中的数据。

因此不要在 Session Cookie 中直接保存:

  • Access Token
  • Refresh Token
  • Client Secret
  • 完整 ID Token
  • 敏感用户资料

更稳妥的模式是:

text 复制代码
浏览器 Cookie → 随机会话 ID → Redis/数据库中的会话记录

Token 保存在服务端,并设置静态加密、过期时间和吊销机制。

2. 多实例必须共享授权临时状态

登录开始时生成的 statenonce、PKCE Verifier,需要在回调阶段读取。如果登录请求落在实例 A,回调落在实例 B,而两边不共享状态,就会随机出现回调失败。

解决方法包括:

  • 使用 Redis 保存服务端会话
  • 使用可靠的共享会话后端
  • 临时使用负载均衡 Sticky Session,但不宜把它当成长久方案
  • 所有实例使用一致的 Session 签名密钥

3. 反向代理必须正确传递协议

若外部地址是:

text 复制代码
https://app.example.com

但 FastAPI 在代理后看到的是:

text 复制代码
http://127.0.0.1:8000

那么 request.url_for() 可能生成错误回调地址。需要正确配置:

  • X-Forwarded-Proto
  • X-Forwarded-Host
  • 可信代理列表
  • Uvicorn 或部署平台的代理头处理
  • IdP 中精确登记的外部 HTTPS 回调地址

不要无条件信任来自任意客户端的转发头,否则攻击者可能影响生成的绝对 URL。

4. 必须验证的 Token 属性

不应只验证 JWT 签名。至少还要检查:

  • iss 是否为预期 IdP
  • aud 是否包含当前客户端或 API
  • exp 是否过期
  • nbf 是否已经生效
  • 签名算法是否位于固定白名单
  • nonce 是否匹配当前认证请求
  • azp 在多 Audience 情况下是否合理
  • JWKS Key ID 是否能在可信密钥集中找到

可以允许几十秒的时钟偏差,但不能因为服务器时间混乱而大幅放宽过期判断。所有服务器都应同步 NTP。

5. Refresh Token 需要像密码一样保护

Refresh Token 往往活得更久,一旦泄露,攻击者可以持续换取新的 Access Token。

生产中应考虑:

  • Refresh Token Rotation
  • 重用检测
  • 服务端加密存储
  • 最长绝对会话期限
  • 空闲会话期限
  • 用户停用时主动吊销
  • 密码修改或风险事件后使会话失效

6. 错误日志不能泄露凭证

下面这类日志应当禁止:

python 复制代码
logger.info("OIDC token response: %s", token)

日志中不应出现:

  • Authorization Header
  • Access Token
  • Refresh Token
  • Authorization Code
  • Client Secret
  • Session Cookie

可以记录 isssub 的脱敏值、Client ID、错误类型、请求 ID、登录时间和结果。

7. SSO 依赖的可用性问题

统一身份提供商一旦故障,所有新登录都可能停止。生产架构应明确:

  • 已建立的本地会话能否继续使用
  • JWKS 是否允许安全缓存
  • IdP 短暂故障时是否重试
  • 管理员是否有受控的紧急访问账号
  • Discovery 和 JWKS 的超时设置
  • 密钥轮换期间如何同时接受新旧公钥

不要在每一个 API 请求中实时访问 IdP 校验 JWT。JWT 的意义之一,就是 Resource Server 可以利用缓存的公钥进行本地验证。


七、一套推荐的落地架构

对于大多数 FastAPI 企业应用,可以采用下面的组合:

flowchart LR Browser[浏览器] --> Proxy[Ingress / Nginx] Proxy --> API[FastAPI BFF] API --> Redis[(Redis 会话)] API --> DB[(业务数据库)] API --> IdP[OIDC 身份提供商] IdP --> Directory[LDAP / AD / 用户目录] IdP --> MFA[MFA / 风险认证] Service[内部服务] --> API API --> JWKS[JWKS 公钥缓存]

推荐策略可以浓缩为以下几条:

  • 使用 OIDC,而不是自己发明登录协议
  • Web 应用使用 Authorization Code Flow
  • 即使是机密客户端,也启用 PKCE
  • 浏览器只保存 HttpOnly + Secure 会话 Cookie
  • Token 存放在服务端 Redis 或数据库
  • 用户身份通过 iss + sub 绑定
  • ID Token 只用于身份认证,不作为业务 API 凭证
  • API 只接受面向自身 Audience 的 Access Token
  • 同时验证签名、Issuer、Audience 和有效期
  • state 防登录 CSRF,nonce 绑定 ID Token,PKCE 防授权码劫持
  • 权限检查放在服务端,不能只相信前端按钮是否可见
  • 本地注销、Token 撤销和 IdP 注销分别设计
  • 对 Token 刷新、密钥轮换、IdP 故障和多实例状态提前做预案

FastAPI 做 SSO 的本质,并不是写一个 /login 接口,而是把外部身份认证、本地用户映射、会话生命周期、Token 验证和业务授权连成一条可信链路。协议负责证明身份,FastAPI 负责把这个身份安全地转化成应用中的用户与权限。边界划清之后,系统会比自建用户名密码登录更统一,也更容易接入 MFA、企业目录和集中审计。


参考资料

  1. OpenID Foundation, OpenID Connect Core 1.0 , openid.net/specs/openi...
  2. IETF, RFC 6749 --- The OAuth 2.0 Authorization Framework , www.rfc-editor.org/rfc/rfc6749
  3. Authlib, FastAPI OAuth Client , docs.authlib.org/en/v1.6.4/c...
  4. IETF, RFC 7636 --- Proof Key for Code Exchange by OAuth Public Clients , www.rfc-editor.org/rfc/rfc7636
  5. IETF, RFC 7519 --- JSON Web Token , www.rfc-editor.org/rfc/rfc7519
  6. Starlette, Session Middleware , www.starlette.io/middleware/...
  7. OpenID Foundation, RP-Initiated Logout 1.0 , openid.net/specs/openi...
  8. FastAPI, Security Documentation , fastapi.tiangolo.com/tutorial/se...
  9. IETF, RFC 8725 --- JSON Web Token Best Current Practices , www.rfc-editor.org/rfc/rfc8725
  10. OWASP, OAuth 2.0 Protocol Cheat Sheet , cheatsheetseries.owasp.org/cheatsheets...
相关推荐
QQ_21696290961 小时前
【源码编号:project86570】SpringBoot电影院在线选座售票系统:电影排片、在线选座、订单购票、后台管理完整实战
java·spring boot·后端
卷无止境1 小时前
FastAPI 与 RustFS 集成指南
后端·python·fastapi
先吃饱再说1 小时前
后端开发绕不开的 SQL:从建表、查询到索引优化,一篇讲透
数据库·后端·sql
用户7813667114451 小时前
Ceph RGW 对象上传完整流程解析:请求解析 → 执行 → 落盘
后端
青 春 记 忆1 小时前
零基础入门python25:新增账目——Decimal、日期和输入校验
python·后端开发
北风toto1 小时前
阿里云 MaxCompute通过python脚本调用odps流程和执行sql
python·阿里云·odps
coderCN1 小时前
HTTP(动静分离)
后端
jj_ccwgw1 小时前
Kafka 集群开机自启动配置指南
后端·kafka
qq_161111271 小时前
Pillow项目深度解析:Python图像处理的终极武器
图像处理·python·pillow·开源库·历史解析