keycloak高可用实践

Keycloak是什么

Keycloak 是适用于 Web 应用程序和 RESTful Web 服务的单点登录解决方案。Keycloak 的目标是使安全性变得简单,以便应用程序开发人员可以轻松保护他们在组织中部署的应用程序和服务。开发人员通常必须自己编写的安全功能是开箱即用的,并且可以轻松地根据组织的个人需求进行定制。

Keycloak 提供可定制的用户界面,用于登录、注册、管理和帐户管理。您还可以使用 Keycloak 作为集成平台,将其连接到现有的 LDAP 和 Active Directory 服务器。您还可以将身份验证委托给 Facebook 和 Google 等第三方身份提供商。

Keycloak的特点

  • 浏览器应用程序的单点登录和单点退出。
  • OpenID 连接支持。
  • OAuth 2.0 支持。
  • SAML 支持。
  • 身份代理 - 使用外部 OpenID Connect 或 SAML 身份提供商进行身份验证。
  • 社交登录 - 启用 Google、GitHub、Facebook、Twitter 和其他社交网络登录。
  • 用户联合 - 同步 LDAP 和 Active Directory 服务器中的用户。
  • Kerberos 桥 - 自动对登录到 Kerberos 服务器的用户进行身份验证。
  • 用于集中管理用户、角色、角色映射、客户端和配置的管理控制台。
  • 帐户控制台允许用户集中管理他们的帐户。
  • 主题支持 - 自定义所有面向用户的页面以与您的应用程序和品牌集成。
  • 双因素身份验证 - 通过 Google Authenticator 或 FreeOTP 支持 TOTP/HOTP。
  • 登录流程 - 可选的用户自行注册、恢复密码、验证电子邮件、要求更新密码等。
  • 会话管理 - 管理员和用户自己可以查看和管理用户会话。
  • 令牌映射器 - 将用户属性、角色等按照您想要的方式映射到令牌和语句中。
  • CORS 支持 - 客户端适配器具有对 CORS 的内置支持。

Keycloak的优势

  1. 开源免费Keycloak是开源项目,用户可以自由使用、修改和扩展,无需支付任何费用。
  2. 功能丰富:支持多种认证协议和社交登录,提供灵活的身份管理和访问控制功能。
  3. 易于集成Keycloak可以与各种应用和企业环境无缝集成,包括Java应用、REST API、移动应用等。
  4. 可扩展性Keycloak支持自定义认证流程、用户属性和策略,满足各种复杂业务需求。
  5. 安全性Keycloak提供多层次的安全保护,包括加密、令牌管理、审计日志等,确保用户数据的安全和隐私。

Keycloak可以解决哪些问题

  1. 单点登录(SSO):用户只需一次登录即可访问多个应用,提高了用户体验和安全性。
  2. 身份管理:集中管理用户身份和权限,简化用户管理过程,降低管理成本。
  3. 访问控制:基于角色和策略的访问控制,确保只有授权用户可以访问特定资源。
  4. 社交登录:支持使用社交账号(如微信、微博等)登录应用,提高用户注册和登录的便捷性。

Keycloak工作原理

当用户尝试访问受保护的资源时,Keycloak通过以下步骤进行认证和授权:

  1. 认证请求:客户端向Keycloak发送认证请求,包含用户的身份信息。
  2. 身份验证Keycloak验证用户的身份信息,包括用户名和密码、社交账号等。
  3. 发放令牌:验证成功后,Keycloak发放访问令牌(Access Token)和刷新令牌(Refresh Token)给客户端。
  4. 访问资源:客户端使用访问令牌向受保护的资源发起请求,Keycloak验证令牌的有效性并授权访问。

Keycloak核心概念

组件/概念 描述
用户(Users) 能够登录到系统的实体。可以理解为账户
认证(Authentication) 识别和验证用户的过程。这个过程确保只有有效的用户可以访问系统资源。
授权(Authorization) 授予用户访问权限的过程。系统根据用户的角色和策略决定其可以访问哪些资源。
凭据(Credentials) Keycloak用于验证用户身份的数据片段。例如,密码、一次性密码、数字证书或指纹等。
角色(Roles)
用户角色映射(User Role Mapping) 定义角色与用户之间的映射关系。用户可以与零个或多个角色相关联。
复合角色(Composite Roles) 可以与其他角色相关联的角色。
组(Groups) 管理用户组的实体。可以为组定义属性。可以将角色映射到组。成为组成员的用户继承该组定义的属性和角色映射。
领域(Realms) 管理一组用户、凭据、角色和组的容器。用户属于某个领域并登录到该领域。领域之间是相互隔离的,只能管理和验证它们所控制的用户。

Keycloak架构

Keycloak的架构主要包括以下几个部分:

  1. 服务器Keycloak服务器负责处理认证请求、管理用户信息和授权策略等。
  2. 适配器:适配器用于将Keycloak集成到各种应用中,包括Java应用、REST API、移动应用等。
  3. 数据库:存储用户信息、认证令牌、审计日志等数据。
  4. 客户端:用户通过客户端(如浏览器、移动应用等)与Keycloak进行交互。

Keycloak高可用方案

Keycloak的高可用方案主要涉及到集群部署和自动发现机制。在集群部署中,解决的关键问题是多个Keycloak实例间的数据共享和Session同步。

  • 数据共享:Keycloak默认使用内嵌的H2数据库作为数据存储,为了实现数据共享,可以将H2替换成外置的共享数据库,如MySQL、PostgreSQL、Oracle或SQL Server等。这样,所有的Keycloak实例都可以访问同一个数据库,确保用户、角色等数据信息的一致性。

  • Session同步:为了让Keycloak多个实例之间的Session进行同步,需要对Keycloak的实例进行配置以实现自动发现。在实际应用中,最推荐使用JDBC_PING实现自动发现。JDBC_PING会把集群中节点信息存储到数据表里,集群内的每个节点通过查询这个数据表来发现其他节点,从而实现Session的同步。

Keycloak Session同步提供一下三种高可用方案:

  • Ping:Keycloak默认使用UDP协议的集群方案,仅在启用多播网络且应公开端口 55200 时才可用,例如同一主机中的裸机、虚拟机、docker 容器。

img

  • TCPPing:使用TCP协议,端口为7600。当多播不可用时,可以使用此功能,例如跨数据中心、跨主机容器部署。

img

  • JDBC_Ping:使用 TCP 协议,端口为 7600,与 TCPPING 类似,但不同之处在于,TCPPING 要求您配置所有实例的 IP 和端口,对于 JDBC_PING 您只需要配置当前实例的 IP 和端口,这是因为在 JDBC_PING 解决方案中,每个实例将自己的信息插入数据库,实例通过从数据库读取的 ping 数据发现对等点。

Keyclaok高可用部署

k8s集群部署keyclaok

环境信息

节点 IP docker版本
keyclaok01 192.168.234.133 20.10.9
keyclaok02 192.168.234.134 20.10.9
vip 192.168.234.252 /

使用k8s部署mariadb数据库

https://github.com/keycloak/keycloak-containers/blob/19.0.3/docker-compose-examples/keycloak-mariadb-jdbc-ping.yml

apiVersion: v1
kind: Service
metadata:
name: mariadb
labels:
app: mariadb
spec:
ports:
- port: 3306
targetPort: 3306
nodePort: 30006
selector:
app: mariadb
type: NodePort

---

apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mariadb
spec:
selector:
matchLabels:
app: mariadb
serviceName: "mariadb"
replicas: 1
template:
metadata:
labels:
app: mariadb
spec:
containers:
- name: mariadb
image: mariadb:10.3.10
env:
- name: MYSQL_ROOT_PASSWORD
value: "123"
ports:
- containerPort: 3306
volumeMounts:
- name: mariadb-data
mountPath: /var/lib/mysql
volumes:
- name: mariadb-data
emptyDir: {}

在mariadb中创建keyclaok数据库

create database keycloak character set utf8 collate utf8_unicode_ci;

官方部署链接:https://www.keycloak.org/getting-started/getting-started-kube

下载部署文件:https://raw.githubusercontent.com/keycloak/keycloak-quickstarts/release/24.0/kubernetes/keycloak.yaml

生成证书

$openssl genrsa -out ca.key 2048
$openssl req -x509 -new -nodes -key ca.key -subj "/CN=example.io" -days 3650 -out ca.crt
$vi req.conf
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
req_extensions = v3_req
distinguished_name = dn

[ dn ]
C = CN
L = XIAN
OU = CondingDemo
CN = KeyCloak

[ v3_req ]
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names

[ alt_names ]
DNS.1 = keycloak.example.io
DNS.2 = keycloak01.example.io
DNS.3 = keycloak02.example.io
IP.1 = 192.168.234.130
IP.2 = 192.168.234.131
$openssl genrsa -out server.key 2048
$openssl req -new -key server.key -out server.csr -config req.conf
$openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out server.crt -days 3650 \
-extensions v3_req -extfile req.conf -sha256

创建secret

kubectl create secret tls keycloak-tls --key server.key --cert server.crt

创建keyclaok.yaml

apiVersion: v1
kind: Service
metadata:
name: keycloak
labels:
app: keycloak
spec:
ports:
- name: http
port: 8080
targetPort: 8080
selector:
app: keycloak
type: ClusterIP
clusterIP: None # keycloak 分布式缓存需要依赖 headless service
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: keycloak
labels:
app: keycloak
spec:
replicas: 1
selector:
matchLabels:
app: keycloak
template:
metadata:
labels:
app: keycloak
spec:
containers:
- name: keycloak
image: quay.io/keycloak/keycloak:20.0.3
args: ["start"]
env:
- name: KEYCLOAK_ADMIN
value: "admin"
- name: KEYCLOAK_ADMIN_PASSWORD
value: "admin"
- name: KC_DB
value: "mariadb"
- name: KC_DB_URL
value: "jdbc:mariadb://192.168.234.130:30006/keycloak?characterEncoding=UTF-8"
- name: KC_DB_USERNAME
value: "root"
- name: KC_DB_PASSWORD
value: "123"
- name: KC_PROXY
value: "edge"
- name: KC_HTTP_ENABLED
value: "true"
- name: KC_HOSTNAME_URL
value: "https://keycloak.example.io"
- name: KC_HOSTNAME_ADMIN_URL
value: "https://keycloak.example.io"
- name: KC_CACHE_STACK
value: "kubernetes"
- name: JAVA_OPTS_APPEND
value: "-Djgroups.dns.query=keycloak.default.svc.cluster.local"
ports:
- name: http
containerPort: 8080
readinessProbe:
httpGet:
path: /realms/master
port: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: keycloak
annotations:
nginx.ingress.kubernetes.io/proxy-buffer-size: "8k"
spec:
ingressClassName: nginx
tls:
- hosts:
- keycloak.example.io
secretName: keycloak-tls
rules:
- host: keycloak.example.io
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: keycloak
port:
number: 8080

添加hosts解析

192.168.234.130 keycloak.example.io
192.168.234.131 keycloak.example.io

浏览器访问:keycloak.example.io

Docker部署keyclaok

节点信息

节点名称 IP地址 软件
keycloak01 192.168.234.133 keepalived、haproxy、mariadb、keycloak
keycloak02 192.168.234.134 keepalived、haproxy、mariadb、keycloak

keepalived

节点添加hosts解析,主要用来给keepalived脚本检查keycloak api是否正常

#keycloak01
192.168.234.133 keycloak.acaiblog.top
#keycloak02
192.168.234.134 keycloak.acaiblog.top

安装keepalived

yum install keepalived -y

创建配置文件

# keepalived.conf 示例  

! Configuration File for keepalived

global_defs {
router_id 55
}

vrrp_script chk_keycloak {
script "/etc/keepalived/check_keycloak.sh"
interval 2
weight 2
}

vrrp_instance VI_1 {
interface ens33
state MASTER
virtual_router_id 55
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.234.250
}
#track_script {
# chk_keycloak
#}
}

创建检查脚本

cat >/etc/keepalived/check_keycloak.sh<<EOF
#!/bin/bash
res=`curl -s http://keycloak.acaiblog.top:8080/realms/master/.well-known/openid-configuration`
if [ -z "$res" ]; then
echo "request http://keycloak.acaiblog.top:8080/realms/master/.well-known/openid-configuration is failed"
exit 1
else
exit 0
fi
EOF

添加脚本执行权限

chmod +x /etc/keepalived/check_keycloak.sh

启动keepalived

systemctl enable keepalived
systemctl start keepalived

MariaDB集群

keycloak01节点

docker run -p --net host --restart always -v /var/lib/mysql:/bitnami/mariadb --name mariadb-galera01 \
-e MARIADB_GALERA_CLUSTER_NAME=kc_cluster \
-e MARIADB_GALERA_MARIABACKUP_USER=my_mariabackup_user \
-e MARIADB_GALERA_MARIABACKUP_PASSWORD=my_mariabackup_password \
-e MARIADB_ROOT_PASSWORD=my_root_password \
-e MARIADB_GALERA_CLUSTER_BOOTSTRAP=yes \
-e MARIADB_USER=kcuser \
-e MARIADB_PASSWORD=kcpassword \
-e MARIADB_REPLICATION_USER=my_replication_user \
-e MARIADB_REPLICATION_PASSWORD=my_replication_password \
bitnami/mariadb-galera:11.2.3

keycloak02节点

docker run --net host --restart always -v /var/lib/mysql:/bitnami/mariadb  --name mariadb-galera02 \
-e MARIADB_GALERA_CLUSTER_NAME=kc_cluster \
-e MARIADB_GALERA_CLUSTER_ADDRESS=gcomm://192.168.234.133:4567,192.168.234.134:4567 \
-e MARIADB_GALERA_MARIABACKUP_USER=my_mariabackup_user \
-e MARIADB_GALERA_MARIABACKUP_PASSWORD=my_mariabackup_password \
-e MARIADB_ROOT_PASSWORD=my_root_password \
-e MARIADB_REPLICATION_USER=my_replication_user \
-e MARIADB_REPLICATION_PASSWORD=my_replication_password \
bitnami/mariadb-galera:11.2.3

确认数据库集群状态

MariaDB [(none)]> show status like '%wsrep_incoming_addresses%';
+--------------------------+-------------------------------------+
| Variable_name | Value |
+--------------------------+-------------------------------------+
| wsrep_incoming_addresses | 192.168.234.133:0,192.168.234.134:0 |
+--------------------------+-------------------------------------+

keycloak部署

添加hosts解析

192.168.234.250 keycloak.acaiblog.top

创建数据库用户和数据库

CREATE USER 'keycloak'@'%' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON keycloak.* TO 'keycloak'@'%';
create database keycloak character set utf8 collate utf8_unicode_ci;

由于keycloak使用MariaDB Galera集群存在bug,需要在源镜像基础上修改以下内容

#vi Dockerfile
FROM quay.io/keycloak/keycloak:24.0.1
RUN /opt/keycloak/bin/kc.sh build --db=mariadb --transaction-xa-enabled=false --cache=ispn
ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]

重新构建keycloak镜像

docker build . -t keycloak:v24.0.1

创建部署keycloak docker-compose配置文件

version: '3'

services:
keycloak:
image: quay.io/keycloak/keycloak:v24.0.1.1
extra_hosts:
- "keycloak.acaiblog.top:192.168.234.250"
container_name: keycloak
environment:
- KEYCLOAK_ADMIN=admin
- KEYCLOAK_ADMIN_PASSWORD=admin
- KC_DB=mariadb
- KC_DB_URL=jdbc:mariadb://192.168.234.250:3306/keycloak?characterEncoding=UTF-8
- KC_DB_USERNAME=keycloak
- KC_DB_PASSWORD=password
- KC_PROXY=edge
- KC_HTTP_ENABLED=true
- KC_HOSTNAME_URL=http://keycloak.acaiblog.top:8080
- KC_HOSTNAME_ADMIN_URL=http://keycloak.acaiblog.top:8080
- JGROUPS_DISCOVERY_PROTOCOL=JDBC_PING
command: ["start", "--optimized","--hostname-strict-https","true"]
network_mode: "host" # 设置网络模式为 host

启动keycloak容器

docker-compose -f keycloak.yml up -d

本地添加hosts解析

192.168.234.250 keycloak.acaiblog.top

测试:登录keycloak:http://keycloak.acaiblog.top:8080/auth/ admin/admin

编辑keepalived配置文件,解开脚本检查注释

track_script {
chk_keycloak
}

测试高可用

分别关闭两个节点keycloak容器,观察VIP是否会漂移。如果VIP能够互相漂移说明高可用生效了。

目前存在的问题

切换高可用后,会返回500错误。详细日志如下:

2024-03-15 08:42:55,314 INFO  [io.quarkus] (main) Profile prod activated.
2024-03-15 08:42:55,314 INFO [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-orm, jdbc-mariadb, keycloak, logging-gelf, narayana-jta, reactive-routes, resteasy-reactive, resteasy-reactive-jackson, smallrye-context-propagation, vertx]
2024-03-15 08:43:46,958 WARN [com.arjuna.ats.jta] (executor-thread-1) ARJUNA016061: TransactionImple.enlistResource - XAResource.start returned: XAException.XAER_RMERR for < formatId=131077, gtrid_length=35, bqual_length=36, tx_uid=0:ffff7f000001:8f14:65f409f9:4c, node_name=quarkus, branch_uid=0:ffff7f000001:8f14:65f409f9:8b, subordinatenodename=null, eis_name=0 >: javax.transaction.xa.XAException: Error trying to start local transaction: (conn=56) Connection is closed

总结

Keycloak作为一个功能强大、易于集成的身份和访问管理解决方案,能够帮助组织解决单点登录、身份管理和访问控制等问题。通过合理的架构设计和高可用部署方案,可以确保Keycloak的稳定性和安全性,为组织提供可靠的身份认证和授权服务。

文章作者: 慕容峻才
文章链接: https://www.acaiblog.top/keycloak高可用实践/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 阿才的博客
微信打赏
支付宝打赏