Keycloak - docker 运行 & 前端集成
这里的记录主要是跟我们的项目相关的一些本地运行/测试,云端用的 keycloak 版本不一样,不过本地我能找到的最简单的配置是这样的
docker 配置 & 运行 keycloak
keycloak 有官方(Red Hat Inc.)的镜像,官方文档里也提供了一些配置好的 Dockerfile,具体可以参考 https://www.keycloak.org/server/containers,最新的版本已经支持到了 v23。不过我这里用的是 JBoss 提供的镜像,上一次更新是两年前的事情,目前的版本是 v16.1.0,稍微有一点旧,优点在于不需要额外的配置和修改,只针对前端部分的测试会方便一些。
docker-compose.yaml 文件如下:
version:'3.8'services:keycloak:container_name: keycloak
image: jboss/keycloak:16.1.0
# red hat 的官方镜像# image: quay.io/keycloak/keycloak:21.1.1# 使用 red hat 启动指令# command: start-devvolumes:- ./realm-config:/opt/jboss/keycloak/realm-config
- ./keycloak-db:/opt/jboss/keycloak/standalone/data
environment:- KEYCLOAK_USER=admin
- KEYCLOAK_PASSWORD=pass
- DB_VENDOR=h2
ports:- 9090:8080
运行指令为
docker compose up
,如下:
这个时候一个名为 keycloak 的 container 就启动了,这个时候访问 http://localhost:9090/auth/ 会看到以下界面:
admin 的用户名和密码可以直接在 docker 的环境配置里查看:
这里的配置是跟着 compose 文件来的,用户名是
admin
,密码就是
pass
docker 文件简单解析
JBoss 已经接手了不少配置,所以 docker compose 文件比较简单,这里简单过一遍,不感兴趣的可以跳过到下一个 section,直接到前端集成部分。
version
docker compose 文件的版本,目前标准配置要么是3
要么是3.8
services
docker 要启动的 services/containers,这里只有一个keycloak
-keycloak
这里启动的服务-container_name
容器的名称-image
这里的镜像名称是 JBoss 提供的 keycloak,在 docker 的镜像官网能找到,我这里顺带 tag 了一下版本-tag
上面指定了版本,所以这个可以忽略如果 images 里面没有指定版本,tags 也不存在,那么就会自动拉latest
版本-volumes
这里相当于做一个数据的持久化,这样只要保存了对应的文件/文件夹,别的项目直接拉取,开启 docker 同样能够获取对应的 realms 和 client 信息,运行docker compose up
后会自动创建对应的文件夹运行指令前,只有一个 compose 文件:运行后自动生成对应的 realms 和 db:-environment
docker 的一些环境变量,上文提到了,用户名密码就是在这里设置的-ports
这里是 docker 的 port mapping。默认 containers 的端口都是在 8080 上运行的,不过外部访问需要用其他的端口,可以理解成<out_port>:<container_port>
这也是为什么通过http://localhost:9090/
可以访问 keycloak
keycloak 极简配置
这里会生成一个 minimal 配置,大体就是需要创建 realm 和对应的 client,刚开始新登录的 realm 是空白的,只会有一个 master:
创建完的配置如下:
这里需要特别注意两个点:
web-origin
,这个是 CORS 的设置,只有这个设置为*
才能从其他的端口定向到 keycloak 所在的 portvalid redirect uri
,这个需要设置前端部分的 port,如果没有设置的话会在登录页面抛出invalid redirect uri
server 的信息,即 installation 如下:
{"realm":"OIDC-client","auth-server-url":"http://localhost:9090/auth/","ssl-required":"external","resource":"vanilla","public-client":true,"confidential-port":0}
keycloak 配置查看
⚠️ 这是 keycloak 本地测试用了 生产环境 的版本,也就是 v21 以后的添加,主要是 auth url 部分进行了更新,具体配置可以在
http://localhost:<out-port>/realms/<realm-name>/.well-known/openid-configuration
进行查看,大概的 JSON 文件如下:
在前端实现时,
auth url
部分需要进行更新,对标
issuer
前端集成
整个 OIDC client 的集成是一个很大的部分,我们也是在已经实现的 package 上新建了一个 wrapper,底层的实现使用的是
oidc-client-ts
这个 package,具体查看地址在 https://authts.github.io/oidc-client-ts/
keycloak 本身支持 Oauth 2.0, OpenID Connect(OIDC)和 SAML,所以这里 keycloak 和
oidc-client-ts
的集成,一旦看懂这个 package 的代码,实现起来还是比较方便的。
这里依旧用 React 的代码实现,不过版本是 v17。React v18 会更新状态两次,从而导致 token 获取不一致无法顺利实现功能。
配置
配置这方面还是需要参考
Interface UserManagerSettings
,网址:https://authts.github.io/oidc-client-ts/interfaces/UserManagerSettings.html
这里同样实现最低可需要的部分:
const config ={authority:'http://localhost:9090/auth/realms/OIDC-client/',client_id:'vanilla',redirect_uri:'http://localhost:4200',silent_redirect_uri:'http://localhost:4200',post_logout_redirect_uri:'http://localhost:4200',};
authority
是写死的值,这是 OIDC 接收的验证网址,即需要实现重定向的网址这部分查看上文更新内容,注意一下 keycloak 的版本与 issuer 之间的变化client_id
就是 client
这部分参考 installation 即可
初始化
也就是实例化一个
UserManager
:
// 建议写在配置文件里const config ={authority:'http://localhost:9090/auth/realms/OIDC-client/',client_id:'vanilla',redirect_uri:'http://localhost:4200',silent_redirect_uri:'http://localhost:4200',post_logout_redirect_uri:'http://localhost:4200',};const userManager =newUserManager(config);
登陆
功能如下:
constlogin=async()=>{await userManager.signinRedirect();};
userManager
会根据配置的信息进行重定向,如:
query param 会携带所有必需的信息,同样,对应的登录信息也会在返回的时候加到 query param 里:
更新
constrefreshToken=async()=>{await userManager.signinSilent();};
这时候
userManager
就会背地调用对应的 token API,进行用户信息的更新,包括 access token,session time 之类的,这个在下面一个 section 会重新提到
这些根据对应的 idle 配置可以实现自动登出的功能
获取用户信息
这个部分相当于是最麻烦的,我写了一个 auth 函数去实现,如果没有用户信息的话就重定向到登录页面去:
constauth=async()=>{const urlData =urlparse(window.location.href,true);if(urlData?.query?.state){const storedState =await userManager.settings.stateStore?.get(
urlData.query.state
);if(storedState){try{await userManager.signinRedirectCallback();logUser();}catch(e){
console.log(e);}}else{awaitlogin();}}};
之前提到过了,在登录完成后 URL 会包含一些对应的 query param,如下:
http://localhost:4200/?state=994a9e5d79f843ad822740007389b392&session_state=6dc0fd00-3229-49c3-b500-2f839f16538b&code=2acd9512-e8b1-4e8d-9e2b-362fc39912f5.6dc0fd00-3229-49c3-b500-2f839f16538b.541c1dbc-f6ab-4838-b9c1-7be9f09f5f50
这里主要需要的是
code
,需要用这个值去调用 keycloak 的
token
API 去获取对应的 token 信息,这也就是
userManager.signinRedirectCallback()
的用处,调用如下:
获取用户信息
当
token
正确获取之后,就可以通过调用
userManager
封装好的功能去获取用户信息了,如下:
constlogUser=async()=>{const user =await userManager.getUser();
console.log(user);};
其他
这里还是简化了很多的实现,
UserManager
还是建议写成一个 singleton,这样可以在任意地方获取同一个
UserManager
的实例对象
这里是直接在
App.js
里面实现的功能,但是可以将实例化的 singleton 抽出来放到 Context 里面去,同样的 singleton 也可以在 axios instance 里面调用,实现自动注入 bearer token 到 header 里、自动更新 session 或是在 session 过期时自动登出等功能
版权归原作者 GoldenaArcher 所有, 如有侵权,请联系我们删除。