Keycloak 26中无用户时如何用自定义FreeMarker模板发送邮件?
Keycloak 26中无用户时渲染FreeMarker邮件模板并发送的解决方案
核心思路
绕过依赖UserModel的邮件模板提供商,直接使用FreeMarkerProvider手动处理模板,自行构造模板所需的变量数据,再通过EmailSenderProvider发送渲染后的邮件内容,完全无需创建持久化用户。
具体实现步骤
1. 获取所需的Keycloak Provider
在自定义EventListenerProvider中,通过构造函数注入或从KeycloakSession获取FreeMarkerProvider、EmailSenderProvider和当前RealmModel:
private final FreeMarkerProvider freeMarkerProvider; private final EmailSenderProvider emailSenderProvider; private final RealmModel realm; // 构造函数注入(ProviderFactory中通过session获取实例) public CustomEventListenerProvider(FreeMarkerProvider freeMarkerProvider, EmailSenderProvider emailSenderProvider, RealmModel realm) { this.freeMarkerProvider = freeMarkerProvider; this.emailSenderProvider = emailSenderProvider; this.realm = realm; }
2. 从事件中提取邮箱地址
从RESET_PASSWORD_ERROR事件的详情中获取目标邮箱:
String targetEmail = event.getDetails().get("email");
3. 构造FreeMarker模板数据模型
自行创建Map存储模板所需变量,无需依赖真实UserModel:
Map<String, Object> templateData = new HashMap<>(); templateData.put("realmName", realm.getName()); templateData.put("targetEmail", targetEmail); templateData.put("errorReason", event.getDetails().get("error")); // 可选:传递错误原因 // 可添加任意自定义变量,匹配模板中的占位符
4. 渲染FreeMarker模板
使用FreeMarkerProvider处理模板文件,生成邮件主题和正文:
// 模板路径对应自定义主题的email目录,如themes/your-custom-theme/email/ String subjectTemplatePath = "email/reset-password-error-subject.ftl"; String bodyTemplatePath = "email/reset-password-error-body.ftl"; String emailSubject = freeMarkerProvider.processTemplate(subjectTemplatePath, templateData); String emailBody = freeMarkerProvider.processTemplate(bodyTemplatePath, templateData);
5. 发送邮件
通过EmailSenderProvider构造邮件并发送,无需传入UserModel:
EmailSenderProvider.Email emailMessage = new EmailSenderProvider.Email(); emailMessage.setFrom(realm.getSmtpConfig().getFrom()); emailMessage.setTo(targetEmail); emailMessage.setSubject(emailSubject); emailMessage.setHtml(emailBody); // 若为HTML模板,设置此属性 emailMessage.setText(emailBody); // 纯文本 fallback emailSenderProvider.send(realm, emailMessage);
特殊场景:模板依赖UserModel属性的处理
如果模板中硬编码了user.xxx这类UserModel属性,可以创建一个临时的非持久化UserModel实现,仅返回需要的属性:
UserModel fakeUser = new UserModel() { @Override public String getEmail() { return targetEmail; } @Override public String getUsername() { return targetEmail.split("@")[0]; // 用邮箱前缀模拟用户名 } // 其他未用到的方法返回默认值或抛出UnsupportedOperationException @Override public String getFirstName() { return ""; } @Override public String getLastName() { return ""; } // 省略其余UserModel接口方法的实现 };
将该假用户加入模板数据模型即可:
templateData.put("user", fakeUser);
注意事项
- 确保自定义模板放在Keycloak主题目录的
email子目录下,且Realm已配置使用该主题。 - 在
EventListenerProviderFactory中正确从KeycloakSession获取所需Provider实例:
@Override public EventListenerProvider create(KeycloakSession session) { return new CustomEventListenerProvider( session.getProvider(FreeMarkerProvider.class), session.getProvider(EmailSenderProvider.class), session.getContext().getRealm() ); }
- 检查Realm的SMTP配置,确保邮件发送功能正常。
内容的提问来源于stack exchange,提问作者Tsvetkov Kirill
相关产品推荐
相关产品推荐

