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的优势
开源免费 :Keycloak
是开源项目,用户可以自由使用、修改和扩展,无需支付任何费用。
功能丰富 :支持多种认证协议和社交登录,提供灵活的身份管理和访问控制功能。
易于集成 :Keycloak
可以与各种应用和企业环境无缝集成,包括Java
应用、REST API
、移动应用等。
可扩展性 :Keycloak
支持自定义认证流程、用户属性和策略,满足各种复杂业务需求。
安全性 :Keycloak
提供多层次的安全保护,包括加密、令牌管理、审计日志等,确保用户数据的安全和隐私。
Keycloak可以解决哪些问题
单点登录(SSO) :用户只需一次登录即可访问多个应用,提高了用户体验和安全性。
身份管理 :集中管理用户身份和权限,简化用户管理过程,降低管理成本。
访问控制 :基于角色和策略的访问控制,确保只有授权用户可以访问特定资源。
社交登录 :支持使用社交账号(如微信、微博等)登录应用,提高用户注册和登录的便捷性。
Keycloak工作原理 当用户尝试访问受保护的资源时,Keycloak
通过以下步骤进行认证和授权:
认证请求 :客户端向Keycloak
发送认证请求,包含用户的身份信息。
身份验证 :Keycloak
验证用户的身份信息,包括用户名和密码、社交账号等。
发放令牌 :验证成功后,Keycloak
发放访问令牌(Access Token)和刷新令牌(Refresh Token)给客户端。
访问资源 :客户端使用访问令牌向受保护的资源发起请求,Keycloak
验证令牌的有效性并授权访问。
Keycloak核心概念
组件/概念
描述
用户(Users)
能够登录到系统的实体。可以理解为账户
认证(Authentication)
识别和验证用户的过程。这个过程确保只有有效的用户可以访问系统资源。
授权(Authorization)
授予用户访问权限的过程。系统根据用户的角色和策略决定其可以访问哪些资源。
凭据(Credentials)
Keycloak用于验证用户身份的数据片段。例如,密码、一次性密码、数字证书或指纹等。
角色(Roles)
用户角色映射(User Role Mapping)
定义角色与用户之间的映射关系。用户可以与零个或多个角色相关联。
复合角色(Composite Roles)
可以与其他角色相关联的角色。
组(Groups)
管理用户组的实体。可以为组定义属性。可以将角色映射到组。成为组成员的用户继承该组定义的属性和角色映射。
领域(Realms)
管理一组用户、凭据、角色和组的容器。用户属于某个领域并登录到该领域。领域之间是相互隔离的,只能管理和验证它们所控制的用户。
Keycloak架构 Keycloak的架构主要包括以下几个部分:
服务器 :Keycloak
服务器负责处理认证请求、管理用户信息和授权策略等。
适配器 :适配器用于将Keycloak
集成到各种应用中,包括Java
应用、REST API
、移动应用等。
数据库 :存储用户信息、认证令牌、审计日志等数据。
客户端 :用户通过客户端(如浏览器、移动应用等)与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 容器。
TCPPing:使用TCP协议,端口为7600。当多播不可用时,可以使用此功能,例如跨数据中心、跨主机容器部署。
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
的稳定性和安全性,为组织提供可靠的身份认证和授权服务。