做业务时,”用户登录”这件事几乎绕不过去。自己写一套账号密码系统?又是加密、又是找回密码、又是防刷,光想想就头大。

更聪明的做法是:把身份认证外包给专业的身份提供商(IdP),自己只负责”拿到一个可信的用户身份”。这就是 OIDC(OpenID Connect)协议要解决的事。

今天这篇文章,我会带你用 Python 最好用的 OAuth/OIDC 库 —— Authlib,接入 Amazon Cognito,跑通一个完整的登录 / 回调 / 登出流程。

看完你会收获:

  • OIDC 的核心概念(5 分钟讲明白)
  • Cognito User Pool 的配置步骤(附截图要点)
  • 一份可以直接跑的 Flask 示例代码
  • 生产环境的 6 个避坑建议

文章字数比较多,建议先点个在看,收藏着慢慢看 👇

一、5 分钟搞懂 OIDC

如果你之前听过 OAuth 2.0,那 OIDC 可以理解为:

OIDC = OAuth 2.0 + 身份层(ID Token)

OAuth 2.0 解决的是”授权”(我允许第三方访问我的某些资源),OIDC 在它的基础上加了一个 id_token,专门用来告诉你”这个用户是谁”。

一次标准的 OIDC 登录流程是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
用户 ──点击登录──▶ 你的应用

│ 1. 跳转到 IdP(Cognito)

Cognito 登录页

│ 2. 用户输入账号密码
│ 3. 登录成功,带 code 回跳

你的应用 /callback

│ 4. 用 code 换 token

Cognito /token

│ 5. 返回 id_token + access_token

你的应用校验 id_token

│ 6. 写入 session,登录完成 ✅

涉及的几个关键名词:

名词作用
Issuer身份提供商的地址,比如 Cognito 的 https://cognito-idp.us-east-1.amazonaws.com/{poolId}
Client ID / Secret你的应用在 IdP 那里的身份凭证
Scope想要哪些信息,常用 openid email profile
id_token一个 JWT,里面装着用户的身份信息
access_token用来调用受保护 API 的令牌

记住这几个词,下面的操作就不懵了。

二、Cognito User Pool 配置

Cognito 是 Amazon 家的托管身份服务,免费额度对中小项目非常友好(每月 50000 MAU 免费)。

1. 创建 User Pool

登录 Amazon Console → 搜索 CognitoCreate user pool,一路下一步,关注几个点:

  • Sign-in options:勾选 Email(或手机号,看业务)
  • Password policy:按需配置
  • MFA:建议至少开启 Optional
  • Self-service sign-up:如果允许用户自己注册就打开

2. 创建 App Client

User Pool 创建好之后,进入 ApplicationsApp client ,选择刚刚创建的Client

  • App type:建议选 Confidential client(服务端应用,能安全保存 secret)
  • Authentication flows:勾 ALLOW_USER_SRP_AUTHALLOW_REFRESH_TOKEN_AUTH
  • Hosted UI settings(重点):
    • Allowed callback URLshttp://localhost:5000/auth/callback
    • Allowed sign-out URLshttp://localhost:5000/
    • OAuth 2.0 grant types:勾 Authorization code grant
    • OpenID Connect scopes:勾 openid email profile

3. 配置域名

BrandingDomain 里能够看到域名,比如这样子,这个和回调有关系,需要记住。

1
https://my-demo.auth.us-east-1.amazoncognito.com

4. 记下四样东西

配置完以后,把这四个值抄下来,马上要用:

1
2
3
4
5
Region:        us-east-1
User Pool ID: us-east-1_XXXXXXXXX
Client ID: xxxxxxxxxxxxxxxxxxxxxxxxxx
Client Secret: xxxxxxxxxxxxxxxxxxxxxxxxxx
Domain: https://my-demo.auth.us-east-1.amazoncognito.com

三、开始写代码

1. 装依赖

1
pip install authlib flask python-dotenv

就这么简单,Authlib 把 OIDC 的脏活累活都封装好了。

2. 配置环境变量

新建 .env

1
2
3
4
5
6
7
FLASK_SECRET_KEY=随便一串随机字符串
COGNITO_REGION=us-east-1
COGNITO_USER_POOL_ID=us-east-1_XXXXXXXXX
COGNITO_CLIENT_ID=你的client_id
COGNITO_CLIENT_SECRET=你的client_secret
COGNITO_DOMAIN=https://my-demo.auth.us-east-1.amazoncognito.com
APP_BASE_URL=http://localhost:5000

3. 完整示例代码

新建 app.py

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import os
from flask import Flask, url_for, session, redirect, jsonify
from authlib.integrations.flask_client import OAuth
from dotenv import load_dotenv

load_dotenv()

app = Flask(__name__)
app.secret_key = os.environ["FLASK_SECRET_KEY"]

REGION = os.environ["COGNITO_REGION"]
POOL_ID = os.environ["COGNITO_USER_POOL_ID"]
CLIENT_ID = os.environ["COGNITO_CLIENT_ID"]
SECRET = os.environ["COGNITO_CLIENT_SECRET"]
DOMAIN = os.environ["COGNITO_DOMAIN"].rstrip("/")
BASE_URL = os.environ["APP_BASE_URL"].rstrip("/")

# Cognito 的 OIDC Discovery 地址
ISSUER = f"https://cognito-idp.{REGION}.amazonaws.com/{POOL_ID}"
DISCOVERY_URL = f"{ISSUER}/.well-known/openid-configuration"

oauth = OAuth(app)
oauth.register(
name="cognito",
client_id=CLIENT_ID,
client_secret=SECRET,
server_metadata_url=DISCOVERY_URL, # Authlib 自动拉取配置
client_kwargs={
"scope": "openid email",
"code_challenge_method": "S256", # 启用 PKCE
},
)


@app.route("/")
def index():
user = session.get("user")
if user:
return (
f"<h2>你好,{user.get('email')}</h2>"
f"<pre>{user}</pre>"
f'<a href="{url_for("logout")}">退出登录</a>'
)
return f'<a href="{url_for("login")}">使用 Cognito 登录</a>'


@app.route("/auth/login")
def login():
redirect_uri = url_for("auth_callback", _external=True)
return oauth.cognito.authorize_redirect(redirect_uri)


@app.route("/auth/callback")
def auth_callback():
# 一行代码搞定:换 token + 校验 id_token 签名/iss/aud/exp/nonce
token = oauth.cognito.authorize_access_token()
userinfo = token.get("userinfo") or oauth.cognito.userinfo(token=token)
session["user"] = dict(userinfo)
return redirect(url_for("index"))


@app.route("/auth/logout")
def logout():
session.clear()
# Cognito 自己的登出端点(不在 OIDC 标准里)
logout_url = (
f"{DOMAIN}/logout"
f"?client_id={CLIENT_ID}"
f"&logout_uri={BASE_URL}/"
)
return redirect(logout_url)


@app.route("/me")
def me():
user = session.get("user")
if not user:
return jsonify({"error": "未登录"}), 401
return jsonify(user)


if __name__ == "__main__":
app.run(port=5001, debug=True)

4. 跑起来

1
python app.py

浏览器打开 http://localhost:5000/,点击登录,跳到 Cognito 登录页,注册个账号,回跳后就能看到用户信息啦 🎉

四、这段代码里藏了多少”好东西”?

表面上就几十行,实际上 Authlib 替你默默做了这些事:

✅ 自动发现端点

server_metadata_url 指向 Discovery 文档,Authlib 自动解析出授权地址、token 地址、公钥地址……你完全不用手写。

✅ 自动校验 id_token

authorize_access_token() 内部会:

  1. 用 code 换 token
  2. jwks_uri 拉公钥
  3. 校验 id_token 的签名
  4. 校验 iss(签发者)是不是对的
  5. 校验 aud(受众)是不是你的 client_id
  6. 校验 exp(过期时间)
  7. 校验 nonce(防重放)

任何一步出错都会抛异常,你不需要手写 JWT 解析。

✅ 自动启用 PKCE

code_challenge_method: S256 开启 PKCE,有效防止授权码被劫持。这是 OAuth 2.1 的推荐做法。

✅ 自动管理 state 和 nonce

state 防 CSRF,nonce 防重放,都写到 session 里了,你不用操心。

五、生产环境避坑指南

Demo 能跑不代表能上线,下面这 6 个点请务必注意:

1️⃣ 一定要用 HTTPS

回调地址必须是 https://,session cookie 记得加上 SecureHttpOnly

1
2
3
4
5
app.config.update(
SESSION_COOKIE_SECURE=True,
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE="Lax",
)

2️⃣ Secret 不要写死在代码里

用 Amazon Secrets Manager 或 Parameter Store 读取,千万别提交到 Git。

3️⃣ 用户 ID 请用 sub,不要用 email

email 可能被用户改掉,sub 是 Cognito 生成的稳定唯一 ID,把它作为你数据库里的用户主键。

4️⃣ session 别放进程内存

多实例部署时,state / nonce 会丢。改用 Redis 存 session:

1
pip install flask-session redis

5️⃣ 登出要调 Cognito 的 /logout

只清本地 session 没用,Cognito Hosted UI 还有自己的 cookie,用户下次点登录会直接免密进来。一定要跳转到 Cognito 的 /logout

6️⃣ 再做一次业务层校验

拿到 id_token 后,除了 Authlib 的标准校验,最好再确认 token_use == "id"(Cognito 特有字段),避免把 access_token 当 id_token 用。

六、常见报错速查

报错信息原因
redirect_mismatch回调地址跟 App client 里配的对不上(末尾斜杠、http/https 都要一致)
invalid_clientsecret 错了,或 Public client 不该发 secret
nonce 校验失败Flask session 没持久化,重启后就丢了
id_token 签名错误Region 或 Pool ID 配错,导致 issuer 不匹配

七、扩展阅读


写在最后

OIDC 本质上就两件事:拿到一个可信的 id_token校验它。Authlib 把这两件事做到了极致简洁,10 行代码就能接入任何符合 OIDC 标准的身份提供商 —— Cognito、Auth0、Keycloak、Okta、Google、微信开放平台…… 换一个 IdP,只要换 Discovery URL 就行。

如果这篇文章对你有帮助,欢迎点赞 + 在看 + 转发,让更多同行少踩坑 🙏

下一篇想看什么?留言告诉我:

  • ① FastAPI 版 OIDC 接入
  • ② 纯 API 服务如何校验 Bearer Token
  • ③ Cognito 对接Auth0等第三方登录

我们下期见 👋


关注我,一起把后端写得又快又稳。