Keycloak集成LDAP

| March 16, 2018

在如今微服务盛行的时代,对Web服务的鉴权和授权需要更好的解决方案,而本篇文章的主角—Keycloak正是为此而生.正如Keycloak的官方介绍:它致力于现代应用和服务的鉴权和授权管理方案

Keycloak实现了OpenID,Auth2.0,SAML单点登录协议,同时提供LDAP和Active Directory,以及OpenID Connect,SAML2.0 IDPs,Github,Google等第三方登录适配功能,能够做到非常简单的开箱即用

本章的主题是Keycloak集成LDAP,那么为什么要集成LDAP呢?

使用Keycloak无外乎就是对公司的已有用户和员工的数据进行管理,许多公司都有自己的一套用户数据库保留用户的信息,在某些情况下,将数据迁移到Keycloak中进行存储是有一定的难度的,所以Keycloak提供了provider以便和其他的用户存储系统进行整合.

除了Keycloak已经提供的LDAP和Active Directory,开发者也可以自己扩展User Storage SPI 定制功能.

Apache DS的应用

Apache DS是一个用纯Java实现的可扩展和嵌入的文件服务器,并且支持LDAPv3

启动Apache DS:

/bin/apacheds.sh start

这里使用了默认的用户default,所以不需要指定用户. Schema存放路径是:${APACHEDS_HOME}/instances/default/partitions/schema,该路径下我们可以选择要导入Keycloak中的数据,以Users DN = ou=schema,cn=java,ou=objectclasses 为例,配置完成后会将该Users DN下的所有数据同步到Keycloak中.

Keycloak配置

Keycloak的获取方法有两种方式:

  • 第一种是从Keycloak官方直接下载,当前最新的版本3.4.3;
  • 第二种方式是从GitHub上下载源码进行编译.

启动Keycloak

cd keycloak-<VERSION>
bin/standalone.sh

默认的端口号是8080.

进入Keycloak界面首先要创建admin用户

Keycloak集成MySQL

Keycloak中已经嵌入H2数据库,但是为了更方便的查看数据以及后续的Keycloak其他操作,这里我们使用MySQL数据库,如果读者使用过wildfly,对于数据库的配置可以说是再熟悉不过了. 首先要下载对应数据库的驱动,配置的路径为:

${KEYCLOAK_HOME}/modules/system/layers/base/keycloak/org/mysql/main

这个路径下要求有driver和一个module.xml文件,创建和配置module.xml文件:

<module xmlns="urn:jboss:module:1.3" name="com.mysql">
    <resources>
        <resource-root path="mysql-java-5.1.42.jar"/>
    </resources>
    <dependencies>
        <module name="javax.api"/>
        <module name="javax.transaction.api"/>
        <module name="javax.servlet.api" optional="true"/>
    </dependencies>
</module>

其次要声明driver

${KEYCLOAK_HOME}/standalone/configuration/standalone.xml

添加驱动

<subsystem xmlns="urn:jboss:domain:datasources:4.0">
     <datasources>
       ...
       <drivers>
          <driver name="h2" module="com.h2database.h2">
              <xa-datasource-class>org.h2.jdbcx.JdbcDataSource</xa-datasource-class>
          </driver>
           <driver name="mysql" module="com.mysql">
              <xa-datasource-class>com.mysql.jdbc.jdbc2.optional.MysqlXADataSource</xa-datasource-class>
           </driver>
       </drivers>
     </datasources>
</subsystem>
​

这里已经存在一个Keycloak内嵌的H2数据库了. 第三步是配置数据库

<subsystem xmlns="urn:jboss:domain:datasources:4.0">
     <datasources>
       ...
       <datasource jta="true" jndi-name="java:jboss/datasources/KeycloakDSMySQL" pool-name="KeycloakDSMySQL" enabled="true" use-ccm="true">
           <connection-url>jdbc:mysql://localhost:3306/KeycloakDS</connection-url>
           <driver-class>com.mysql.jdbc.Driver</driver-class>
           <driver>mysql</driver>
           <security>
               <user-name>username</user-name>
               <password>password</password>
           </security>
           <validation>
               <valid-connection-checker class-name="org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLValidConnectionChecker"/>
               <background-validation>true</background-validation>
               <exception-sorter class-name="org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLExceptionSorter"/>
            </validation>
      </datasource>
        ...
    </datasources>
</subsystem>
​

第四步是关联Keycloak和数据库

<subsystem xmlns="urn:jboss:domain:keycloak-server:1.1">
    ...
    <spi name="connectionsJpa">
     <provider name="default" enabled="true">
         <properties>
             <property name="dataSource" value="java:jboss/datasources/KeycloakDSMySQL"/>
             <property name="initializeEmpty" value="false"/>
             <property name="migrationStrategy" value="manual"/>
             <property name="migrationExport" value="${jboss.home.dir}/keycloak-database-update.sql"/>
         </properties>
     </provider>
    </spi>
    ...
</subsystem>
​

DataSource的value值和第三步中的jndi-name保持一致. 最后一步需要将${KEYCLOAK_HOME}下的keycloak-database-update.sql执行,建立Keycloak的数据表.

Keycloak和Apache DS的桥梁——LDAP Provider

前面做了N步的铺垫工作,终于来到本期的主角:LDAP Provider.通过Provider可以将Apache DS服务器中的数据导入或者更新到Keycloak中

Provider分为Settings和Mappers两部分需要配置,但是这里需要强调一点,当前的数据提供的服务器是Apache DS,所以如果使用OpenLDAP在其中的Mappers上可能会有不同,这里大家需要注意. 如图所示是Settings的基本配置,这里没有展示全部配置信息,未显示出来的配置可以直接按照默认方式填充:

Settings配置信息

这里需要强调几点:

  • Vendor: 要选择Active Directory
  • Username LDAP attribute: 可以直接使用已经提供的cn作为名称,但是后面还需要调整mapper中的设置.
  • UUID LDAP attribute: 保持entryUUID 不变(这个值和Active Directory选项对应)
  • User Object Classes: 这个值可以在Apache DS的schema 的objectclasses LDIF文件中得到或者使用ldapsearch命令查找.
  • Users DN: ou=schema,cn=java,ou=objectclasses

最后点击save保存 接下来是Mappers配置,在mappers中,我暂且只介绍username的配置:

Mappers配置信息

这里只需要配置两处,但是这两处非常的关键

  • User Model Attribute: 默认设置是usernaem,但是这个要和LDIF文件相对应,在Apache DS的LDIF文件中,需要使用creatorsname
  • LDAP Attribute: cn就是Settings中的Username LDAP attribute值,如果在LDIF文件中没有cn值,需要自行添加.

最后一击:通过Keycloak Admin REST接口获取所有Users

在Users界面,通过点击View all users按钮可以查看所有的用户,但是这也仅限于Keycloak应用内部查看,而在实际的业务需求中可能存在其他应用对用户数据的需求,这是就需要外部应用也能够访问Keycloak内部数据. 如果直接在浏览器中输入获取用户的URL,得到的返回只有401 Unanthorized,因为在Keycloak应用内部访问时有用户的登录信息,根据登录信息Keycloak判断当前用户是否有权限访问该数据. 通过观察在Keycloak应用内部的HTTP请求可以看到它的头部附带一个Authorization的字段,这个Token就是授权信息.如果能够模拟出这个Token那么就可以得到所有用户的数据. 在Keycloak的文档中提供了一个解决方法:

curl -d "client_id=admin-cli" -d "username=admin" -d "password=admin" -d "grant_type=password" "http://localhost:8080/auth/realms/master/protocol/openid-connect/token"

该请求返回的就是Token值,这个值的有效期是1分钟,然后我们使用这个Token值再去请求(可以使用curl方式,也可以使用PostMan等工具完成)

curl -H "Authorization: bearer {Token}" "http://localhost:8080/auth/admin/realms/master/users"

这样就会得到全部的用户数据 罗里吧嗦说了这么多只是提供一个解决的思路,实际上Keycloak已经为我们提供了完美的解决方案,通过它提供的API可以看出,它应该是将这些命令进行了包装.

添加依赖

<dependency>
    <groupId>org.keycloak</groupId>
    <artifactId>keycloak-admin-client</artifactId>
    <version>3.4.3.Final</version>
</dependency>

获取Keycloak用户数据

Keycloak keycloak = Keycloak.getInstance("http://localhost:8180/auth","master","admin","admin", "admin-cli");
UsersResource usersResource = keycloak.realm("master").users();