Client authentication(客户端授权)开关与OAuth2 PKCE的深刻理解

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 白名单

  • 所以必须开 PKCEpkceMethod: '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
  ❌ 不一致 → 拒绝
相关推荐
福兮说1 小时前
在浏览器里写一个“够用就好“的 Excel 公式求值器
前端·javascript·excel·编译原理
志尊宝1 小时前
Vue3 零基础每日笔记(027):组件上的 v-model——让自定义组件也能双向绑定
前端·javascript·vue.js·笔记·html5
萧行之3 小时前
可视化技术复盘:从图形语法看 D3、Vega-Lite、ECharts、Observable Plot
前端·学习·数据可视化
尾善爱看海11 小时前
Vue 面试收官篇:SSR、性能优化落地、30 道高频面试题精讲(附标准答案)
前端·javascript·vue.js·面试·vue
驳是11 小时前
一个组件,提升 react-router 开发的幸福感
前端·preact
GreenTea12 小时前
OpenAI Agents API 上手实测:一次调用把整个 agent loop 甩给 OpenAI
前端·后端·算法
星云API技术支持12 小时前
企业微信二次开发:群权限设置、成员管理与群资料维护的接口组合实践
java·前端·企业微信
京东云开发者12 小时前
81.8 秒的视频,我们改了 15 个版本:一次纯 Codex 驱动的 AI-native 视频实践
前端·aigc
小程序设计12 小时前
某师范学院网络规划与设计
网络·安全