什么是 Free Arch

Free Arch 是我杜撰的一个词,旨在为应用架构提供免费自由的架构指引,特别是针对个人开发者,但不局限于个人开发者。

什么是 Keycloak

Keycloak 是开源的身份与访问控制系统。它实现了 SAML 和 OAuth 协议,可以为你的应用提供单点登录功能,还能仅通过配置就接入多个身份提供商,不用写代码。之前写过多篇相关的文章,但是除了接入身份提供商,它还做用户联邦,这个之前还没有探索过,今天就来探索一下。

它支持两种用户联邦提供服务:kerberos 和 ldap。今天关注 ldap:
image.png

什么是 Authing

可以参考其文档: https://docs.authing.cn/v2/concepts/。它是一个身份云,在云上为企业和开发者提供专业的身份认证和授权服务。它拥有和 Keycloak 同样的功能,除此之外,还有更多贴心的服务。可以参考《Free Arch: Keycloak vs Authing - Jeff Tian的文章 - 知乎 》一文对比 Keycloak 和 Authing,不过,Keycloak 和 Authing 都在迭代,所以文章中有些内容可能已经过时了。

什么是 LDAP

LDAP(轻型目录访问协议)是一种软件协议 ,使任何人都可以在公共互联网或公司内网上查找网络中的组织,个人和其他资源(例如文件和设备)的数据 。LDAP 是目录访问协议(DAP)的“轻量级”版本,它是 X.500( 网络中目录服务的标准 )的一部分。

一般来说,企业都会有 LDAP 服务,员工的元信息都能在 LDAP 服务中查询到,以及密码等安全凭据都保存于此。

在 Keycloak 中联邦 LDAP 用户

前几篇文章层层递进,从一个用户开始,验证了端到端使用 Keycloak 取代一个系统的用户登录功能;然后,第二篇详述了批量用户迁移;接着,第三篇介绍了关联在不同系统中的用户名不一样的同一用户的方法,从而能将企业中独立自建的各个系统的用户登录模块迁移到 Keycloak 中。

本篇文章在前三篇的基础上,联邦 LDAP 用户,从而允许企业员工使用域账号登录 Keycloak。

Keycloak 内置了 LDAP 提供商,在同一个 Keycloak Realm 里,可以联邦多个不同的 LDAP 服务。

LDAP 映射器

你可以将 LDAP 用户的属性映射到 Keycloak 的通用用户模型中,默认是映射用户名、邮箱、姓氏和名字,但是也可以通过 LDAP 映射器配置额外的映射。

用户属性映射器

通过该映射器,你可以指定将哪个 LDAP 属性映射为哪个 Keycloak 用户属性,比如将 LDAP 中的 mail映射为 Keycloak 中的 email等。这必须是一对一的映射。

全名映射器

通过它可以指定用户的全名。在 LDAP 中,这通常被存储为 cn,而在 Keycloak 数据库中,这通常是 firstName和 lastName。

角色映射器

同一个 LDAP 提供商可以配置多个角色映射器。

硬编码角色映射器

它可以为所有链接到 Keycloak 用户的 LDAP 用户分配一个固定的 Keycloak 角色。

用户组映射器

它可以用来将处于 LDAP 树中某个分支的 LDAP 组遇到到 Keycloak 中的某个用户组。

微软活动目录账号映射器

这是微软活动目录(MSAD)专用的。它能将 MSAD 中的用户状态和 Keycloak 中的账号状态做对应,比如账号是否禁用、密码是否过期等等。它用了 userAccountControl和 pwdLastSet属性,这两个都是 MSAD 专有,而非 LDAP 标准。如果 pwdLastSet是 0,Keycloak 就会给用户添加一个 UPDATE_PASSWORD动作要求,从而迫使用户更新密码。如果 userAccountControl是514(禁用的账号),Keycloak 就会禁用该用户。

存储模式

Keycloak 默认会将 LDAP 用户导入到本地用户存储。被导入的用户副本要么按需同步,要么通过定期后台任务同步。
image.png

但是密码是个例外,密码不会被导入,密码验证会委托给 LDAP 服务器。
image.png

你也可以选择不将用户导入到 Keycloak 数据库,这样的话, Keycloak 的通用用户模型就在运行时依赖 LDAP 服务器。

存储模式通过 Import Users开关来控制,如果要导入,就将它设置为开启:
image.png

编辑模式

只读

用户名、邮箱、姓氏和名字,以及其他的映射属性,一旦导入就不能更改。这时,任何修改尝试都会触发 Keycloak 的报错。而且,这种模式下也不支持修改密码。

可写

用户名、邮箱、姓氏和名字,以及其他的映射属性甚至密码,都允许更新,并且会自动同步回 LDAP 服务器。

不同步

用户名、邮箱、姓氏和名字,以及其他的映射属性甚至密码都会被存储在 Keycloak 本地存储。如何同步回 LDAP 取决于你。

实验环境准备

Keycloak

实验将继续使用我的线上 Keycloak 实例: https://keycloak.jiwai.win

LDAP 服务

有多种选择,最推荐的是 Authing 提供的 LDAP 服务,所以标题特别注明了“感谢 Authing”。这里先列举几个,然后以 Authing 为例,详细记录在 Keycloak 中联邦 LDAP 的过程。

OpenLDAP 容器

详见: https://hub.docker.com/r/rroemhild/test-openldap/,它自带测试数据,可以使用以下命令运行: shell docker pull rroemhild/test-openldap docker run --privileged -d -p 389:389 -p 636:636 rroemhild/test-openldap

使用公司的 LDAP 服务

如果使用公司的 LDAP 服务,你需要知道连接它的账号和密码。
![image.png](https://cdn.nlark.com/yuque/0/2022/png/221736/1669893093440-36224e62-5d4f-4e68-90f6-8577ff80e4f4.png#averageHue=%2349413e&clientId=uc810936d-02a5-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=680&id=uef0029f4&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1360&originWidth=1618&originalType=binary∶=1&rotation=0&showTitle=true&size=674136&status=done&style=none&taskId=ue6afa98d-08f8-4974-81be-4efa7efce49&title=%E5%9C%A8%20mac%20os%20%E4%B8%8A%E5%8F%AF%E4%BB%A5%E4%BD%BF%E7%94%A8%20Directory%20Utility%20%E6%9F%A5%E7%9C%8B%E5%85%AC%E5%8F%B8%E7%9A%84%20LDAP%20%E6%9C%8D%E5%8A%A1%E4%BF%A1%E6%81%AF&width=809 在 mac os 上可以使用 Directory Utility 查看公司的 LDAP 服务信息)

Authing 的 LDAP 服务

如果不知道公司 LDAP 服务的账号密码,又不想用容器方案(不够真实,也不能在线上服务中使用到它),那么强烈推荐 Authing 提供的 LDAP 服务。

首先,你需要创建一个用户池:
image.png
可以选择 To E,为企业员工管理身份:

image.png

给这个用户池取个名字,即可完成创建:
image.png

这时,在组织机构菜单下可以看到 LDAP 子菜单项,它默认是关闭的,需要点击开启:
image.png

随后,去到设置、基础设置、密钥管理中,分别记录下“用户池ID”和“当前密钥”。它们是登录 LDAP 服务的凭证。
image.png

命令行测试

运行一下 whoami,得到了 [email protected]的结果,说明一切正常。 shell ldapwhoami -H ldap://ldap.authing.cn:1389 -x -D ou=users,o=63887ae0829d0db6835ff760,dc=authing,dc=cn -w 8315***********

[email protected]

如果使用没有开启 LDAP 的用户池和密钥,可能得到 NoSuchObjectError错误: shell ldapwhoami -H ldap://ldap.authing.cn:1389 -x -D ou=users,o=620097b69a9dab5e967d0c44,dc=authing,dc=cn -w ff5a******

ldap_bind: No such object (32) matched DN: dc=authing, dc=cn additional info: NoSuchObjectError

运行 ldapsearch命令查询用户,可以得到如下结果: shell ldapsearch -H ldap://ldap.authing.cn:1389 -x -D ou=users,o=63887ae0829d0db6835ff760,dc=authing,dc=cn -w 8315******** -LLL -b ou=users,o=63887ae0829d0db6835ff760,dc=authing,dc=cn dn: ou=users,o=63887ae0829d0db6835ff760,dc=authing,dc=cn o: 63887ae0829d0db6835ff760 name: duplo cn: duplo description: duplo gidNumber: 63328 objectGUID: 63887ae0829d0db6 objectclass: organization objectclass: top objectclass: posixgroup

dn: uid=63887ae152675c6a7e65212e,ou=users,o=63887ae0829d0db6835ff760,dc=authin g,dc=cn id: 63887ae152675c6a7e65212e createdAt: Thu Dec 01 2022 17:58:57 GMT+0800 (China Standard Time) updatedAt: Thu Dec 01 2022 17:58:57 GMT+0800 (China Standard Time) userPoolId: 63887ae0829d0db6835ff760 username: test photo: https://files.authing.co/authing-console/default-user-avatar.png gender: U registerSource: unknown emailVerified: false phoneVerified: false signedUp: Thu Dec 01 2022 17:58:57 GMT+0800 (China Standard Time) blocked: false uid: 63887ae152675c6a7e65212e objectclass: users objectclass: posixAccount cn: test uidNumber: 8494 homeDirectory: /home/users/test entryuuid: 63887ae152675c6a7e65212e gidNumber: 63328 objectGUID: 63887ae152675c6a distinguishedName: uid=63887ae152675c6a7e65212e,ou=users,o=63887ae0829d0db6835 ff760,dc=authing,dc=cn

dn: o=63887ae1f22f9b1387548e97,ou=users,o=63887ae0829d0db6835ff760,dc=authing, dc=cn id: 63887ae1f22f9b1387548e97 createdAt: Thu Dec 01 2022 17:58:57 GMT+0800 (China Standard Time) updatedAt: Thu Dec 01 2022 17:58:57 GMT+0800 (China Standard Time) userPoolId: 63887ae0829d0db6835ff760 orgId: 63887ae1dde6eb1865bb7f11 name: duplo sort: 100000000 i18n: [object Object] code: ohMSBNiUiYXu2QGLApCyiBN8VmjzxA isVirtualNode: false gidNumber: 36503 objectGUID: 63887ae1f22f9b13 entryuuid: 63887ae1f22f9b1387548e97 distinguishedName: o=63887ae1f22f9b1387548e97,ou=users,o=63887ae0829d0db6835ff 760,dc=authing,dc=cn o: 63887ae1f22f9b1387548e97 ou: 63887ae1f22f9b1387548e97 objectClass: top objectClass: organization objectClass: group objectClass: domaindns objectClass: posixgroup cn: duplo path: duplo parent: Root parentId: 0 parentCode: root

图形界面工具

推荐使用 Apache Directory Studio,如果是 MacOS,可以通过 brew install apache-directory-studio安装。安装后如果不能打开,并且点击了设置中的 open anyway还是被阻止的话,就需要在命令行里输入: sudo spctl --master-disable再次 open anyway即可打开。
WX20221201-171735@2x.png

打开后,新建连接,输入 Authing 的 LDAP 服务器信息:
image.png
以及登录凭据:
image.png
连接成功后,可以看到新创建的用户池里的 LDAP 目录中有一个 test 用户,我们接下来可以使用 Keycloak 的用户联邦功能导入该用户到 Keycloak 的用户数据库中。
image.png

实验步骤

一、在 Keycloak 里添加 LDAP 提供商

在 Vendor 栏选择 Active Directory 后,会默认填入多数选项。在输入 Authing 的 LDAP 服务地址后,点击测试连接,可以看到连接成功的消息:
WX20221201-181224@2x.png

然后,输入 Bind DN 和 Bind Credential 后,点击测试登录,可以看到登录成功的反馈。
WX20221201-181201@2x.png

接着,将 User Object Classes 从自己填充的“person, organizationalPerson, user”修改为“top”。点击保存,就将该 LDAP 连接保存了下来。

二、同步所有用户

随后,可以点击“同步所有用户”,稍等一会儿,就能看到反馈信息,比如:成功同步了一个用户。
WX20221201-181330@2x.png

三、验证同步过来的用户

在用户页面,可以找到从 LDAP 同步过来的用户,会发现用户名被映射成了 duplo,即用户池的名字,而不是测试用户的名字。
WX20221201-182149@2x.png
WX20221201-182031@2x.png

这是默认行为导致的,它将 LDAP 中的 cn 映射成了 Keycloak 中的 username。

image.png
而对于 Authing 的 LDAP 设置,它其实是有 username 字段的,所以应该修改一下映射器。
image.png
以上是用 ladapsearch 查询的结果,通过 Authing 的网页控制台也可以看到 username 字段:
WX20221201-181512@2x.png
WX20221201-181528@2x.png
要修改 username 映射器,可以在 Keycloak LDAP 的映射器标签页找到 username:
image.png
点击进入发现果然是将 cn映射成了 username,修改成 username并保存,再重新同步:
image.png
如果这样同步到的还是不对,可以再检查一下对用户的选择器,userObject 是否正确。前面我们配置了 top,这样有问题。通过仔细检查 ldapsearch的输出,我们发现 Authing 配置的 userobject 是 users,在 LDAP 的配置里更正后,再次重新同步,可以看到,Keycloak 里的 LDAP 用户的用户名已经更正为 test:
image.png

四、查看 Keycloak 的日志输出

可以通过日志输出,清楚地看到同步过来的过程,以及属性映射的过程:
WX20221201-181908@2x.png

如果有导入用户失败的情况,你也可以通过 Keycloak 的日志排查原因,如果日志不够详细,可以用 Debug 日志级别来排查。比如输入了错误的 UUID LDAP attribute 值(将 ObjectGUID输入成了 uuid)。
image.png
会导致如下的错误日志输出:
image.png

又比如,如果将 username 映射配置错误的话,就会得到 User returned from LDAP has null username!错误。
image.png

如果有其他问题,建议结合使用 ldapsearch去逐一排查。