在Keycloak中添加API密钥认证 - Elestio博客

作者:API传播员 · 2025-09-13 · 阅读时间:6分钟
本文详细介绍了如何在Keycloak中添加API密钥认证机制,特别适合微服务架构。通过实现EventListenerProvider和创建API密钥验证端点,扩展Keycloak功能,为REST API提供额外的安全层。

背景

API密钥身份验证是确保访问资源和API最直接的方法之一。这种方法通过提供一个静态密钥来实现,该密钥需要妥善保管,并用于访问受保护的API,通常通过特定的请求头或身份验证头传递。如果您正在使用 Keycloak 并希望结合 API 密钥验证机制,请继续阅读。本指南将演示如何通过添加简单的 API 密钥认证机制来扩展 Keycloak。这种方法特别适合微服务架构,因为不同服务可能需要不同的认证方式。


设计

假设一个由两个服务组成的系统:一个是提供仪表板页面的 Spring Boot 应用程序,另一个是提供天气预报数据的无状态 Node.js REST API。这两个服务通过不同的 URI 独立访问,但需要通过仪表板注册后才能使用。用户注册后会获得一个 API 密钥,该密钥用于访问天气 REST API。这种场景在 API 即服务(API-as-a-Service)应用中非常常见。

此外,该系统还包含一个 Keycloak 身份验证服务器,用于身份验证和授权。为了保护仪表板服务,我们采用了 Keycloak 的单点登录(SSO)机制。而为了保护 REST API 服务,我们引入了 API 密钥认证机制:在用户注册时生成并存储一个随机密钥,并提供一个端点用于验证 API 密钥的有效性。

为实现上述目标,我们扩展了 Keycloak 模块,新增以下功能:

  1. 在用户注册过程中生成随机密钥并存储为用户属性。
  2. 提供一个端点,用于验证 API 密钥的有效性。

实施

密钥生成

Keycloak 的可扩展性允许通过 SPI 接口或覆盖提供者来轻松实现新功能。我们首先从 API 密钥生成开始,这需要捕获用户注册事件以生成密钥。通过实现 EventListenerProvider,可以捕获 Keycloak 的内部事件并执行相应操作。

以下是实现代码的核心部分:

public class RegisterEventListenerProvider implements EventListenerProvider {
    private KeycloakSession session;
    private RealmProvider model;
    private RandomString randomString;
    private EntityManager entityManager;

    public RegisterEventListenerProvider(KeycloakSession session) {
        this.session = session;
        this.model = session.realms();
        this.entityManager = session.getProvider(JpaConnectionProvider.class).getEntityManager();
        this.randomString = new RandomString(50);
    }    public void onEvent(Event event) {
        if (event.getType().equals(EventType.REGISTER)) {
            RealmModel realmModel = model.getRealm(event.getRealmId());
            String userId = event.getUserId();
            addApiKeyAttribute(userId);
        }
    }    public void addApiKeyAttribute(String userId) {
        String apiKey = randomString.nextString();
        UserEntity userEntity = entityManager.find(UserEntity.class, userId);
        UserAttributeEntity attributeEntity = new UserAttributeEntity();
        attributeEntity.setName("apiKey");
        attributeEntity.setValue(apiKey);
        attributeEntity.setId(UUID.randomUUID().toString());
        entityManager.persist(attributeEntity);
    }    public void close() {
        // 清理资源
    }
}

在 Keycloak 中,每个提供者都需要一个工厂类来创建实例。以下是 EventListenerProviderFactory 的实现:

public class RegisterEventListenerProviderFactory implements EventListenerProviderFactory {
    public EventListenerProvider create(KeycloakSession session) {
        return new RegisterEventListenerProvider(session);
    }

    public String getId() {
        return "api-key-registration-generator";
    }
}

API 密钥验证端点

接下来,我们创建一个端点,用于验证 API 密钥是否有效:

public class ApiKeyResource {
    private KeycloakSession session;

    public ApiKeyResource(KeycloakSession session) {
        this.session = session;
    }    @GET
    @Produces("application/json")
    public Response checkApiKey(@QueryParam("apiKey") String apiKey) {
        List result = session.userStorageManager()
            .searchForUserByUserAttribute("apiKey", apiKey, session.realms().getRealm("example"));
        return result.isEmpty() ? Response.status(401).build() : Response.ok().build();
    }
}

为了让 Keycloak 识别我们的端点,我们需要实现 RealmResourceProviderRealmResourceProviderFactory

public class ApiKeyResourceProvider implements RealmResourceProvider {
    private KeycloakSession session;

    public ApiKeyResourceProvider(KeycloakSession session) {
        this.session = session;
    }    public Object getResource() {
        return new ApiKeyResource(session);
    }    public void close() {
        // 清理资源
    }
}public class ApiKeyResourceProviderFactory implements RealmResourceProviderFactory {
    public RealmResourceProvider create(KeycloakSession session) {
        return new ApiKeyResourceProvider(session);
    }    public String getId() {
        return "api-key-resource-provider";
    }
}

提供者配置

为了让 Keycloak 知道我们新增的提供者,需要在 META-INF/services 目录下创建以下映射文件:

  • 文件名:org.keycloak.events.EventListenerProviderFactory

    com.gwidgets.providers.RegisterEventListenerProviderFactory
  • 文件名:org.keycloak.services.resource.RealmResourceProviderFactory

    com.gwidgets.providers.ApiKeyResourceProviderFactory

模块打包

Keycloak 是一个运行在 Wildfly 上的独立 Web 应用程序,允许通过 .ear.jar 文件将模块安装到 standalone/deployments 目录下。以下是项目结构示例:

api-key-ear/
api-key-module/
pom.xml

更多详细信息请参考 Keycloak 官方文档。


测试 API 密钥

使用正确的 API 密钥调用 REST API 服务:

curl -H "X-API-KEY: YPqIeqhbxUcOgDd6ld2jl9txfDrHxAPme89WLMuC8e0oaYXeA7" https://[CNAME]

响应示例:

{
  "forecast": "今天天气凉爽"
}

使用错误的 API 密钥会返回 401 未授权响应:

curl -v -H "X-API-KEY: invalid-key" https://[CNAME]

总结

通过扩展 Keycloak,我们可以轻松实现 API 密钥认证机制,为微服务架构中的 REST API 提供额外的安全层。这种方法不仅灵活,还能与 Keycloak 的现有功能无缝集成。如果您正在寻找一种简单而高效的方式来保护 API,不妨尝试本文介绍的实现方法。

原文链接: https://blog.elest.io/adding-api-key-authentication-in-keycloak/