如果你在测试keycloak的快速入门程序,会发现大部分代码是在3年前写的。多数情况是,下载下来以后跑不通的。这里以一个sso的例子,调试通quickstart的代码,并理解其中的原理。
一、 简要原理
Realm:Keycloak作为认证授权服务器,使用realm区分认证域,不同的reaml不能互相认证。
Client:在同一个realm下,需要创建client,一个client是一个授权策略、资源、范围及其对应关系的集合。一般需要指定回调uri,标明授权给的资源服务器。
资源服务器:通常理解的应用服务,可以是web应用,可以是service。原先的做法是各应用建设认证和授权的模块。统一认证不仅可以做到少登录,还能依托认证授权服务器,实现统一的权限管理。

一般的,我们会在keycloak建一个realm,为对应的应用服务器新建client。同一个realm下的不同client,可以被相互认证。也就是说,A应用和B应用对应keycloak的不同client,用户在应用A登录后,无需登录即可访问B,只要有对应的资源权限。
以下就keycloak官方提供的示例代码,介绍一下。
二、 示例代码
官方提供的示例代码地址:
https://github.com/keycloak/keycloak-quickstarts

这个示例项目跟随主项目更新频率特别高,第一次接触这个是5.0.0,上个月还在8.0.1,现在已经9.0.0了,仔细查看release note,发现主题框架和功能没有变化,新增了一些安全性的和易用性的改进,以及支持浏览器升级带来的安全性设置。
示例包含了keycloak官方文档中提到的各章节,分别都做了单独的项目示例,总数超过20个,java居多,部分是JavaScript,包括前端和后端。我们选取java的两个后端项目做示例。
示例代码的演示主题在各自readme中,不太能看出来。需要在官方文档对应章节了解。Java的对应了多种web容器,不过实际使用中,还是以springboot居多,于是在springboot相关的项目中,选择了一个web应用,选择一个服务型的应用。
Web应用地址:
https://github.com/keycloak/keycloak-quickstarts/tree/latest/app-springboot
服务应用地址:
https://github.com/keycloak/keycloak-quickstarts/tree/latest/service-springboot-rest
众所周知,示例代码一般是跑不起来的。我们改一下~
三、 代码调试
Fork代码
直接clone下来的代码不能用,只好自己改,但是呢,改了的也不好提交到原来的仓库,所以fork一个新的仓库。在github注册一个账户,找到官方提供的示例代码地址:
https://github.com/keycloak/keycloak-quickstarts

选择fork,就会创建一个自己的仓库。此时可以clone到自己的电脑,开始改代码了。
Clone到本地
需要已经安装好了git 客户端,并且配置的个人账户和secret。这样就可以修改后提交代码 了。
克隆到本地
git clone git@github.com:voicyfei/keycloak-quickstarts.git 这个地址应该是fork的时候生成的地址,会有些许不同。
服务器端设置
新建realm,新建客户端,客户端设置。
这个代码仓库提供了现成测试的导出文件,我们不需要一个一个设置了,直接导入即可。

选择文件,gitrepo/ quickstart-realm.json,直接导入即可
在新建的quickstart-realm下,选择客户端,添加客户端,分别导入/app-springboot和/service-springboot-rest两个目录下config文件中的json文件。此时创建好了两个客户端,分别是app-springboot和service-springboot。
如果导入文件失败,不要慌,在keycloak管理界面中,左侧菜单选择导入,再试一次,导入策略选择覆盖。

IDE 准备
我使用的是vscode,微软开发、轻量级、支持插件的IDE,免费!
为了支持java开发,需要安装一些插件

到插件菜单,搜索java,maven,把相关的评分高的插件安装即可。如果有依赖关系会直接安装。在导入代码后,IDE不支持的特性,也会提示安装插件,非常简便。如果没有网络连接也没关系,插件支持离线安装,可以到vscode 官方网站,提前下载好。图中我加了几个别的差价,支持docker的支持k8s,非常好用。
导入第一个项目
Vscode 选择开发文件夹,选择clone的仓库中的app-springboot,打开

这里一般的耍法就是,直接maven 打包启动。
在命令行输入 mvn package build success!真棒
错误一:
然后在输入mvn spring-boot:run
报错:
KeycloakBaseSpringBootConfiguration required a bean of type 'org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver' that could not be found.
Spring解析bean的方法是依赖注入,不管这个bean在哪里定义的,只要是在需要他之前已经生成好了,就应该没有问题。利用类名作关键字,在整个项目搜索。找到了相关的定义如下
@Bean
publicKeycloakConfigResolver KeycloakConfigResolver() {
returnnew KeycloakSpringBootConfigResolver();
}
直接new出来对象并没有生效。上网搜索解决方案,添加一个自定义的解析类,并替换此处的创建方式。
新增类:
@Configuration
publicclass CustomKeycloakSpringBootConfigResolver extends KeycloakSpringBootConfigResolver {
privatefinalKeycloakDeployment keycloakDeployment;
public CustomKeycloakSpringBootConfigResolver(KeycloakSpringBootProperties properties) {
keycloakDeployment = KeycloakDeploymentBuilder.build(properties);
}
@Override
publicKeycloakDeployment resolve(HttpFacade.Request facade) {
return keycloakDeployment;
}
}
修改初始化:
@Bean
@Primary
publicKeycloakConfigResolver keycloakConfigResolver(KeycloakSpringBootProperties properties) {
returnnew CustomKeycloakSpringBootConfigResolver(properties);
}
错误二:
IDE在编译时找到两个错误的override错误
protectedServletContextListener listener() {
returnnew ServletContextListener() {
@Override
publicvoid contextInitialized(ServletContextEvent sce) {
logger.info("ServletContext initialized");
}
@Override
publicvoid contextDestroyed(ServletContextEvent sce) {
logger.info("ServletContext destroyed");
}
};
去掉两个override注解即可
错误三:配置文件参数错误
我们刚才指定的keycloak客户端名称是app-springboot
keycloak.resource=product-app
修改此处为app-springboot
错误四:
编译错误和配置文件修改后,再次debug启动,出现如下错误
Caused by: java.lang.ClassNotFoundException: org.apache.http.client.methods.HttpUriRequest
查看pom.xml依赖文件
<dependency>
<groupId>org.apache.httpcomponentsgroupId>
<artifactId>httpclientartifactId>
<version>${httpclient.version}version>
<scope>testscope>
dependency>
其实指定了httpcomponents,scope限制在了test,去掉scope标签,起来了!
错误五:
浏览器访问本地8080端口http://localhost:8080/ 会有标签解析错误。此框架采用的是freemarker来处理标签,在application.properties文件中添加相关配置
spring.freemarker.allow-request-override=false
spring.freemarker.cache=false
spring.freemarker.check-template-location=true
spring.freemarker.charset=UTF-8
spring.freemarker.content-type=text/html
spring.freemarker.expose-request-attributes=false
spring.freemarker.expose-session-attributes=false
spring.freemarker.expose-spring-macro-helpers=false
spring.freemarker.prefix=
spring.freemarker.suffix=.ftl
再次访问http://localhost:8080/,出现页面了。
错误六:
点击页面中的超链接会出现keycloak的登录页面,要求出入用户名密码。

输入角色为user的用户alice/alice,等一会,页面出现403,没有授权?
查看代码,找到这个链接对应的控制器。
@RequestMapping(value = "/products", method = RequestMethod.GET)
publicString handleCustomersRequest(Principal principal, Model model) {
model.addAttribute("products", productService.getProducts());
model.addAttribute("principal", principal);
String logoutUri = KeycloakUriBuilder.fromUri("http://localhost:8180/auth").path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH)
.queryParam("redirect_uri", "http://localhost:8080/products").build("quickstart").toString();
model.addAttribute("logout", logoutUri);
return"products";
}
发现调用了一个service 的get方法productService.getProducts()。
再看这个service
@Value("${product.service.url}")
privateString endpoint;
publicList<String> getProducts() {
ResponseEntity<String[]> response = template.getForEntity(endpoint, String[].class);
return Arrays.asList(response.getBody());
}
从一个远程的url,获取了一个string数组。查看配置文件可知,那个远程的url是
product.service.url=http://localhost:8081/products
本机8081端口。
我们需要启动第二个服务。
导入第二个项目
再打开一个vscode窗口,导入项目service-springboot-rest
先来一通mvn clean package, mvn spring-boot:run
依然出现了找不到KeycloakBaseSpringBootConfiguration的错误,按照上面一个项目的方法,添加一个自定义类即可。
还出现了override错误,解决。
再查看配置文件中,对应的客户端名称,修改为我们新建的service-springboot
启动调试,正常启动。
回头再访问我们第一个项目的对应链接
错误七:
{"error":"unauthorized_client","error_description":"Client secret not provided in request"}
需要指定client secret,在keycloak管理端查得后添加到配置文件
keycloak.credentials.secret=6656872a-8844-41e9-8dc5-ad7a84f7e723
再次访问第一个应用,点击链接,出来啦!
至此,我们就调通了!
四、总结
Keycloak官方提供了springboot的客户端包,可供程序直接使用,无需编辑oauth2.0或者openid connection相关代码。
Keycloak在客户端程序认证时,可替换security config相关的类,即可实现集成。
在这个例子中,app应用和service应用属不同的客户端,隶属同一个realm,在客户通过app应用获得了token之后,无需再次认证,即可访问service应用。
Keycloak还支持app应用或者service向keycloak服务发送请求,修改创建资源、修改角色和资源的对应关系等,实现认证和授权的定义。下一篇再做demo。