第 4 周实验指南:授权码管理
1. 学习目标
本周,我们专注于 OAuth 2.0 的基石——授权码流程。我们将实现创建和验证短期有效的授权码的逻辑,并添加一项至关重要的安全功能:代码交换证明密钥(PKCE)。完成本次实验后,你将能够:
- 理解授权码在 OAuth 2.0 流程中的作用。
- 实现授权码的创建和一次性使用。
- 理解授权码被截获的威胁。
2. 理论背景
2.1 授权码流程
该流程被认为是 Web 和移动应用最安全的流程,分两步进行:
- 授权请求:用户从客户端应用重定向到 SSO 服务器的授权端点。用户登录后(或如果他们已有 TGT 会话),服务器会生成一个临时的、一次性的授权码,并通过预先注册的重定向 URI 将其发回给客户端。
- 令牌请求:然后,客户端应用的后端安全地将此授权码(连同其客户端密钥)发送到 SSO 服务器的令牌端点。服务器验证该码,如果有效,则返回最终的访问令牌和刷新令牌。
这个两步过程是安全的,因为宝贵的访问令牌从未暴露在用户的浏览器(前端通道)中。
2.2 PKCE (代码交换证明密钥)
如果用户移动设备上的恶意应用在授权码返回给合法应用时截获了它怎么办?攻击者随后可以用它来交换访问令牌。PKCE(读作“pixy”)可以防止这种情况。
- 客户端创建密钥:在开始流程之前,客户端应用会生成一个名为
code_verifier
的随机字符串。 - 客户端创建挑战:然后,它对此验证器进行哈希处理(通常使用 SHA-256)以创建一个
code_challenge
。 - 带挑战的授权请求:客户端在初始授权请求中将
code_challenge
和哈希方法(S256
)发送到 SSO 服务器。服务器将这些与授权码一起存储。 - 带验证器的令牌请求:当客户端用授权码交换令牌时,它也包含了原始的、明文的
code_verifier
。 - 服务器验证:服务器使用存储的方法对提供的
code_verifier
进行哈希,并检查它是否与存储的code_challenge
匹配。如果匹配,服务器就知道请求来自原始客户端。只截获了授权码的攻击者将不会有原始的验证器。
3. 实验任务
导航到 smart-sso-starter-server
模块中的 LocalCodeManager.java
文件。你将在这里实现管理授权码的逻辑。
任务 1:初始化缓存
在构造函数中,初始化一个名为 codeCache
的 Guava Cache
。
-
键将是授权码
String
。 -
值将是
CodeContent
对象,其中包含用户 ID 和 PKCE 挑战等信息。 -
将
expireAfterWrite()
设置为timeout
值(例如 5 分钟)。授权码的有效期应该非常短。
任务 2:实现核心方法
-
create(String code, CodeContent content)
方法:
- 很简单:只需将授权码及其内容添加到缓存中。codeCache.put(code, content);
-
getAndRemove(String code)
方法:
- 这必须是一个原子操作,以确保一个码只能使用一次。
- 首先,检索内容:CodeContent content = codeCache.getIfPresent(code);
- 如果内容存在,立即从缓存中使该码失效:codeCache.invalidate(code);
- 返回检索到的内容。
4. 测试与验证
- 运行
LocalCodeManagerTest.java
:打开此测试文件。它包括以下测试: - 授权码的创建、一次性使用和过期。 - 使用正确验证器成功进行 PKCE 验证。 - 使用不正确验证器失败的 PKCE 验证。 - 正确处理不使用 PKCE 的请求。 - 调试与修复:运行测试并使用调试器逐步执行你的哈希和比较逻辑。确保你的 Base64 编码是正确的(URL 安全,无填充)。修复你的实现,直到所有测试都通过。