You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Keycloak中添加自定义FTL页面并触发自定义Java代码?

实现Keycloak自定义FTL页面+Java逻辑全流程

我来一步步帮你搞定这个需求,Keycloak扩展自定义页面和后端逻辑有固定的套路,咱们分四个核心步骤来实现:

1. 搭建Keycloak扩展项目结构

首先你需要创建一个Maven项目(或者Gradle),用来打包自定义逻辑成JAR包部署到Keycloak里。项目依赖要包含Keycloak的核心SPI库,pom.xml里加这些依赖(版本和你的Keycloak版本保持一致):

<dependencies>
    <dependency>
        <groupId>org.keycloak</groupId>
        <artifactId>keycloak-core</artifactId>
        <version>22.0.5</version> <!-- 替换成你的Keycloak版本 -->
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.keycloak</groupId>
        <artifactId>keycloak-server-spi</artifactId>
        <version>22.0.5</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.keycloak</groupId>
        <artifactId>keycloak-server-spi-private</artifactId>
        <version>22.0.5</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.keycloak</groupId>
        <artifactId>keycloak-services</artifactId>
        <version>22.0.5</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

2. 编写自定义FTL页面

在项目的src/main/resources/theme/base/login/目录下创建special-number.ftl文件,内容就是带数字输入框的表单,提交地址指向Keycloak的登录动作端点,同时带上自定义action标识:

<!DOCTYPE html>
<html>
<head>
    <title>输入特殊数字</title>
    ${include('login/includes/header.ftl')}
</head>
<body class="login-pf">
    <div class="container">
        <div class="row">
            <div class="col-sm-10 col-sm-offset-1 col-md-8 col-md-offset-2 col-lg-6 col-lg-offset-3">
                <div class="card-pf">
                    <h1 class="card-pf-title">请输入特殊数字</h1>
                    <form id="special-number-form" action="${url.loginAction}" method="post">
                        ${csrfToken} <!-- 必须加这个防止CSRF攻击 -->
                        <div class="form-group">
                            <label for="specialNumber">特殊数字:</label>
                            <input type="number" id="specialNumber" name="specialNumber" class="form-control" required>
                        </div>
                        <div class="form-actions">
                            <input type="hidden" name="action" value="specialNumberSubmit">
                            <button type="submit" class="btn btn-primary btn-block">提交</button>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
    ${include('login/includes/footer.ftl')}
</body>
</html>

注意:要引入Keycloak自带的header和footer,保持页面风格和系统一致,同时必须加上${csrfToken}防止跨站请求伪造。

3. 编写Java后端逻辑(处理表单提交)

我们用Keycloak的Authenticator SPI来实现,这个SPI专门用来处理认证流程中的自定义步骤,完美匹配你的需求:

3.1 实现Authenticator接口

创建com/yourcompany/keycloak/authenticator/SpecialNumberAuthenticator.java:

package com.yourcompany.keycloak.authenticator;

import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.Authenticator;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;

import javax.ws.rs.core.Response;

public class SpecialNumberAuthenticator implements Authenticator {

    @Override
    public void authenticate(AuthenticationFlowContext context) {
        // 当进入这个认证步骤时,渲染自定义FTL页面
        Response response = context.form()
                .createForm("special-number.ftl");
        context.challenge(response);
    }

    @Override
    public void action(AuthenticationFlowContext context) {
        // 用户提交表单后,触发这个方法处理逻辑
        String specialNumber = context.getHttpRequest().getDecodedFormParameters().getFirst("specialNumber");
        
        // 这里写你的自定义业务逻辑,比如验证数字、调用外部API、更新用户属性等
        boolean isNumberValid = checkSpecialNumber(specialNumber);
        
        if (isNumberValid) {
            // 验证通过,继续后续认证流程
            context.success();
        } else {
            // 验证失败,返回错误信息并重新渲染页面
            context.form()
                    .setError("invalid-special-number", "请输入正确的特殊数字")
                    .createForm("special-number.ftl");
        }
    }

    // 示例:自定义验证逻辑
    private boolean checkSpecialNumber(String number) {
        return "666888".equals(number); // 替换成你的真实逻辑
    }

    // 以下是接口默认实现,按需修改
    @Override
    public boolean requiresUser() { return true; }
    @Override
    public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { return true; }
    @Override
    public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {}
    @Override
    public void close() {}
}

3.2 注册Authenticator工厂

创建com/yourcompany/keycloak/authenticator/SpecialNumberAuthenticatorFactory.java,用来把自定义Authenticator注册到Keycloak:

package com.yourcompany.keycloak.authenticator;

import org.keycloak.Config;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.AuthenticatorFactory;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;

import java.util.List;

public class SpecialNumberAuthenticatorFactory implements AuthenticatorFactory {

    public static final String ID = "special-number-authenticator";
    private static final SpecialNumberAuthenticator INSTANCE = new SpecialNumberAuthenticator();

    @Override
    public String getId() { return ID; }

    @Override
    public String getDisplayType() { return "特殊数字验证"; } // 后台显示的名称

    @Override
    public String getHelpText() { return "验证用户输入的特殊数字"; }

    @Override
    public String getReferenceCategory() { return "custom"; }

    @Override
    public boolean isConfigurable() { return false; }

    @Override
    public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
        return new AuthenticationExecutionModel.Requirement[] {
                AuthenticationExecutionModel.Requirement.REQUIRED,
                AuthenticationExecutionModel.Requirement.ALTERNATIVE,
                AuthenticationExecutionModel.Requirement.DISABLED
        };
    }

    @Override
    public boolean isUserSetupAllowed() { return false; }

    @Override
    public List<ProviderConfigProperty> getConfigProperties() { return null; }

    @Override
    public Authenticator create(KeycloakSession session) { return INSTANCE; }

    @Override
    public void init(Config.Scope config) {}

    @Override
    public void postInit(KeycloakSessionFactory factory) {}

    @Override
    public void close() {}
}

3.3 配置SPI服务注册

在src/main/resources/META-INF/services/目录下创建文件org.keycloak.authentication.AuthenticatorFactory,内容是你的工厂类全路径:

com.yourcompany.keycloak.authenticator.SpecialNumberAuthenticatorFactory

4. 部署并配置Keycloak触发页面

4.1 打包部署

用Maven打包项目:mvn clean package,把生成的JAR包放到Keycloak的standalone/deployments/目录下,Keycloak会自动热部署(或者重启Keycloak确保生效)。

4.2 配置认证流程触发页面

登录Keycloak后台,按以下步骤配置:

  • 进入你的Realm → 点击左侧菜单Authentication → Flows
  • 复制默认的Browser流程(避免修改默认流程出问题),命名为Custom Browser Flow
  • 展开复制后的流程,点击Add execution,选择刚才注册的特殊数字验证,设置为Required
  • 把这个新流程拖到合适的位置(比如放在Username Password Form之后)
  • 点击左侧菜单Authentication → Bindings,把Browser Flow设置为刚才创建的Custom Browser Flow

现在用户登录时,输入完用户名密码后,就会自动跳转到你的special-number.ftl页面,输入数字提交后就会触发你写的Java逻辑了!


内容的提问来源于stack exchange,提问作者Jack

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 12:47:55