| 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的基本配置,这里没有展示全部配置信息,未显示出来的配置可以直接按照默认方式填充:
这里需要强调几点:
- 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的配置:
这里只需要配置两处,但是这两处非常的关键
- 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();