1. 前置知识:认证与授权是什么? #
在开始学习 Keycloak 之前,需要先理解两个核心概念,否则后续内容会难以理解。
1.1 认证(Authentication)和授权(Authorization)的区别 #
认证:证明"你是谁"。比如你输入用户名和密码,系统验证后确认你是张三。
授权:证明"你能做什么"。比如张三登录后,系统判断他是否有权限删除某条数据。
生活化类比:进小区门禁是认证(证明你是业主),进自家房门是授权(证明你有这间房的钥匙)。
1.2 OAuth2 和 OpenID Connect 是什么? #
OAuth2:一种"授权"的标准协议。通俗讲:你的应用想访问用户在微信里的好友列表,但用户不想把微信密码给你。OAuth2 让用户去微信那里授权,微信给你一个"临时通行证"(Token),你拿这个通行证去取数据,用户密码始终不经过你的手。
OpenID Connect(OIDC):在 OAuth2 基础上加了一层"认证"。除了给你 Token,还告诉你"这个用户是谁"(用户 ID、昵称、邮箱等)。Keycloak 主要支持 OIDC 协议。
为什么需要了解这些? Keycloak 就是一个"认证授权中心",你的 Python 应用通过 OIDC 协议和它打交道。知道这些概念后,后面配置"客户端""重定向 URI"时就不会一头雾水。
2. Keycloak 是什么?能解决什么问题? #
2.1 通俗解释 #
Keycloak 是一个开源的统一登录系统。你可以把它想象成"小区的门卫室":
- 用户只需在门卫室登记一次(登录一次)
- 之后访问小区里的多个楼栋(多个应用),门卫都认识你,不用重复登记
- 门卫还会根据你的身份,决定你能进哪些楼、哪些房间(权限控制)
2.2 核心能解决的问题 #
| 问题 | Keycloak 的解决方案 |
|---|---|
| 每个应用都要自己写登录逻辑 | 统一交给 Keycloak,应用只验证 Token |
| 用户要在多个系统重复登录 | 单点登录(SSO):登录一次,到处通行 |
| 想接微信、GitHub 等第三方登录 | 内置社交登录集成,配置即可 |
| 用户、角色、权限管理混乱 | 提供管理界面,集中管理用户和角色 |
2.3 会用到的主要功能 #
- 单点登录(SSO):用户登录一次,可访问多个应用
- OIDC 协议:通过标准协议获取用户身份和 Token
- 集中式用户管理:在 Keycloak 里创建用户、分配角色
3. 核心概念 #
3.1 Realm(领域/租户) #
是什么:一个独立的"用户王国"。每个 Realm 有自己的一套用户、客户端、角色,互不干扰。
类比:公司 A 和公司 B 都用同一套 Keycloak,但各自一个 Realm,A 的用户看不到 B 的用户。
建议:先用默认的 master Realm 练手,熟练后再创建自己的 Realm。
3.2 Client(客户端) #
是什么:你的应用程序在 Keycloak 里的"注册身份"。每个要接入 Keycloak 的应用(网站、API、移动端)都需要创建一个 Client。
关键配置:
- Client ID:唯一标识,如
my-python-app - Client Type:
public(前端、移动端)或confidential(后端服务,需要密钥) - Valid Redirect URIs:登录成功后跳转的地址,必须精确配置,否则会报错
3.3 User(用户) #
是什么:可以登录的账号。在 Keycloak 管理界面创建,设置用户名、密码、邮箱等。
3.4 Token(令牌) #
是什么:登录成功后,Keycloak 颁发的一串字符串(JWT 格式)。你的应用拿到 Token 后,每次请求 API 时带上它,服务端就能知道"是谁在访问"。
4. 环境准备 #
4.1 安装 Python 依赖 #
本次使用 requests 和 python-keycloak 两个库。requests 用于发 HTTP 请求,python-keycloak 是 Keycloak 的 Python 客户端,可简化操作。
创建一个虚拟环境并安装依赖(推荐):
# 创建虚拟环境(可选,但推荐)
python -m venv venv
# 激活虚拟环境(Windows PowerShell)
venv\Scripts\activate
# 安装依赖
pip install requests python-keycloak4.2 用 Docker 启动 Keycloak #
如果你没有 Docker,可到 Keycloak 官网 下载并运行。本以 Docker 为例。
# 启动 Keycloak 容器,映射到本机 8080 端口
# Windows PowerShell 中可复制整段运行
docker run -d --name keycloak -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:latest start-dev说明:
start-dev是开发模式,方便本地学习。生产环境请用start并配置数据库等。
启动后访问:http://localhost:8080/admin,用 admin / admin 登录管理控制台。
5. 在 Keycloak 中完成基础配置 #
在写代码之前,需要先在 Keycloak 管理界面完成以下配置。请按顺序操作。
5.1 创建 Realm(可选,也可用 master) #
- 登录管理控制台
- 左上角下拉选择
master,点击 "Create realm" - Realm name 填
myrealm,点击 Create - 创建后会自动进入该 Realm
如果暂时不想创建,可直接用 master Realm,后续代码中的 myrealm 改为 master 即可。
5.2 创建 Client(客户端) #
- 左侧菜单点击 "Clients" → "Create client"
- Client ID:
my-python-app(必填,唯一) - 点击 Next
- Client authentication:选择 "ON"(即 confidential,需要密钥)
- Authorization:保持 OFF
- 认证流程选择标准流程和直接访问授权
- 点击 Save
- 在客户端详情页:
- Valid redirect URIs:填
http://localhost:8000/*(本地回调地址,可按需改端口) - Web origins:填
http://localhost:8000或*(开发时可填*)
- Valid redirect URIs:填
- 点击 Save
- 切换到 "Credentials" 标签,复制 Secret,后面代码会用到
5.3 创建用户 #
- 左侧菜单 "Users" → "Add user"
- Username:
testuser - Email:可填 `test@example.com`(可选)
- 点击 Create
- 进入用户详情,点击 "Credentials" 标签
- 设置密码,如
test123,关闭 "Temporary"(否则首次登录会强制改密码) - 点击 Set password
6. 获取 Token(密码模式) #
场景:你的 Python 脚本代表一个用户,用用户名和密码直接向 Keycloak 换取 Token。适合后端服务、脚本、命令行工具。
注意:前端网页不应使用密码模式(会暴露密码),应使用授权码模式。
6.1 可运行代码 #
将下面的代码保存为 get_token.py,修改其中的 KEYCLOAK_URL、REALM、CLIENT_ID、CLIENT_SECRET、USERNAME、PASSWORD 后即可运行。
# 导入 requests 库,用于发送 HTTP 请求
import requests
# Keycloak 服务器地址(不要加末尾斜杠)
KEYCLOAK_URL = "http://localhost:8080"
# Realm 名称(你创建的领域,如 myrealm 或 master)
REALM = "master"
# 客户端 ID(在 Keycloak Clients 中创建的)
CLIENT_ID = "mcp-server"
# 客户端密钥(在 Client 的 Credentials 标签页获取)
CLIENT_SECRET = "OXg1z6OunGbAVoeZXbqaf5JeRLBVcptg"
# 要登录的用户名
USERNAME = "testuser"
# 要登录的用户密码
PASSWORD = "test123"
# 构建 Token 接口的完整 URL,格式为 {KEYCLOAK_URL}/realms/{REALM}/protocol/openid-connect/token
token_url = f"{KEYCLOAK_URL}/realms/{REALM}/protocol/openid-connect/token"
# 定义 POST 请求的数据,采用 OAuth2 密码模式
data = {
# 指定授权类型为密码模式
"grant_type": "password",
# 客户端标识
"client_id": CLIENT_ID,
# 客户端密钥(confidential 类型必须)
"client_secret": CLIENT_SECRET,
# 用户名
"username": USERNAME,
# 密码
"password": PASSWORD,
}
# 向 token_url 发送 POST 请求,提交 data,默认 Content-Type 为 application/x-www-form-urlencoded
response = requests.post(token_url, data=data)
# 判断请求是否成功(状态码为 200 时成功)
if response.status_code == 200:
# 解析返回结果为 JSON 格式,并保存到 token_data 字典
token_data = response.json()
# 输出 access_token,用于访问受保护资源
print("Access Token:", token_data.get("access_token"))
# 输出 refresh_token,用于在 access_token 过期后刷新
print("Refresh Token:", token_data.get("refresh_token"))
# 输出 access_token 的过期时间,单位为秒
print("过期时间(秒):", token_data.get("expires_in"))
else:
# 如果获取 Token 失败,则输出错误状态码
print("获取 Token 失败:", response.status_code)
# 输出服务器返回的错误信息
print(response.text)6.2 运行与预期输出 #
python get_token.py成功时类似输出:
Access Token: eyJhbGciOiJSUzI1NiIsInR5c
Refresh Token: eyJhbGciOiJIUzUxMiIsInR5
过期时间(秒): 606.3 常见错误排查 #
| 错误信息 | 可能原因 | 解决方法 |
|---|---|---|
401 Unauthorized |
用户名/密码错误,或 Client Secret 错误 | 检查 Keycloak 中的用户和客户端配置 |
Invalid redirect uri |
未配置或配置错误 | 在 Client 的 Valid redirect URIs 中添加正确地址 |
Connection refused |
Keycloak 未启动 | 确认 Docker 容器在运行:docker ps |
7.用 Token 获取用户信息 #
场景:已经拿到 access_token,想获取当前登录用户的基本信息(用户名、邮箱等)。Keycloak 提供了 /userinfo 接口。
7.1 代码 #
保存为 get_userinfo.py。可先运行示例一获取 Token,把输出的 Token 填到下面;或直接复用示例一的逻辑,在同一个脚本里先取 Token 再调 userinfo。
# 导入 requests 库,用于发送 HTTP 请求
import requests
# ========== 配置区域 ==========
# Keycloak 服务器地址,不要带斜杠
KEYCLOAK_URL = "http://localhost:8080"
# Realm 名称
REALM = "master"
# 可手动填写 access_token;如留空则自动获取
ACCESS_TOKEN = ""
# 如果 ACCESS_TOKEN 为空,则使用下面的客户端信息和用户信息自动获取
CLIENT_ID = "mcp-server"
CLIENT_SECRET = "OXg1z6OunGbAVoeZXbqaf5JeRLBVcptg"
USERNAME = "admin"
PASSWORD = "admin"
# ========== 若未提供 Token,则先获取 ==========
# 如果 ACCESS_TOKEN 为空,调用 Keycloak 获取 Token
if not ACCESS_TOKEN:
# 构造获取 token 的 URL
token_url = f"{KEYCLOAK_URL}/realms/{REALM}/protocol/openid-connect/token"
# 发送 POST 请求获取 Token,需要提供用户名、密码、client_id 等信息
token_resp = requests.post(token_url, data={
"grant_type": "password",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"username": USERNAME,
"password": PASSWORD,
"scope": "openid", # userinfo 需要 openid scope
})
# 判断请求是否成功
if token_resp.status_code != 200:
# 获取失败打印错误信息并退出
print("获取 Token 失败:", token_resp.text)
exit(1)
# 获取成功则取出 access_token
ACCESS_TOKEN = token_resp.json().get("access_token")
# ========== 调用 userinfo 接口获取用户信息 ==========
# 构造 userinfo 接口的 URL
userinfo_url = f"{KEYCLOAK_URL}/realms/{REALM}/protocol/openid-connect/userinfo"
# 构造请求头,带 Authorization: Bearer {token}
headers = {
"Authorization": f"Bearer {ACCESS_TOKEN}"
}
# 发送 GET 请求获取用户信息
response = requests.get(userinfo_url, headers=headers)
# 判断接口请求是否成功
if response.status_code == 200:
# 解析用户信息响应为 JSON
user_info = response.json()
# 打印用户信息(标准 OIDC 字段)
print("用户信息:")
print(" 用户名:", user_info.get("preferred_username"))
print(" 邮箱:", user_info.get("email"))
print(" 姓名:", user_info.get("name"))
print(" 完整数据:", user_info)
else:
# 获取用户信息失败时打印状态码和错误内容
print("获取用户信息失败:", response.status_code)
print("响应内容:", response.text)7.2 运行与预期输出 #
python get_userinfo.py成功时输出类似:
用户信息:
用户名: testuser
邮箱: test@example.com
姓名:
完整数据: {'sub': 'xxx', 'preferred_username': 'testuser', ...}8. 使用 python-keycloak 管理用户 #
场景:用 Python 代码创建用户、查询用户等管理操作。需要管理员账号。适合做自动化运维或批量导入用户。
8.1 前置说明 #
python-keycloak 通过 Keycloak 的 Admin REST API 进行操作,需要管理员的账号密码或 Service Account。本示例使用管理员账号。
8.2 代码 #
保存为 admin_create_user.py。会创建一个新用户 newuser,密码 newpass123。
# 导入 KeycloakAdmin 类,用于操作 Keycloak 管理接口
from keycloak import KeycloakAdmin
# 设置 Keycloak 服务器地址
KEYCLOAK_URL = "http://localhost:8080"
# 设置管理员用户名
ADMIN_USERNAME = "admin"
# 设置管理员密码
ADMIN_PASSWORD = "admin"
# 设置需要操作的 Realm 名称
REALM = "master"
# 创建 KeycloakAdmin 实例,连接到 Keycloak 管理端口
keycloak_admin = KeycloakAdmin(
# 指定 Keycloak 的服务器 URL
server_url=KEYCLOAK_URL,
# 指定管理员账号
username=ADMIN_USERNAME,
# 指定管理员密码
password=ADMIN_PASSWORD,
# 管理员初始连接 master 域
realm_name="master",
# 设置是否校验证书(生产环境为 True,测试可为 False)
verify=True,
)
# 切换 KeycloakAdmin 实例的 realm_name 属性到目标 Realm
keycloak_admin.realm_name = REALM
# 构造要创建的新用户基本信息
new_user = {
# 指定新用户的用户名
"username": "newuser2",
# 指定新用户的邮箱
"email": "newuser2@example.com",
# 指定新用户的名
"firstName": "New",
# 指定新用户的姓
"lastName": "User",
# 指定用户是否启用
"enabled": True,
}
# 调用 create_user 方法创建新用户,返回用户的唯一标识 ID
user_id = keycloak_admin.create_user(new_user)
# 输出用户创建成功及其 ID
print(f"用户创建成功,ID: {user_id}")
# 设置新用户的初始密码,temporary=False 表示非临时密码
keycloak_admin.set_user_password(user_id, "newpass123", temporary=False)
# 输出密码设置完成信息
print("密码已设置为: newpass123")
# 获取刚刚创建的用户详细信息
user = keycloak_admin.get_user(user_id)
# 输出查询到的用户详情
print("用户详情:", user)8.3 运行与验证 #
python admin_create_user.py成功后会输出用户 ID 和详情。之后可用 testuser 换成 newuser,密码 newpass123,在示例一、二中测试登录。
9. 登录并调用"受保护 API" #
场景:模拟一个完整流程:用户登录 → 获取 Token → 用 Token 调用你的后端 API。你的 API 需要验证 Token 是否有效。
为简化演示,这里用 Keycloak 自带的 userinfo 接口模拟"受保护的 API"(实际项目中是你的业务接口)。
9.1 代码 #
# 导入 requests 库,用于发送 http 请求
import requests
# ========== 配置区 ==========
# 设置 Keycloak 服务器地址
KEYCLOAK_URL = "http://localhost:8080"
# 设置使用的 Realm 名称
REALM = "master"
# 设置 client_id(在 Keycloak 的 Clients 配置中找到)
CLIENT_ID = "mcp-server"
# 设置 client_secret(在 Clients 的 Credentials 标签页获取)
CLIENT_SECRET = "OXg1z6OunGbAVoeZXbqaf5JeRLBVcptg"
# 设置登录用户名
USERNAME = "admin"
# 设置登录用户密码
PASSWORD = "admin"
# ========== 步骤 1:获取 Token ==========
# 构造获取 token 的接口 URL
token_url = f"{KEYCLOAK_URL}/realms/{REALM}/protocol/openid-connect/token"
# 以密码模式携带相关数据请求 token
token_resp = requests.post(token_url, data={
"grant_type": "password", # 授权类型为密码模式
"client_id": CLIENT_ID, # 客户端 ID
"client_secret": CLIENT_SECRET, # 客户端密钥
"username": USERNAME, # 用户名
"password": PASSWORD, # 密码
"scope": "openid", # 获取 userinfo 需要 openid scope
})
# 判断 token 获取是否成功
if token_resp.status_code != 200:
# 获取失败输出错误信息并退出
print("登录失败:", token_resp.text)
exit(1)
# 提取返回的 token 数据
token_data = token_resp.json()
# 获取 access_token
access_token = token_data["access_token"]
# 输出获取成功提示
print("步骤 1 完成:已获取 Access Token")
# ========== 步骤 2:用 Token 调用受保护接口 ==========
# 以 userinfo 接口为示例,实际可替换你自己的 API 地址
api_url = f"{KEYCLOAK_URL}/realms/{REALM}/protocol/openid-connect/userinfo"
# 构造请求头,带上 Bearer token
headers = {"Authorization": f"Bearer {access_token}"}
# 调用 userinfo 接口获取用户信息
api_resp = requests.get(api_url, headers=headers)
# 判断接口调用是否成功
if api_resp.status_code != 200:
# 失败则输出错误信息并退出
print("调用 API 失败:", api_resp.text)
exit(1)
# 获取用户信息的 JSON 数据
user_info = api_resp.json()
# 输出用户信息获取成功提示
print("步骤 2 完成:已获取用户信息")
# 打印当前用户的用户名
print("当前用户:", user_info.get("preferred_username"))
# 流程成功结束提示
print("完整流程演示成功!")9.2 你的 API 如何验证 Token? #
在实际项目中,你的后端 API(如 Flask、FastAPI)需要验证客户端传来的 Authorization: Bearer <token>。两种常见方式:
- 调用 Keycloak 的 Introspection 接口:把 Token 发给 Keycloak,让它告诉你是否有效。
- 本地验证 JWT:Keycloak 提供公钥,你的服务用公钥验证 JWT 签名,无需每次请求 Keycloak。
10. 常见问题与排查 #
10.1 获取 Token 时报 401 #
- 检查用户名、密码是否正确
- 检查 Client Secret 是否复制正确(注意没有多余空格)
- 确认用户已启用(Users → 用户 → Details → Enabled 为 ON)
10.2 获取 Token 时报 "Invalid client" #
- 确认 Client ID 拼写正确
- 若 Client 为 confidential,必须传
client_secret - 确认 Client 的 "Client authentication" 已开启(需要 Secret 时)
10.3 Redirect URI 相关错误 #
- 在 Client 的 "Valid redirect URIs" 中添加你实际使用的回调地址
- 开发环境可用
http://localhost:端口/*或http://127.0.0.1:端口/*
10.4 连接被拒绝 (Connection refused) #
- 确认 Keycloak 已启动:
docker ps查看容器 - 确认端口正确:默认 8080,检查是否被占用