ネットレックスが開発しているあるサービスでは認証・認可にKeycloakを使っています。
ユーザ作成時に招待メールを送りたいという要件が出てきたのでKeycloakのプロバイダで対応してみました。
Keycloak
Keycloakはオープンソースのアイデンティ管理、アクセス管理のプロダクトです。
CNCF(Cloud Native Computing Foundation)のIncubatingプロジェクトとして開発が進められています。
プロバイダ
Keycloakにはプロバイダを実装できるService Provider Interface(SPI)が用意されていて独自のプロバイダを実装することで機能を拡張することができます。
今回作ったプロバイダは、ユーザ作成時のイベントをトリガとして作成されたユーザにメールを送信するものになります。
プロバイダにはProviderFactoryおよびProviderインタフェースの実装とサービス構成ファイルの作成が必要です。
ProviderFactory
ProviderFactoryでは以下のように、プロバイダIDと生成したProviderインスタンスを返却するように実装します。
AutoServiceアノテーションでサービス構成ファイルを作成します。
import org.keycloak.Config;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventListenerProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import com.google.auto.service.AutoService;
@AutoService(EventListenerProviderFactory.class)
public class RegisterListenerFactory implements EventListenerProviderFactory {
private static final String ID = "register-listener";
@Override
public EventListenerProvider create(KeycloakSession session) {
return new RegisterListener(session);
}
@Override
public void init(Config.Scope config) {
// NOOP
}
@Override
public void postInit(KeycloakSessionFactory factory) {
// NOOP
}
@Override
public void close() {
// NOOP
}
@Override
public String getId() {
return ID;
}
}
Provider
ProviderではonEventのイベントハンドラでリソースタイプ、オペレーションタイプを見てユーザ作成時にメールを送るように実装します。
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.keycloak.email.EmailException;
import org.keycloak.email.EmailTemplateProvider;
import org.keycloak.events.Event;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import lombok.RequiredArgsConstructor;
import lombok.extern.jbosslog.JBossLog;
@RequiredArgsConstructor
@JBossLog
public class RegisterListener implements EventListenerProvider {
private final KeycloakSession session;
@Override
public void onEvent(Event event) {
// NOOP
}
@Override
public void onEvent(AdminEvent event, boolean includeRepresentation) {
if (event.getResourceType() == ResourceType.USER && event.getOperationType() == OperationType.CREATE) {
trySendWelcomeEmail(event.getRealmId(), event.getResourcePath().substring("users/".length()));
}
}
private void trySendWelcomeEmail(String realmId, String userId) {
RealmModel realm = session.realms().getRealm(realmId);
UserModel user = session.users().getUserById(realm, userId);
if (user.getEmail() == null) {
log.warnf("Could not send welcome email due to missing email. realm=%s user=%s", realm.getId(), user.getUsername());
return;
}
Map<String, Object> mailBodyAttributes = new HashMap<>();
mailBodyAttributes.put("username", user.getUsername());
List<Object> subjectParams = List.of(user.getUsername());
try {
EmailTemplateProvider emailProvider = session.getProvider(EmailTemplateProvider.class);
emailProvider.setRealm(realm);
emailProvider.setUser(user);
emailProvider.send("welcomeEmailSubject", subjectParams, "welcome-email.ftl", mailBodyAttributes);
} catch (EmailException eex) {
log.errorf(eex, "Failed to send welcome email. realm=%s user=%s", realm.getId(), user.getUsername());
eex.printStackTrace();
}
}
@Override
public void close() {
// NOOP
}
}
テンプレートなど
メールの件名や内容にはテーマのテンプレートを使うことができます。
{
"themes": [
{
"name": "sample",
"types": [
"email"
]
}
]
}
parent=base
<html>
<body>
${kcSanitize(msg("welcomeEmailBody", username))?no_esc}
</body>
</html>
<#ftl output_format="plainText">
${msg("welcomeEmailBody", username)}
# encoding: UTF-8
welcomeEmailBody={0} のアカウントが作成されました。
welcomeEmailSubject={0} アカウント作成
デプロイ
ビルドして生成されたjarファイルをKeycloakサーバの$KEYCLOAK_HOME/providersディレクトリに配置しKeycloakを再起動します。
Keycloakの管理コンソールに独自プロバイダのプロバイダIDが表示されるので選択して設定を保存します。
テーマ設定
ここまでの設定だけではメールテンプレートがみつからないといったエラーになります。
Keycloakの管理コンソールでjarに含まれているテーマを指定する必要があります。
以上でテンプレートの内容でメール送信できるようになります。
認証・認可というセキュリティ的にも重要な機能を自前で開発する時代は終わったと思っています。
そのような中オープンでスタンダードなだけでなくカスタマイズ可能なKeycloakは有用な選択肢の一つになっています。