有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java如何在KeyClope登录页面上实现Recaptcha

我想在KeyClope登录页面(比如注册页面)中实现recaptcha。我用所需的工厂类扩展了UsernamePasswordForm类。我甚至还暗示了行动要求的课程。但我仍然看不到在提供者标签中添加登录。我修改了现有登录名。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吗。以前有人这样做过吗?有人可以分享示例代码,以便我可以查看和尝试更多

提前谢谢


共 (3) 个答案

  1. # 1 楼答案

    您的实现有几个问题。如果您打算使用浏览器登录功能,最好只为Recaptcha创建一个新流。该流将使用UsernamePasswordForm和UsernamePasswordFormFactory,因此需要扩展这两个类

    除了扩展这两个类之外,还必须调用UsernamePasswordForm/Factory中的构造函数来实现新的实现。基本上,这个解决方案为您提供用户名+密码+重述密码

    在RecaptchaFormFactory中,您不需要来自注册验证码的构建页面,也不需要ValidateCaptcha和validate(您将在action方法中使用它们,并从RecaptchaForm进行身份验证)

    工厂中只需要配置和UsernamePasswordFormFactory中完全相同的方法

    在RecaptchaForm类中,您将拥有action方法、authenticate方法和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相同的结构,action方法只需要将getEvent从Registration更改为AUTH_方法

    @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");
        }
    }
    
  2. # 2 楼答案

    基于@ghinea-alex的回复,我们在这个{a1}中做了一个工作{}

    我们制作了一个maven模块,它也是一个JBoss模块

    首先在{}中扩展{},并且在{}中扩展{}

    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,就像前面提到的keydeposes Recaptcha Documentation中那样

    如何使用

    在本github repo中,我们创建了一个易于使用的maven模块和使用手册

    复制回购协议就行了。 你应该已经安装了javamaven。 对于构建,您需要运行mvn clean install。它将产生jar target/recaptcha-login.jar。 为了让它可以在keydove中访问,您应该将这个jar复制到keydoves standalone/deployment/目录中。 就这样。 如果你在docker环境中使用它,你应该把它挂载在/opt/jboss/keycloak/standalone/deployment/recaptcha-login.jar。 例如,在我的docker compose文件中:

    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
    

    在你的主题文件中,你应该在你的登录中添加这段代码。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>
    

    您应该在登录模板(login.ftl)中的登录<form></form>中通过它

    最后,您应该启用外部源代码https://google.com,就像前面提到的keydeposes Recaptcha Documentation中那样

    要在GUI中启用它,请执行以下操作: 转到身份验证 Then Go to Authentication

    然后为自己创建一个流,在本例中,我的流是BrowserWithRecaptcha,它应该类似于KeyCloves的默认流Browser,只是它有Recaptcha Username Password Form而不是Username Password Formenter image description here

    然后根据您的google recaptcha键在以下位置配置Recaptacha Uusername Password Formenter image description here

    然后在下一个选项卡中将Browser Flow绑定到BrowserWithRecaptchaenter image description here

    同时,允许谷歌进入也是必须的。com访问Realm Settings>Security Defences

    enter image description here

  3. # 3 楼答案

    这里是另一种方法,可能不像其他伟大的开发人员所建议的那样优雅,但是它可能对那些正在寻找基于主题的解决方案的人有所帮助

    Important: You need to enable google re-captcha for the keyCloak registration page first.

    • 转到主题,并在第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
    

    刷新你的页面,清除缓存,你会得到有效的验证码

    它不是很强大,因为它只是禁用登录按钮,直到你/用户从谷歌重新验证码服务获得成功回复

    enter image description here