如何在不创建账户的情况下,将自定义邮件模板发送到某个邮箱地址?

编程语言 2026-07-10

我在Keycloak 26中实现一个自定义的 EventListenerProvider
当发生 RESET_PASSWORD_ERROR 事件时,我想把通知邮件发送到输入的邮箱地址。
关键点是:

  • 邮箱地址可能不存在于Keycloak的数据库中,
  • 我不想在Keycloak中创建一个真实的用户,
  • 我想呈现并发送一个自定义的 FreeMarker 邮件模板。

我已经能够从事件详情中提取邮箱地址,并且可以用 DefaultEmailSenderProvider 发送一封普通邮件。然而,我想使用来自我的邮箱主题的自定义邮件模板。

我当前的尝试使用 FreeMarkerProvider.processTemplate(...),但我很困难,因为大多数示例和提供者似乎都期望一个UserModel,而我只有一个邮箱地址。

在数据库中没有用户时,如何在Keycloak事件监听器中正确渲染并发送自定义邮件模板?如果答案是 EmailTemplateProvider 需要一个用户,是否有一种干净的方式在不创建持久化用户的情况下使用 FreeMarker 模板?

解决方案

你应该使用 EmailTemplateProvider。这只支持以用户对象作为收件人,因此你必须从一个Keycloak域(realm)获取收件人。你本来不想添加一个用户,但由于这可以在任何域中进行,你可以将用户添加到一个域中,用户不可见。 不幸的是,EmailTemplateProvider 只公开使用内部User-Object集的 send() 方法。 例如,像下面这样的实现:

 private void sendLoginFailureEmail(Event event, UserModel user, String recipientRealmName, String recipientEmail, EventType eventType, RealmModel realm) throws EmailException {
        setUserTimeStamp(user, "lastLoginFailureTimestamp");
        int loginFailureCount = getUserLoginFailureCount(user);
        setUserLoginFailureCount(user, loginFailureCount + 1);
        if (loginFailureCount > 5) {
            RealmModel recipientRealm = keycloakSession.realms().getRealmByName(recipientRealmName);
            UserModel recipient = keycloakSession.users().getUserByEmail(recipientRealm, recipientEmail);
            Map<String, Object> attributes = new HashMap<>();
            attributes.put("event", new EventBean(event));                       

            EmailTemplateProvider emailTemplateProvider = keycloakSession.getProvider(EmailTemplateProvider.class);

            if (recipient != null) {
                emailTemplateProvider.setRealm(realm).setUser(recipient).send("loginErrorSubject", "login-error.ftl", attributes);
            }
        }

EventBean用于向模板提供事件信息。这个示例来自Keycloak的源代码:

public class EventBean {
    private final Event event;

    public EventBean(Event event) {
        this.event = event;
    }

    public Date getDate() {
        return new Date(event.getTime());
    }

    public String getEvent() {
        return event.getType().toString().toLowerCase().replace("_", " ");
    }

    public String getClient() {
        return event.getClientId();
    }


    public String getIpAddress() {
        return event.getIpAddress();
    }

    ...

如果使用现有用户绝对不是选项,你可以实现 UserModel。例如,如下所示:

public class EMailUser implements UserModel {
    private String email;
    private final String id;

    public EMailUser(String email) {
        this.email = email;
        this.id = UUID.randomUUID().toString();
    }

    @Override
    public String getId() {
        return this.id;
    }
    @Override
    public String getEmail() {
        return this.email;
    }

    @Override
    public void setEmail(String email) {
        this.email = email;
    }

// other methods
}


// use it
 EMailUser eMailUser = new EMailUser("[email protected]");
            emailTemplateProvider.setRealm(realm).setUser(eMailUser).send("Repeated login Failures for User " + user.getEmail(), "login-error.ftl", attributes);
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章