Client authentication(客户端授权)这是 Keycloak(和整个 OIDC/OAuth2 体系)里一个非常核心 的开关。它决定的是:这个客户端有没有能力安全地保存一个"密钥"(client_secret)。
翻译成人话就是------"你的应用是能被信任保管密码的后端,还是藏不住任何秘密的前端?"
一、先理解 OIDC 的两种客户端类型
OAuth2/OIDC 把所有客户端分成两类:
| 类型 | 英文 | 能否安全存密钥 | 典型场景 |
|---|---|---|---|
| 机密客户端 | Confidential | ✅ 能 | 后端服务、SSR 应用、有服务端代码的应用 |
| 公开客户端 | Public | ❌ 不能 | 纯前端 SPA、移动 App、桌面 App |
区别的唯一标准就是:这个应用的代码和运行环境,能不能藏住一个 secret?
二、Client authentication 打开 = 机密客户端
含义
打开后,Keycloak 要求这个客户端在换 token 时 必须提供 client_id + client_secret,用来证明"我就是这个客户端"。
交互流程
bash
1. 前端 → Keycloak:我要登录,client_id=vue-app
2. Keycloak → 用户:登录页
3. Keycloak → 前端:返回 code
4. 前端 → 后端:把 code 给后端
5. 后端 → Keycloak:拿 code + client_id + client_secret 换 token ← secret 在这一步
6. Keycloak:校验 secret ✅ → 返回 token
关键点
所以它不是 在授权流程里"返回"给你的,而是配置阶段就存在的,像一把"这个 client 的固定密码"。
-
client_secret绝对不能在浏览器里出现 -
所以换 token 这一步必须由后端来做,前端只负责拿 code、把 code 转交给后端
-
client_secret不是"流程中产生的",而是你在 Keycloak 里创建这个 client 时,Keycloak 就生成好的一个固定密钥,存在 Keycloak 服务端。 -
只有
Client authentication= 打开 的 client 才会有 -
在 Keycloak 控制台 → Clients → 你的 client → Credentials 标签里能看到
-
它就是一个长期不变的字符串(除非你手动重新生成)
适合谁
-
有后端的传统 Web 应用(Java/Spring、Node/Express 做服务端渲染)
-
前后端同源、由服务端处理登录的应用
-
任何有服务端、能保管密钥的应用
安全优势
攻击者即使拦截到 code,没有 secret 也换不到 token。secret 是第二道防线。
三、Client authentication 关闭 = 公开客户端
含义
关闭后,Keycloak 不要求 client_secret。换 token 时只带 client_id 就行(因为前端根本藏不住 secret)。
交互流程
bash
1. 前端 → Keycloak:我要登录,client_id=vue-app
2. Keycloak → 用户:登录页
3. Keycloak → 前端:返回 code
4. 前端 → Keycloak:拿 code + client_id 换 token ← 没有 secret!
5. Keycloak:不校验 secret → 返回 token
关键点
-
整个流程前端独立完成,不需要后端参与换 token
-
因为没有 secret 校验,必须靠 PKCE 来补安全(否则 code 被拦截就完蛋)
-
这就是为什么"公开客户端 + PKCE"是标配组合
适合谁
-
纯前端 SPA(Vue / React / Angular,就是您现在的场景)
-
移动 App(iOS / Android)
-
桌面应用(Electron 等)
安全短板
-
没有 secret,安全全靠 PKCE + redirect_uri 白名单
-
所以必须开 PKCE (
pkceMethod: 'S256')
四、PKCE
PKCE 的核心思路:在流程开始时生成一个"临时密码",流程结束时用它来证明"换 token 的人就是发起授权的人"。这样即使 code 被拦截,攻击者没有那个"临时密码"也换不到 token。
两个关键值
| 名称 | 含义 |
|---|---|
| code_verifier | 客户端生成的一串随机字符串(43~128 字符),自己偷偷留着 |
| code_challenge | 由 code_verifier 经过变换得到的值,发给授权服务器 |
变换方式有两种:
-
plain:challenge = verifier(不推荐) -
S256:challenge = BASE64URL(SHA256(verifier)) (推荐,keycloak-js默认用这个)
因为 SHA256 是单向 的,服务器拿到 challenge 推不出 verifier。
bash
【准备阶段】客户端本地
1. 生成随机 code_verifier
2. 计算 code_challenge = BASE64URL(SHA256(code_verifier))
(code_verifier 存在本地,不发给任何人)
【第 1 步】客户端 → 授权服务器
请求 /auth?...&code_challenge=xxx&code_challenge_method=S256
(带上 challenge,不带 verifier)
【第 2 步】授权服务器 → 用户
弹登录页,用户登录
【第 3 步】授权服务器 → 客户端
返回 code
(服务器把 code 和 challenge 关联存起来)
【第 4 步】客户端 → 授权服务器
拿 code + code_verifier 换 token
(这次带上原始 verifier)
【第 5 步】授权服务器校验
服务器对收到的 code_verifier 做 SHA256,得到结果
和之前存的 code_challenge 比对
✅ 一致 → 发 token
❌ 不一致 → 拒绝