文章目录
提到OAuth2
,大家多少都有些了解。
不了解的话可以先看下之前的简单聊聊鉴权背后的那些技术先回顾一下基本概念和流程。
简单来说,以google
授权为例,一般就是通过用户授权页面登录google
账号,再跳转用code
换取到相应权限的token
,就可以代表用户去发起一些google api
的请求。
直接代码实现这套授权逻辑并不复杂,不过如果还需要接入facebook
授权,instagram
授权呢,总不能挨个去实现一遍吧。
最好能有一套通用的解决方案来解放双手, 今天我们就聊聊如何用keycloak
实现一套通用的身份验证和授权管理方案。
提前说明,无法本地复刻的技术方案不利于理解,也不利于方案探讨。虽然本文章所用代码是使用了
rust
的axum
框架(为啥?因为rust
is future!)+keycloak
,但从服务启动到keycloak
服务及相关配置,都用docker-compose+terraform+shell
脚本化管理,可100%本地复刻,欢迎本地尝试。(当然我说的是Mac
下)代码地址: https://github.com/NewbMiao/axum-koans
初探OAuth
在引入keycloak
之前我们以google
为例先看下常规OAuth
怎么接入,方便后边和keycloak
接入对比。
前置工作: 获取
google OAuth application
的clientId
和clientSecret
,不清楚的话,可以参考 Create a Google Application in How to setup Sign in with Google using Keycloak
如下图,一般授权流程(standard flow
)中客户端和auth server
主要是两个阶段
- 生成
auth url
跳转登录后请求换取授权令牌的code
- 在
auth callback
中用code
换取token
,得到能代表用户的credentials
,一般是accessToken
这个流程自己也可以实现,但一般都用oidc client
(其实现了OpenID connect
协议,是建立在OAuth2.0
上的身份验证协议,用来为应用提供用户身份信息)来实现。
编程语言实现上大同小异,下边代码以rust
的oauth2
库为例讲解
如果不熟悉rust
,可以重点看代码注释,也不影响理解
初始化oidc client
1 | // src/extensions/google_auth.rs@GoogleAuth::new |
生成auth url
1 | // src/extensions/google_auth.rs@GoogleAuth::auth_url |
这里参数access_type=offline
对于应用需要长期accessToken
是很关键的。一般accessToken
都有过期时间,如果没有有效的refreshToken
来刷新accessToken
,就会有accessToken
失效后还要用户再登录的尴尬局面-_-!
另外为安全考虑除了可以用state
做请求合法校验,还可以用PKCE(Proof Key for Code Exchange)
来加强, 实际用到的代码有实现,感兴趣可以看下
auth callback换取token
1 | // src/extensions/google_auth.rs@GoogleAuth::get_tokens |
这部分不复杂,按文档配好本地,可以访问http://localhost:8000/google/auth
来尝试上述flow
使用keycloak IDP
keycloak 配置
上边流程怎么让keycloak这个身份和访问管理系统接管呢,答案是使用keycloak IDP
(Identity provider
)
我们先看下需要如何配置相应配置,这里先用terraform - keycloak provider
展示下配置。
等效的页面配置可以后边参考之前的链接 How to setup Sign in with Google using Keycloak
1 | # 这里使用默认的admin-cli配置keycloak |
别看代码版的配置稍微有点多,主要配置其实就只有注释里的三处,然后google OAuth的代理设置就完成了,不信我们继续往下看怎么代码接入
keycloak auth接入
上边keycloak
配置了realm
,后边授权和token
获取都会和这个realm
下的issueUrl
打交道,这里issueUrl
就类似google
的auth server
地址。
- 初始化
keycloak oidc client
1 | // src/extensions/keycloak_auth.rs@KeycloakAuth::new |
- 生成
auth_url
方法基本和之前google
配置一模一样。
这里也能看出为啥需要oidc
协议,其实就是抽象化,提供了一种安全、标准化和可扩展的身份验证和授权协议。它简化了应用程序中的身份管理和访问控制,提供了一致的用户登录体验,并提高了应用程序的安全性。
这里auth url
默认跳转的是keycloak
登录页面,然后google idp
是作为一种登录选项让用户选择。但如果就打算让用户直接google
登录,可以跳过keycloak
登录页。
方法是使用客户端建议的idp(kc_idp_hint)
:Client-suggested Identity Provider
这样就可以直接使用指定的idp
进行授权登录
代码如下
1 | // src/extensions/keycloak_auth.rs@KeycloakAuth::auth_url |
auth callback
换取token
方法也同 google auth callback
, 这里不赘述了。
不过这里拿到的是keycloak
的token
。要是需要google
的token
怎么办?
别急,有两种办法。
方法一: token-exchange
token-exchange
是用于token
交换场景,我们这里是用keycloak token
换取外部google token
(external token
)
相应keycloak
配置
token-exchange
目前还是keycloak
预览(preview
)功能,需要至少在features
中启用admin-fine-grained-authz,token-exchange
才可使用(详见keycloak docker-composer
配置 )
1 | // 启用idp获取refresh token |
代码实现
1 | let token_url = |
这样就获取到了可用的google access token
, 实际上内部是通过google refresh token
换取到的。
这样常规请求没问题了,只要你有keycloak access token
, 就能换取到google access token
来请求google api
。so easy?!
方法二:broker 读取 stored token
然而,要是需要google refresh token
怎么办?
有些场景是客户端需要自己通过google refresh token
换取access token
来发起请求的,难道这个时候客户端先去拿个keycloak access token
么。。。?
这就可以用Retrieving external IDP tokens
底层实现是授权时存储了external token
,再配合添加broker read token
权限给生成的用户,就可以用keycloak access token
换取存储的external access token + refresh token
.
相应keycloak配置
1 | resource "keycloak_oidc_google_identity_provider" "google" { |
题外话:这里
add_read_token_role_on_create
对应的配置在21.1.1版keycloak admin
页面没有,但admin api
确可以设置,也是很tricky
代码实现
就是直接换取refresh_token
, 请求地址指明对应的idp
即可
1 | // src/extensions/keycloak_auth.rs@KeycloakAuth::get_idp_token |
题外话:当然直接给用户这么获取
refresh token
的能力并不安全,还需要考虑对broker read token
接口的访问约束等来更好的保证安全token
换取。
上边keycloak
授权方案可以本地配好环境后,用http://localhost:8000/keycloak/login 来尝试。
好了,keycloak
如何管理external auth
到这里就结束了。以上是我在使用keycloak
的一些摸索和思考,欢迎大家一起探讨。
再次附上本文的代码地址以供验证:https://github.com/NewbMiao/axum-koans
如有疑问,请文末留言交流或邮件:newbvirgil@gmail.com 本文链接 : https://newbmiao.github.io/2023/06/18/auth-manage-via-keycloak.html