如何在Keycloak登录页面上实现Recaptcha

6

我想在keycloak登录页面中像注册页面一样实现recaptcha。我使用所需的工厂类来扩展了UsernamePasswordForm类。 我甚至也实现了所需的操作类。 但仍然无法在提供程序选项卡中看到以便添加到登录中。 我还修改了现有的login.ftl文件,但没有成功。

以下是我尝试过的内容。

我的验证器类:

public class MyLoginAuthenticator extends UsernamePasswordForm {

    @Override
    public void action(AuthenticationFlowContext context) {
        MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
        if (formData.containsKey("cancel")) {
            context.cancelLogin();
            return;
        }

        if (!validateForm(context, formData)) {
            return;
        }
        context.success();
    }

    protected boolean validateForm(AuthenticationFlowContext context, MultivaluedMap<String, String> formData) {
        return validateUserAndPassword(context, formData);
    }

    @Override
    public void authenticate(AuthenticationFlowContext context) {
        MultivaluedMap<String, String> formData = new MultivaluedMapImpl<>();
        String loginHint = context.getAuthenticationSession().getClientNote(OIDCLoginProtocol.LOGIN_HINT_PARAM);

        String rememberMeUsername = AuthenticationManager.getRememberMeUsername(context.getRealm(), context.getHttpRequest().getHttpHeaders());

        if (loginHint != null || rememberMeUsername != null) {
            if (loginHint != null) {
                formData.add(AuthenticationManager.FORM_USERNAME, loginHint);
            } else {
                formData.add(AuthenticationManager.FORM_USERNAME, rememberMeUsername);
                formData.add("rememberMe", "on");
            }
        }
        Response challengeResponse = challenge(context, formData);
        context.challenge(challengeResponse);
    }

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

    protected Response challenge(AuthenticationFlowContext context, MultivaluedMap<String, String> formData) {
        LoginFormsProvider forms = context.form();

        if (formData.size() > 0) forms.setFormData(formData);

        return forms.createLogin();
    }

    @Override
    public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
        // never called
        return true;
    }

    @Override
    public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
        // never called
    }

    @Override
    public void close() {

    }
}

我的工厂类:

public class LoginAuthenticatorFactory extends UsernamePasswordFormFactory {

    public static final String G_RECAPTCHA_RESPONSE = "g-recaptcha-response";
    public static final String RECAPTCHA_REFERENCE_CATEGORY = "login-recaptcha";
    public static final String SITE_KEY = "site.key";
    public static final String SITE_SECRET = "secret";
    public static final String PROVIDER_ID = "auth-username-password-form-recaptcha";

    public static final MyLoginAuthenticator SINGLETON = new MyLoginAuthenticator();

    @Override
    public String getDisplayType() {
        System.out.println("Ranveer Singh getDisplayType ");
        return "Login Recaptcha";
    }

    @Override
    public String getReferenceCategory() {
        return RECAPTCHA_REFERENCE_CATEGORY;
    }

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


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

    private static AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {AuthenticationExecutionModel.Requirement.REQUIRED, AuthenticationExecutionModel.Requirement.DISABLED};

    @Override
    public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
        return REQUIREMENT_CHOICES;
    }

    public void buildPage(FormContext context, LoginFormsProvider form) {
        System.out.println("Ranveer Singh buildPage");
        AuthenticatorConfigModel captchaConfig = context.getAuthenticatorConfig();
        if (captchaConfig == null || captchaConfig.getConfig() == null || captchaConfig.getConfig().get(SITE_KEY) == null || captchaConfig.getConfig().get(SITE_SECRET) == null) {
            form.addError(new FormMessage(null, Messages.RECAPTCHA_NOT_CONFIGURED));
            return;
        }
        String siteKey = captchaConfig.getConfig().get(SITE_KEY);
        form.setAttribute("recaptchaRequired", true);
        form.setAttribute("recaptchaSiteKey", siteKey);
        form.addScript("https://www.google.com/recaptcha/api.js");
    }

    public void validate(ValidationContext context) {
        System.out.println("Ranveer Singh validate");
        MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
        List<FormMessage> errors = new ArrayList<>();
        boolean success = false;
        context.getEvent().detail(Details.REGISTER_METHOD, "form");

        String captcha = formData.getFirst(G_RECAPTCHA_RESPONSE);
        if (!Validation.isBlank(captcha)) {
            AuthenticatorConfigModel captchaConfig = context.getAuthenticatorConfig();
            String secret = captchaConfig.getConfig().get(SITE_SECRET);

            success = validateRecaptcha(context, success, captcha, secret);
        }
        if (success) {
            context.success();
        } else {
            errors.add(new FormMessage(null, Messages.RECAPTCHA_FAILED));
            formData.remove(G_RECAPTCHA_RESPONSE);
            context.error(Errors.INVALID_REGISTRATION);
            context.validationError(formData, errors);
            return;


        }
    }

    protected boolean validateRecaptcha(ValidationContext context, boolean success, String captcha, String secret) {
        System.out.println("Ranveer Singh ");
        HttpClient httpClient = context.getSession().getProvider(HttpClientProvider.class).getHttpClient();
        HttpPost post = new HttpPost("https://www.google.com/recaptcha/api/siteverify");
        List<NameValuePair> formparams = new LinkedList<>();
        formparams.add(new BasicNameValuePair("secret", secret));
        formparams.add(new BasicNameValuePair("response", captcha));
        formparams.add(new BasicNameValuePair("remoteip", context.getConnection().getRemoteAddr()));
        try {
            UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
            post.setEntity(form);
            HttpResponse response = httpClient.execute(post);
            InputStream content = response.getEntity().getContent();
            try {
                Map json = JsonSerialization.readValue(content, Map.class);
                Object val = json.get("success");
                success = Boolean.TRUE.equals(val);
            } finally {
                content.close();
            }
        } catch (Exception e) {
            ServicesLogger.LOGGER.recaptchaFailed(e);
        }
        return success;
    }

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


    @Override
    public void close() {

    }

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

    }

    @Override
    public void postInit(KeycloakSessionFactory factory) {

    }

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

    @Override
    public String getHelpText() {
        return "Adds Google Recaptcha button.  Recaptchas verify that the entity that is registering is a human.  This can only be used on the internet and must be configured after you add it.";
    }

    private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();

    static {
        ProviderConfigProperty property;
        property = new ProviderConfigProperty();
        property.setName(SITE_KEY);
        property.setLabel("Recaptcha Site Key");
        property.setType(ProviderConfigProperty.STRING_TYPE);
        property.setHelpText("Google Recaptcha Site Key");
        configProperties.add(property);
        property = new ProviderConfigProperty();
        property.setName(SITE_SECRET);
        property.setLabel("Recaptcha Secret");
        property.setType(ProviderConfigProperty.STRING_TYPE);
        property.setHelpText("Google Recaptcha Secret");
        configProperties.add(property);

    }

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

}

我是否有遗漏的地方?有没有人能帮我在登录页面中添加reCAPTCHA?之前有人做过吗?能否分享一些示例代码,以便我可以查看并尝试更多。

提前感谢。

4个回答

10

根据@ghinea-alex的回复,在这个Github仓库中,我们制作了一个可用的keycloak jboss模块

我们制作了一个同时也是JBoss模块的Maven模块。

首先在RecaptchaUsernamePasswordForm中扩展了UsernamePasswordForm,并在RecpatchaUsernamePasswordFormFactory中扩展了UsernamePasswordFormFatory

RecaptchaUsernamePasswordForm:

<!-- language: java -->
import java.io.InputStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.ws.rs.core.MultivaluedMap;

import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.message.BasicNameValuePair;
import org.jboss.logging.Logger;
import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.authenticators.browser.UsernamePasswordForm;
import org.keycloak.connections.httpclient.HttpClientProvider;
import org.keycloak.events.Details;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.utils.FormMessage;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.validation.Validation;
import org.keycloak.util.JsonSerialization;

public class RecaptchaUsernamePasswordForm extends UsernamePasswordForm implements Authenticator{
    public static final String G_RECAPTCHA_RESPONSE = "g-recaptcha-response";
    public static final String RECAPTCHA_REFERENCE_CATEGORY = "recaptcha";
    public static final String SITE_KEY = "site.key";
    public static final String SITE_SECRET = "secret";
    private static final Logger logger = Logger.getLogger(RecaptchaUsernamePasswordFormFactory.class);

    @Override
    public void authenticate(AuthenticationFlowContext context) {
        context.getEvent().detail(Details.AUTH_METHOD, "auth_method");
        if (logger.isInfoEnabled()) {
            logger.info(
                    "validateRecaptcha(AuthenticationFlowContext, boolean, String, String) - Before the validation");
        }

        AuthenticatorConfigModel captchaConfig = context.getAuthenticatorConfig();
        LoginFormsProvider form = context.form();
        String userLanguageTag = context.getSession().getContext().resolveLocale(context.getUser()).toLanguageTag();

        if (captchaConfig == null || captchaConfig.getConfig() == null
                || captchaConfig.getConfig().get(SITE_KEY) == null
                || captchaConfig.getConfig().get(SITE_SECRET) == null) {
            form.addError(new FormMessage(null, Messages.RECAPTCHA_NOT_CONFIGURED));
            return;
        }
        String siteKey = captchaConfig.getConfig().get(SITE_KEY);
        form.setAttribute("recaptchaRequired", true);
        form.setAttribute("recaptchaSiteKey", siteKey);
        form.addScript("https://www.google.com/recaptcha/api.js?hl=" + userLanguageTag);

        super.authenticate(context);
    }

    @Override
    public void action(AuthenticationFlowContext context) {
        if (logger.isDebugEnabled()) {
            logger.debug("action(AuthenticationFlowContext) - start");
        }
        MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
        List<FormMessage> errors = new ArrayList<>();
        boolean success = false;
        context.getEvent().detail(Details.AUTH_METHOD, "auth_method");

        String captcha = formData.getFirst(G_RECAPTCHA_RESPONSE);
        if (!Validation.isBlank(captcha)) {
            AuthenticatorConfigModel captchaConfig = context.getAuthenticatorConfig();
            String secret = captchaConfig.getConfig().get(SITE_SECRET);

            success = validateRecaptcha(context, success, captcha, secret);
        }
        if (success) {
            super.action(context);
        } else {
            errors.add(new FormMessage(null, Messages.RECAPTCHA_FAILED));
            formData.remove(G_RECAPTCHA_RESPONSE);
            // context.error(Errors.INVALID_REGISTRATION);
            // context.validationError(formData, errors);
            // context.excludeOtherErrors();
            return;
        }

        if (logger.isDebugEnabled()) {
            logger.debug("action(AuthenticationFlowContext) - end");
        }
    }

    protected boolean validateRecaptcha(AuthenticationFlowContext context, boolean success, String captcha, String secret) {
        HttpClient httpClient = context.getSession().getProvider(HttpClientProvider.class).getHttpClient();
        HttpPost post = new HttpPost("https://www.google.com/recaptcha/api/siteverify");
        List<NameValuePair> formparams = new LinkedList<>();
        formparams.add(new BasicNameValuePair("secret", secret));
        formparams.add(new BasicNameValuePair("response", captcha));
        formparams.add(new BasicNameValuePair("remoteip", context.getConnection().getRemoteAddr()));
        try {
            UrlEncodedFormEntity form = new UrlEncodedFormEntity(formparams, "UTF-8");
            post.setEntity(form);
            HttpResponse response = httpClient.execute(post);
            InputStream content = response.getEntity().getContent();
            try {
                Map json = JsonSerialization.readValue(content, Map.class);
                Object val = json.get("success");
                success = Boolean.TRUE.equals(val);
            } finally {
                content.close();
            }
        } catch (Exception e) {
            ServicesLogger.LOGGER.recaptchaFailed(e);
        }
        return success;
    }    

}

RecaptchaUsernamePasswordFormFactory:

<!-- language: java -->
import java.util.ArrayList;
import java.util.List;

import org.keycloak.Config;
import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.AuthenticatorFactory;
import org.keycloak.authentication.DisplayTypeAuthenticatorFactory;
import org.keycloak.authentication.authenticators.browser.UsernamePasswordForm;
import org.keycloak.authentication.authenticators.browser.UsernamePasswordFormFactory;
import org.keycloak.authentication.authenticators.console.ConsoleUsernamePasswordAuthenticator;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.provider.ProviderConfigProperty;

public class RecaptchaUsernamePasswordFormFactory  implements AuthenticatorFactory, DisplayTypeAuthenticatorFactory {

    public static final String PROVIDER_ID = "recaptcha-u-p-form";
    public static final RecaptchaUsernamePasswordForm SINGLETON = new RecaptchaUsernamePasswordForm();

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

    @Override
    public Authenticator createDisplay(KeycloakSession session, String displayType) {
        if (displayType == null) return SINGLETON;
        if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
        return ConsoleUsernamePasswordAuthenticator.SINGLETON;
    }

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

    }

    @Override
    public void postInit(KeycloakSessionFactory factory) {

    }

    @Override
    public void close() {

    }

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

    @Override
    public String getReferenceCategory() {
        return UserCredentialModel.PASSWORD;
    }

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

    public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
            AuthenticationExecutionModel.Requirement.REQUIRED
    };

    @Override
    public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
        return REQUIREMENT_CHOICES;
    }

    @Override
    public String getDisplayType() {
        return "Recaptcha Username Password Form";
    }

    @Override
    public String getHelpText() {
        return "Validates a username and password from login form + google recaptcha";
    }

    private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = new ArrayList<ProviderConfigProperty>();

    static {
        ProviderConfigProperty property;
        property = new ProviderConfigProperty();
        property.setName(RecaptchaUsernamePasswordForm.SITE_KEY);
        property.setLabel("Recaptcha Site Key");
        property.setType(ProviderConfigProperty.STRING_TYPE);
        property.setHelpText("Google Recaptcha Site Key");
        CONFIG_PROPERTIES.add(property);
        property = new ProviderConfigProperty();
        property.setName(RecaptchaUsernamePasswordForm.SITE_SECRET);
        property.setLabel("Recaptcha Secret");
        property.setType(ProviderConfigProperty.STRING_TYPE);
        property.setHelpText("Google Recaptcha Secret");
        CONFIG_PROPERTIES.add(property);

    }

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

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

}

必须存在一个 META-INF 目录,其中必须存在一个 service\org.keycloak.authentication.AuthenticatorFactory 文件,其内容如下:

#
# Copyright 2016 Red Hat, Inc. and/or its affiliates
# and other contributors as indicated by the @author tags.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

org.keycloak.marjaa.providers.login.recaptcha.authenticator.RecaptchaUsernamePasswordFormFactory

所有独立可部署的 JBoss 模块也必须有一个 jboss-deployment-structure.xml 文件,用于描述这些模块的依赖关系:

<jboss-deployment-structure>
    <deployment>
        <dependencies>
            <module name="org.keycloak.keycloak-server-spi" export="true"/>
            <module name="org.keycloak.keycloak-server-spi-private" export="true"/>
            <module name="org.keycloak.keycloak-core" export="true"/>
            <module name="org.jboss.logging" export="true"/>
            <module name="org.keycloak.keycloak-services" export="true"/>
        </dependencies>
    </deployment>
</jboss-deployment-structure>

在您的主题中的login.ftl文件中,您应该在<form></form>标签内添加以下内容:

<#if recaptchaRequired??>
<div class="form-group">
    <div class="${properties.kcInputWrapperClass!}">
        <div class="g-recaptcha" data-size="compact" data-sitekey="${recaptchaSiteKey}">            
        </div>
    </div>
</div>
</#if>

最后,您应该启用外部来源 https://google.com,就像keycloak的Recaptcha文档中所述。

使用方法

在这个GitHub存储库中,我们创建了一个易于使用的MAVEN模块和使用手册。

只需克隆存储库。 您应该安装javamaven。 要进行构建,您需要运行mvn clean install。它将生成JAR文件target/recaptcha-login.jar。 为了使其在Keycloak中可访问,您应该将此JAR文件复制到keycloaks的standalone/deployment/目录中。 就这样。 如果您在docker环境中使用它,则应将其挂载到/opt/jboss/keycloak/standalone/deployment/recaptcha-login.jar中。 例如,在我的Docker组合文件中:

keycloak:
    image: jboss/keycloak:4.2.1.Final
    .
    .
    .
    volumes:
        - ./realm-config:/opt/jboss/keycloak/realm-config
        - ./my-theme/:/opt/jboss/keycloak/themes/my-theme/
        - ./kc-recaptcha-module/target/recaptcha-login.jar:/opt/jboss/keycloak/standalone/deployments/recaptcha-login.jar

在您的主题文件中,您应该在login.ftl模板文件中添加以下代码:

<#if recaptchaRequired??>
    <div class="form-group">
        <div class="${properties.kcInputWrapperClass!}">
            <div class="g-recaptcha" data-size="compact" data-sitekey="${recaptchaSiteKey}">            
            </div>
        </div>
    </div>
</#if>

你需要将它粘贴到登录<form></form>中,即登录模板(login.ftl)中。

最后,你应该像Keycloak的Recaptcha文档中所提到的那样启用外部来源https://google.com

要在GUI中启用它,请执行以下操作: 进入身份验证 然后转到身份验证

然后创建一个自己的流程,在这种情况下我的是BrowserWithRecaptcha,它应该像Keycloak的默认Browser一样,只是它有Recaptcha用户名密码表单而不是用户名密码表单输入图像描述

然后根据你的Google Recaptcha密钥配置你的Recaptacha用户名密码表单输入图像描述

然后在下一个选项卡中将你的浏览器流程绑定到BrowserWithRecaptcha

输入图像描述

同时,允许Realm Settings>Security Defences中的google.com访问也是必须的:

输入图像描述


构建并复制.jar文件后该怎么办? - Yiping
我已经为此创建了一个 GitHub 仓库,并在那里进行了说明。但我将编辑此回复并添加使用手册。 - hossein bakhtiari
可能存在一些问题。欢迎提交拉取请求。 - hossein bakhtiari
您能否提一下reCAPTCHA V2是可用的吗?因为我测试过V3不行。 - Yiping
2
我还创建了一个 GitHub 存储库来实现条件 reCAPTCHA。https://github.com/muhammedyalcin/keycloak-login-recaptcha - Muhammed Yalçın Kuru
显示剩余3条评论

3
你的实现有几个问题。如果你打算使用浏览器登录功能,最好为Recaptcha创建一个新的流程。此流程将使用UsernamePasswordForm和UsernamePasswordFormFactory,因此需要扩展这两个类。
除了扩展这两个类之外,还必须调用UsernamePasswordForm/Factory的构造函数以进行新实现。基本上,此解决方案提供了用户名+密码+Recaptcha。
在RecaptchaFormFactory中,您不需要buildPage,那是从注册验证码中来的,也不需要validateRecaptcha和validate(您将在RecaptchaForm的操作方法和身份验证中使用)。
工厂中唯一需要的是config和与UsernamePasswordFormFactory完全相同的方法。
在RecaptchaForm类中,您将拥有action方法、authenticate方法(均为Override)和validateRecaptcha方法。
首先调用的方法是具有以下结构的身份验证方法:
@Override
public void authenticate(AuthenticationFlowContext context) {
    MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();

    context.getEvent().detail(Details.AUTH_METHOD, "auth_method");
    if (logger.isInfoEnabled()) {
        logger.info(
                "validateRecaptcha(AuthenticationFlowContext, boolean, String, String) - inainte de validation");
    }

    AuthenticatorConfigModel captchaConfig = context.getAuthenticatorConfig();
    Map<String, String> econd = captchaConfig.getConfig();
    logger.debug("Am in config in context: {}", econd);
    context.form().addScript("https://www.google.com/recaptcha/api.js");
    context.form().setAttribute("recaptchaRequired", true);
    context.form().setAttribute("recaptchaSiteKey", econd.get(SITE_KEY));

    super.authenticate(context);
}

validateRecaptcha的结构与RegistrationCaptcha中的相同,而操作方法只需要将getEvent从Registration更改为AUTH_METHOD。

@Override
public void action(AuthenticationFlowContext context) {
    if (logger.isDebugEnabled()) {
        logger.debug("action(AuthenticationFlowContext) - start");
    }
    MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
    List<FormMessage> errors = new ArrayList<>();
    boolean success = false;
    context.getEvent().detail(Details.AUTH_METHOD, "auth_method");

    String captcha = formData.getFirst(G_RECAPTCHA_RESPONSE);
    if (!Validation.isBlank(captcha)) {
        AuthenticatorConfigModel captchaConfig = context.getAuthenticatorConfig();
        String secret = captchaConfig.getConfig().get(SITE_SECRET);

        success = validateRecaptcha(context, success, captcha, secret);
    }
    if (success) {
        super.action(context);
    } else {
        errors.add(new FormMessage(null, Messages.RECAPTCHA_FAILED));
        formData.remove(G_RECAPTCHA_RESPONSE);
        // context.error(Errors.INVALID_REGISTRATION);
        // context.validationError(formData, errors);
        // context.excludeOtherErrors();
        return;
    }

    if (logger.isDebugEnabled()) {
        logger.debug("action(AuthenticationFlowContext) - end");
    }
}

0

这段代码中的所有内容都很好,但在 .ftl 模板中,您应该添加标签名称,否则 keycloak 将无法将 captcha 变量中创建的值传递到 spi 中:

public static final String G_RECAPTCHA_RESPONSE = "g-recaptcha-response";

类似于这样:

<#if recaptchaRequired??>
<div class="form-group">
    <div class="${properties.kcInputWrapperClass!}">
        <div class="g-recaptcha" data-size="compact" name="g-recaptcha-response" data-sitekey="${recaptchaSiteKey}">            
        </div>
    </div>
</div>
</#if>


0

这里有另一种方法,可能不像其他优秀开发者建议的那样优雅,但对于那些寻找基于主题的解决方案的人可能会有所帮助。

重要提示:您需要先为keyCloak注册页面启用Google re-captcha。

  • 进入主题并在第46行后将以下代码行添加到themes --> base --> login.ftl

<script src="https://www.google.com/recaptcha/api.js" async defer></script>
 <script>
   function verifyCaptcha() {
     document.getElementById('kc-login').disabled = false;
   }
 </script>

 <div class="g-recaptcha" data-sitekey="${properties.recaptchaSiteKey!}" data-callback="verifyCaptcha"></div>

 <div id="kc-form-buttons" class="${properties.kcFormGroupClass!}">
 <input type="hidden" id="id-hidden-input" name="credentialId" <#if auth.selectedCredential?has_content>value="${auth.selectedCredential}"</#if>/>
  <input tabindex="4" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}" name="login" id="kc-login" disabled type="submit" value="${msg("doLogIn")}"/>
</div>

  • 前往主题并将以下代码行添加到 themes --> base --> theme.properties
recaptchaSiteKey=YourGoogleSiteKeyWhichYouShouldGetFromGoogleSite

刷新页面并清除缓存,您将得到可用的验证码。

它并不是非常强大,因为它只会在您或用户从Google re-captcha服务成功回调之前禁用登录按钮。

enter image description here


如何验证验证码? - Hett

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接