四时宝库

程序员的知识宝库

Keycloak Servlet Filter Adapter使用

Keycloak Client Adapters简介

Keycloak client adapters are libraries that make it very easy to 
secure applications and services with Keycloak. We call them adapters 
rather than libraries as they provide a tight integration to the 
underlying platform and framework. This makes our adapters easy to use 
and they require less boilerplate code than what is typically required 
by a library

以上是Keycloak管网对Client Adapter的解释。简单理解就是,Keycloak为不同平台的应用提供了一些库,方便应用集成Keycloak服务。

对于使用OpenID Connector协议,Keycloak针对不同平台的应用,提供了如下adapter:

Java

  • JBoss EAP
  • WildFly
  • Fuse
  • Tomcat
  • Jetty 9
  • Servlet Filter
  • Spring Boot
  • Spring Security

JavaScript (client-side)

  • JavaScript

Node.js (server-side)

  • Node.js

C#

  • OWIN (community)

Python

  • oidc (generic)

Android

  • AppAuth (generic)

iOS

  • AppAuth (generic)

Apache HTTP Server

  • mod_auth_openidc

这里主要关注Java相关的adapter使用。首先介绍使用比较简单、通用的Servlet Filter Adapter,并通过源码分析Servlet Filter Adapter的原理、流程,后续再介绍如何使用Spring Boot Adapter以及Spring Security Adapter


使用Servlet Filter Adapter实现Token校验

背景简介

有一个应用,提供了REST API,但是需要提供合法的Token,Token校验通过了才能访问

环境准备

1.在Keycloak中创建测试Realm,名称为SpringBoot

2.创建Access Type为public类型的Client,client id为adapter-servlet-public,用于模拟前端应用获取Token,如下:

3.创建Acess Type为bearer-only类型的Client,client id为adapter-servlet-bearer,对应后端应用,如下:

从Installation Tab页面获取adapter-servlet-bearer配置信息,如下:

{
  "realm": "SpringBoot",
  "bearer-only": true,
  "auth-server-url": "http://localhost:8180/auth/",
  "ssl-required": "external",
  "resource": "adapter-servlet-bearer",
  "confidential-port": 0
}

4.在Keycloak管理控制台创建用户zhangsan,密码为123456

以上相关信息的创建可以参考前文Keycloak管理控制台操作

应用开发

1.创建SpringBoot应用

在父pom中指定SpringBoot版本及keycloak版本,如下:

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>

    <spring-boot.version>2.5.5</spring-boot.version>
    <keycloak.version>16.0.0</keycloak.version>
</properties>

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-dependencies</artifactId>
      <version>${spring-boot.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

2.创建keycloak-adapter-servlet子模块

创建一个子模块,并添加Keycloak Servlet Filter Client Adapter依赖和spring-boot-web依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
  <groupId>org.keycloak</groupId>
  <artifactId>keycloak-servlet-filter-adapter</artifactId>
  <version>${keycloak.version}</version>
</dependency>

添加application.yml配置,指定服务端口及日志级别

server:
  port: 8081

logging:
  level:
    org.keycloak: debug

3.创建REST 资源

package com.ywu.keycloak.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class ResourceController {
    
    @ResponseBody
    @RequestMapping("/protected/resource")
    public String protectedResource() {
        return "this is protected resource";
    }
    
    @ResponseBody
    @RequestMapping("/common/resource")
    public String commonResource() {
        return "this is common resource";
    }
}

其中,/protected/resource表示受保护的资源,需要验证后才能访问;/common/resource表示能随意访问

4.创建Keycloak配置

在resources目录下新建keycloak目录,并在keycloak目录下创建keycloak-bearer-only.json文件,内容为前文创建的bearer-only类型client的Installation信息,如下:

{
  "realm": "SpringBoot",
  "bearer-only": true,
  "auth-server-url": "http://localhost:8180/auth/",
  "ssl-required": "external",
  "resource": "adapter-servlet-bearer",
  "confidential-port": 0
}

5.配置Keycloak Servlet Filter

package com.ywu.keycloak.config;

import org.keycloak.adapters.servlet.KeycloakOIDCFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.File;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class FilterConfig {

    /**
     * 配置keycloak servlet adapter 过滤器
     * @return
     */
    @Bean
    public FilterRegistrationBean<KeycloakOIDCFilter> keycloakOIDCFilter() {
        FilterRegistrationBean<KeycloakOIDCFilter> keycloakOIDCFilter = new FilterRegistrationBean<>();

        keycloakOIDCFilter.setFilter(new KeycloakOIDCFilter());

        // 添加过滤器拦截的URL,即访问这些URL需要认证收授权
        keycloakOIDCFilter.addUrlPatterns("/protected/*");

        // 为过滤器设置keycloak json配置文件的路径
        Map<String, String> initParams = new HashMap<>();
        String configFileName = "keycloak-bearer-only.json";
        String keycloakJsonFilePath = getKeycloakJsonFilePath(configFileName);
        initParams.put(KeycloakOIDCFilter.CONFIG_FILE_PARAM, keycloakJsonFilePath);

        keycloakOIDCFilter.setInitParameters(initParams);

        return keycloakOIDCFilter;
    }

    /**
     * 获取resources目录下keycloak json配置文件的绝对路径
     *
     * @param fileName
     * @return
     */
    private String getKeycloakJsonFilePath(String fileName) {
        URL resource = FilterConfig.class.getResource(String.join(File.separator, "/keycloak", fileName));
        return resource.getPath();
    }
}

通过FilterRegistrationBean注册了一个KeycloakOIDCFilter,拦截/protected/*模式匹配的资源,指定了Filter使用的Keycloak配置为keycloak-bearer-only.json


到此,整个应用就开发完毕了

测试

启动测试服务,正常情况下,服务会监听在8081端口

直接访问

通过Post Man访问common resource

可以正常访问

使用Post Man访问受保护资源,如下:

返回401,表示未授权

获取Token

使用之前创建的public类型的client来获取Token,模拟前端请求Token,如下:

其中,获取Token的URL格式为http://{keycloak服务器域名}:{keycloak服务器端口}/auth/realms/{自定义realm名称}
/protocol/openid-connect/token

cURL格式的请求如下:

curl --location --request POST 'http://localhost:8180/auth/realms/SpringBoot/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Cookie: JSESSIONID=6FCFD67D3063CFE0A443BD7B161601A5.f64bbac2c4cd; JSESSIONID=6FCFD67D3063CFE0A443BD7B161601A5' \
--data-urlencode 'client_id=adapter-servlet-public' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'username=zhangsan' \
--data-urlencode 'password=123456'

正常情况下就能获取到Token,可以看出Token是JWT,并且默认有效期只要300秒

通过Token访问受保护资源

重新访问受保护资源,这次在请求Header中携带上Token,如下:

这次请求添加了请求头Authorization,值为Bearer {Token},其中Token部分即为上一步获取的JWT

cURL格式请求如下:

curl --location --request GET 'http://localhost:8081/protected/resource' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJyOGxFQUMyQmZSVUhUVDUtRGEyUUp3dFJBNFdMbnpOaHZsTjdMSVF1YXVZIn0.eyJleHAiOjE2NTAyOTA1ODUsImlhdCI6MTY1MDI5MDI4NSwianRpIjoiY2JiMmZjOTYtNGNhNi00YjI2LTkzMGItMmQ2MzczZmUyZTFhIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MTgwL2F1dGgvcmVhbG1zL1NwcmluZ0Jvb3QiLCJhdWQiOlsiYWRhcHRlci1zZXJ2bGV0IiwiYWNjb3VudCJdLCJzdWIiOiI5MjUzNmM2Ny00YzdlLTQ2YzctOWM0NS04YWRkYWVjYTRmYzQiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJhZGFwdGVyLXNlcnZsZXQtcHVibGljIiwic2Vzc2lvbl9zdGF0ZSI6IjQ0NmRmYTJmLTZkOGEtNDU0Mi05YmRhLWZmODU4ZjQxYzVhMSIsImFjciI6IjEiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJBRE1JTiIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWRhcHRlci1zZXJ2bGV0Ijp7InJvbGVzIjpbIlNZUyJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJwcm9maWxlIGVtYWlsIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ6aGFuZ3NhbiJ9.o7nnY_ZqCCJYy_bXQFgx99PKrX0ZH9k6v3JzHDFkpzsHL9nEyZu68EDJyts04DGb58gkHyMgcULu6YoN2KJ48jwmMwiM4yfXfSGR6lrKPfWxBpmMYeB1kxgU69eCwxNdVfIGC49Ilt6kwVTI7GZinwxJNfWZrOUpgXqpMvajd0MsB4PVCZS46cN5qh9AH-RAvbEkMKoa3mV_lTe7QZ5VqKk45ztS2gP5SUYNygMhgT6TXuHBQRTSMAcALEvyIjNmKNuPX4SNwReLk3DrmpA3gcHEQG8uyRTT5LZXchS71egr7QAXNIwBnrUg-pDPsa-Un-pJHbjJ1xJZmNU1H-Ihjw' \
--header 'Cookie: JSESSIONID=6FCFD67D3063CFE0A443BD7B161601A5'

可以看出,这次能正常访问受保护资源了

使用Servlet Filter Adapter实现登录授权认证

背景简介

有些应用需要用户授权登录才能访问,此时可以使用授权码流程,当用户登录到应用时,发现未登录,则重定向到Keycloak服务并登陆,再重定向回应用

环境准备

  1. 创建Access Type类型为confidential的Client,client id为adapter-servlet-credentials-1,添加Valid Redirect URIs为http://localhost:8081/protected/*,获取其Installation信息,如下:
{
  "realm": "SpringBoot",
  "auth-server-url": "http://localhost:8180/auth/",
  "ssl-required": "external",
  "resource": "adapter-servlet-credentials-1",
  "credentials": {
    "secret": "0e053b89-d87f-460a-92ec-adcd11ce6034"
  },
  "confidential-port": 0
}

应用修改

1.修改之前创建的子模块keycloak-adapter-servlet,在resources下的keycloak目录中创建
keycloak-confidential.json,内容如下:

{
  "realm": "SpringBoot",
  "auth-server-url": "http://localhost:8180/auth/",
  "ssl-required": "external",
  "resource": "adapter-servlet-credentials-1",
  "credentials": {
    "secret": "0e053b89-d87f-460a-92ec-adcd11ce6034"
  },
  "confidential-port": 0
}

2.修改FilterConfig类

将配置文件名称修改为
keycloak-confidential.json,如下

测试

重启应用后,在浏览器中输入访问地址:
http://localhost:8081/protected/resource,结果浏览器重定向到了Keycloak的登录页面,并提示用户登录

完整的地址如下:

http://localhost:8180/auth/realms/SpringBoot/protocol/openid-connect/auth?
response_type=code&client_id=adapter-servlet-credentials-1&
redirect_uri=http%3A%2F%2Flocalhost%3A8081%2Fprotected%2Fresource
&state=ade02c3f-4323-4888-b0cc-c5ad6dfd8a1f&login=true&scope=openid

其中,response_type值为code表示先获取code再用code换取Token的授权码流程,redirect_uri就是我们刚才访问的受保护资源。填写之前创建的用户名/密码为zhangsan/123456后,点击登陆,就能正常访问受保护资源了

示例代码地址

至此,就完成了使用Servlet Filter Adapter实现Token校验及授权码流程认证,代码地址如下:
https://github.com/ywu2014/keycloak-demo/tree/master/keycloak-adapter-servlet

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接