LabHub
学习 学习路径 课程

Keycloak 与企业认证

四种流程,以及各自的位置

在 LabHub 中继续学习

一句话总结

公开客户端有authorization code + PKCE,服务器间通信有client credentials。其余的大部分都有理由不使用。

流程图: 将收到的代币存放在哪里 · BFF(Backend For Frontend) · 无论选择哪一边,如果有XSS,大部分都会崩溃 · 访问令牌不会被取消

为什么需要这个?

自然会问:“为什么谷歌登录按钮需要这么复杂的协议?”答案是这样的:它试图在不向第三方应用程序提供用户密码的情况下,让该应用程序代表用户访问有限的资源。

为了这个目标,创造了许多流派,随着时间的推移,有几个被废弃了。

怎么行动

Authorization Code是基本和标准。用户被重定向到认证服务器登录,收到授权码后返回应用程序,应用程序的后端将该代码用作令牌。关键是代码暴露在浏览器URL上,但是一次性,不能单独使用。交换需要客户端秘密。

问题是SPA和移动应用程序。它们无法安全地存储秘密。如果放在源中,任何人都可以看到。所以以前使用了Implicit流——在没有代码的情况下直接从URL片段中接收令牌的方式——但令牌留在浏览器历史记录和浏览器缓存中存在严重问题,所以现在不推荐使用。

替代方案是PKCE(Proof Key for Code Exchange)。客户端每次请求都会随机生成字符串。code_verifier创建,将该SHA256哈希编码为Base64URL的code_challenge发送给授权请求。以后用代码兑换代币时,原始code_verifier提交。认证服务器会重新计算并匹配哈希值。即使攻击者窃取了授权码code_verifier没有的话就不能收到代币。即使没有秘密也能变得安全。

Client Credentials 是用户不介入的服务器间通信用。部署工作、服务账户、内部API调用。因为没有用户上下文sub是服务账户。

Resource Owner Password是应用程序直接接收用户的ID和密码,将其转换为令牌的过程。这是OAuth试图消除的模式,实际上已经被废弃了。除了传统迁移外,不使用。

在现场相遇的样子

按代币种类整理存储位置和寿命如下。

代币 用途 寿命 存储位置
Authorization Code 令牌交换用1次性 秒数 URL参数(立即失效)
ID Token 用户身份确认 5~60分钟 客户端
Access Token API访问权限 5~60分钟 内存或BFF
Refresh Token 访问令牌更新 长(可取消) httpOnly Cookie或服务器

把刷新令牌放在localStorage里是不对的理由很明确。XSS可以让JavaScript读取,刷新令牌是长期有效,如果被泄露的话,会允许持续访问。

state参数也不能遗漏。如果没有这个,攻击者可以通过受害者的回电来绕过自己账户发行的授权码,从而登录到攻击者的账户。

在浏览器应用程序中把令牌放在哪里?

即使正确选择流程,在将收到的代币存放在哪里上又会分歧。浏览器上可以选择的位子只有三个,各自对什么弱点不同。

位置 弱点 备注
localStorage 通过XSS直接被读取 即使刷新也会留下,虽然方便,但是最危险的。
JavaScript内存 XSS 仍然存在暴露,刷新后消失 携带访问令牌短时间内没有问题
httpOnlyCookie 虽然无法读取JavaScript,但需要单独阻止CSRF SameSite和认证一起需要

这里出来的实际答案是BFF(Backend For Frontend)。完全不给浏览器代币,而是让服务器持有,只给浏览器提供会话cookie。服务器接收浏览器的请求,然后附上代币并传递回去的结构。而不是一次性解决前面三个数字的问题,而是增加了服务器一层。

无论选择哪一边,如果有XSS,大部分都会崩溃这一点必须明确。因为即使脚本运行后无法直接读取令牌,也可以在该页面上代替发送请求。因此,与考虑令牌存储位置一样,也应该注意内容安全政策和输入处理。

注销也不是想象中那么简单。正如在前面的模块中看到的,访问令牌不会被取消,所以注销几乎相当于无效化刷新令牌并丢弃客户端持有的令牌。如果多个应用程序共享相同的登录会话,那么在某个地方注销时,其余的内容也应该被整理掉,为此需要有单独的条款,并且每个应用程序都应该实现它。不是“按下注销按钮就结束了”,而是要在设计中确定能整理到什么程度。

在下次确认中看到的东西

这个模块只涉及概念。从下一个模块开始,从实际的Keycloak中接收和解码令牌,然后用脚本完成PKCE流程。