
智能语音新革命:有道与Azure的API服务对决
API 密钥身份验证是保护 API 访问的最直接方法之一。它通过静态密钥控制访问权限,密钥通常通过请求头传递,并需要妥善保管。如果您正在使用 Keycloak 并希望结合 API 密钥验证机制,本指南将演示如何扩展 Keycloak 来实现这一功能。
这种方法非常适合微服务架构,因为不同服务可能需要不同的认证方式。
假设一个系统由两个服务组成:
用户注册后将获得一个 API 密钥,用于访问天气 REST API。系统还包含 Keycloak 身份验证服务器:
为实现该场景,需要扩展 Keycloak 模块,实现:
Keycloak 支持通过 SPI 接口或覆盖提供者扩展功能。我们通过实现 EventListenerProvider
捕获用户注册事件,并生成 API 密钥。
核心实现示例:
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)) {
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() {
// 清理资源
}
}
提供者工厂类实现:
public class RegisterEventListenerProviderFactory implements EventListenerProviderFactory {
public EventListenerProvider create(KeycloakSession session) {
return new RegisterEventListenerProvider(session);
}
public String getId() {
return "api-key-registration-generator";
}
}
创建端点用于验证 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 识别该端点,需要实现 RealmResourceProvider
和工厂类:
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";
}
}
在 META-INF/services
目录下添加映射文件:
org.keycloak.events.EventListenerProviderFactory
com.gwidgets.providers.RegisterEventListenerProviderFactory
org.keycloak.services.resource.RealmResourceProviderFactory
com.gwidgets.providers.ApiKeyResourceProviderFactory
Keycloak 允许通过 .jar
或 .ear
文件安装模块到 standalone/deployments
目录。示例项目结构:
api-key-ear/
api-key-module/
pom.xml
使用正确的 API 密钥访问 REST API:
curl -H "X-API-KEY: YPqIeqhbxUcOgDd6ld2jl9txfDrHxAPme89WLMuC8e0oaYXeA7" https://[CNAME]
响应示例:
{
"forecast": "今天天气凉爽"
}
错误密钥返回 401 未授权:
curl -v -H "X-API-KEY: invalid-key" https://[CNAME]
通过扩展 Keycloak,我们可以:
这种方式简单高效,非常适合希望保护 API 的微服务系统。