keycloak 微服务认证_【在家办公】keycloak认证客户端示例程序

如果你在测试keycloak的快速入门程序,会发现大部分代码是在3年前写的。多数情况是,下载下来以后跑不通的。这里以一个sso的例子,调试通quickstart的代码,并理解其中的原理。

一、   简要原理

  • Realm:Keycloak作为认证授权服务器,使用realm区分认证域,不同的reaml不能互相认证。

  • Client:在同一个realm下,需要创建client,一个client是一个授权策略、资源、范围及其对应关系的集合。一般需要指定回调uri,标明授权给的资源服务器。

  • 资源服务器:通常理解的应用服务,可以是web应用,可以是service。原先的做法是各应用建设认证和授权的模块。统一认证不仅可以做到少登录,还能依托认证授权服务器,实现统一的权限管理。

 0b1c8ff4c4cb97586f45a3a1f1c79edf.png

一般的,我们会在keycloak建一个realm,为对应的应用服务器新建client。同一个realm下的不同client,可以被相互认证。也就是说,A应用和B应用对应keycloak的不同client,用户在应用A登录后,无需登录即可访问B,只要有对应的资源权限。

以下就keycloak官方提供的示例代码,介绍一下。

二、   示例代码

官方提供的示例代码地址:

https://github.com/keycloak/keycloak-quickstarts

aefff7ed3f472c5893762457ea952c5d.png

这个示例项目跟随主项目更新频率特别高,第一次接触这个是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

905443e96a78b150fca41068bb0f34a5.png

选择fork,就会创建一个自己的仓库。此时可以clone到自己的电脑,开始改代码了。

  • Clone到本地

需要已经安装好了git 客户端,并且配置的个人账户和secret。这样就可以修改后提交代码 了。

克隆到本地

git clone git@github.com:voicyfei/keycloak-quickstarts.git 这个地址应该是fork的时候生成的地址,会有些许不同。

  • 服务器端设置

新建realm,新建客户端,客户端设置。

这个代码仓库提供了现成测试的导出文件,我们不需要一个一个设置了,直接导入即可。

87c1cc5095082ed41e342fb3f21e471c.png

选择文件,gitrepo/ quickstart-realm.json,直接导入即可

在新建的quickstart-realm下,选择客户端,添加客户端,分别导入/app-springboot和/service-springboot-rest两个目录下config文件中的json文件。此时创建好了两个客户端,分别是app-springboot和service-springboot。

如果导入文件失败,不要慌,在keycloak管理界面中,左侧菜单选择导入,再试一次,导入策略选择覆盖。

51fb23cb15b6289344dd1b1e10f715c9.png

  • IDE 准备

我使用的是vscode,微软开发、轻量级、支持插件的IDE,免费!

为了支持java开发,需要安装一些插件

4c6d56403002ea4ad2b0b3c970cb3cad.png

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

  • 导入第一个项目

Vscode  选择开发文件夹,选择clone的仓库中的app-springboot,打开

1e62dc9458260982e19157e2cfa9a94e.png

这里一般的耍法就是,直接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的登录页面,要求出入用户名密码。

3b863733f551e47ea80cd74990610234.png

输入角色为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。