在 OIDC 的登录过程中,我们一般采用授权码模式的方式获取 Access Token,其中最重要的一步是验证之后通过 Authorization code 换取 Access Token,那么我们在整个登录过程中是如何保证 Authorization code 不被窃取的呢?
OIDC 中的 Client 类型
Auth 2.0 中将 client 分为两类:
Confidential Clients 机密型应用:能够安全的存储凭证(client_secret),例如后端服务,可以理解为机密性应用,因为你的后端能够安全的保存 client_secret,而不会将其直接暴露给用户,此时你可以使用授权码模式。
Public Clients 公共客户端:无法安全存储凭证(client secrets),例如Windows 应用程序、移动端,或者完全前后端分离的应用,应当使用 Authorization code + PKCE 模式。
授权码模式
Authorization Code Grand
在OIDC中,我们通常使用授权码模式 Authorization code grand,其流程如上图:
client 向 user agent 发起验证请求;
user agent 向 authorization server 发起验证请求;
验证成功后 authorization server 把 authorization code 返回给 user agent;
user agent 将 authorization code 返回给 client;
client 将 authorization code 发送给 authorization server;
authorization server 根据 redirect url 将 access token 返回给 client。
在上述步骤中,user agent 与 authorization server之间和 client 与 authorization server 之间的通信都受 TLS 的保护,所以我们认为这些步骤都是安全的。
Authorization code 在第(4)步中有可能被截获。
Confidential Clients 如何利用
client_secret 保证安全
confidential client 在第(5)步把 authorization code 发送给authorization server的时候把client_secret放在request header 里, authorization server 验证 request header 里的 client_secret 就可以信任这个发送请求的 client 了。
Public Clients 的中间人攻击
OAuth 2.0的public clients容易受到 Authorization code 拦截攻击的影响。
Authorization server 在步骤(3)中返回 authorization code。在步骤(4)中,authorization code 通过步骤(1)中提供的 redirect url 返回给请求者。
恶意应用程序有可能注册自身作为自定义方案的处理程序,除了合法的 OAuth 2.0 应用程序。一旦注册成功,恶意应用程序就能在步骤(4)中拦截授权码。这使得攻击者可以分别在步骤(5)和(6)中请求并获取 access token。
PKCE 如何保障 Public Clients
避免中间人攻击
PKCE: Proof Key for Code Exchange
为了减轻这种攻击,PKCE 利用一个动态创建的加密随机密钥,称为 code verifier。每个授权请求都会创建一个唯一的代码验证器,并将其转换后的值,称为 code challenge,发送到授权服务器以获 authorization code。获取的 authorization code 随后与 code verifier 一起发送到 token endpoint,服务器会将其与先前接收的请求代码进行比较,以验证客户端对 code verifier 的拥有权。这种方法有效地减轻了攻击风险,因为攻击者无法获知这个一次性密钥,它通过 TLS 传输,无法被拦截。
A. client 创建并记录一个 code_verifier,并且用一个转换方法 t_m(transformation_method) 将这个 code_verifier生成一个 t(code_verifier)(称为code_challenge),将其与转换方法 (transformation method)t_m 一起发送到 OAuth 2.0授权请求中。
B. authorization endpoint 像往常一样响应,但记录下 code_challenge 和转换方法 t_m(transformation method)。
C.client 在请求 access token 时,如往常一样发送 authorization code,但包括在步骤 A 生成的 code_verifier。
D. authorization server 用之前记录的 t_m 和收到的 code_verifier 生成 t_m(code_verifier),并与步骤 B 中记录的 code_challenge 进行比较。如果它们不一致,则拒绝访问。
攻击者如果在步骤 B 拦截了 authorization code,则无法使用它兑换 access token,因为他们没法获取 code_verifier。
上述步骤 B 中 authorization endpoint 也可以不用把 code_challenge 和 t_m(transformation method) 记录下来,而是如下图所示通过加加密之后放在 respone 中由 client 再传回来,这也是 authorization server 通常的做法。
参考文献:
https://openid.net/specs/openid-connect-core-1_0.html
https://datatracker.ietf.org/doc/html/rfc6749#page-4
https://datatracker.ietf.org/doc/html/rfc6750
https://datatracker.ietf.org/doc/html/rfc7636