- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我想在 keycloak 登录页面(如注册页面)中实现 recaptcha。我用所需的工厂类扩展了 UsernamePasswordForm 类。我什至还实现了 Action 所需的类(class)。但我仍然看不到在提供程序选项卡中添加登录。我也修改了现有的 login.ftl,但没有运气。
下面是我尝试过的。
我的身份 validator 类:
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。以前有人这样做过吗?有人可以分享示例代码,以便我可以查看并尝试更多。
最佳答案
基于@ghinea-alex 的回复,我们做了一个有效的keycloak jboss module
在这个Github Repository .
我们制作了一个 Maven 模块,它也是一个 JBoss 模块。
第一次扩展UsernamePasswordForm
在 RecaptchaUsernamePasswordForm
并且还扩展了UsernamePasswordFormFatory
在 RecpatchaUsernamePasswordFormFactory
.
Recaptcha用户名密码表格:
<!-- 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;
}
}
<!-- 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-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
就像 key 斗篷里的样子
Recaptcha Documentation提及。
java
和
maven
安装。
mvn clean install
.它将产生 jar
target/recaptcha-login.jar
.
standalone/deployment/
目录。
/opt/jboss/keycloak/standalone/deployment/recaptcha-login.jar
.
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
<#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
)
https://google.com
就像 key 斗篷里的样子
Recaptcha Documentation提及。
BrowserWithRecaptcha
, 它应该像 keycloaks 的默认
Browser
除了它有
Recaptcha Username Password Form
而不是
Username Password Form
:
Recaptacha Uusername Password Form
根据你的谷歌recaptcha键:
Browser Flow
至
BrowserWithRecaptcha
在下一个选项卡中:
Realm Settings
>
Security Defences
:
关于java - 如何在 keycloak 登录页面上实现 Recaptcha,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46974448/
我已将 Keycloak 设置为 SAML 代理,身份验证由当局提供的外部 IdP 完成。使用此 IdP 登录的用户都被接受,我们需要从 Keycloak 获得一个 OAuth token 来访问我们
我在 master 有一个服务帐号领域。来自 admin-cli我要分配服务帐号master-realm-client管理员角色。我怎样才能做到这一点。 kcadm.sh add-roles -r m
在 Keycloak 中,是否可以对同一领域中的所有客户端仅进行一次登录?我已经以这种方式配置了服务器(从管理控制台): - 创建一个新领域(我们称之为 MyRealm); - 创建两个不同的客户端(
我们的团队正在开发一个集成到 Keycloak 中的项目。我们创建了一个自定义领域,例如 ProjectX ,并使其上的自定义主题能够应用于 Keycloak 的登录页面。 由于我们的主题应用于领域
Keycloak 是一个很棒的工具,但它缺乏适当的文档。 所以我们有 Realm.roles、Client.roles 和 User.roles 使用特定客户端访问应用程序时,这 3 者如何协同工作?
我们的团队正在开发一个集成到 Keycloak 中的项目。我们创建了一个自定义领域,例如 ProjectX ,并使其上的自定义主题能够应用于 Keycloak 的登录页面。 由于我们的主题应用于领域
有没有办法让所有用户在 keycloak 中创建自定义用户属性(如电话号码)? 最佳答案 您可以创建一个组并向其添加一个属性。 然后将该组设置为默认组。这样,所有用户都可以使用设置为组的属性 关于ke
我还尝试了 Web 来源和有效重定向 URI 的所有不同组合 我通过 keycloak 登录,它不断地在我的本地主机应用程序和这个 url 之间来回重定向我:http://localhost:4200
在 Keycloak's documentation ,据说为了部署提供程序,我可以 copy your provider jar to the Keycloak deploy/ directory,
目前,我使用此端点一次将用户添加到一个组: PUT /{realm}/users/{id}/groups/{groupId} 在我的用例中,批量执行影响是有益的,到目前为止我还没有找到这样做的记录方式
目前,我使用此端点一次将用户添加到一个组: PUT /{realm}/users/{id}/groups/{groupId} 在我的用例中,批量执行影响是有益的,到目前为止我还没有找到这样做的记录方式
我一直在尝试在 docker-compose 文件中使用 jboss/keycloak-mysql 来建立 Keycloak 服务器和 mysql 数据库。我在本地部署期间遇到一个问题,告诉我 WEB
我正在使用 Tomcat 适配器运行 Keycloak。但是,当我尝试获取 KeycloakPrincipal 时,它出错了; java.lang.ClassCastException: org.ke
我正在使用 keycloak 4.5.0 v 并创建了一个领域。我已经设置了登录以启用忘记用户名和验证电子邮件。在我输入的电子邮件选项卡中 host - smtp.gmail.com smtp po
我想让我的客户端应用程序从 keycloak 访问用户信息。因此,我在 keycloak 中创建了另一个领域 (myrealm1),并在该领域内创建了一个新客户端 (myclient1)。 key 斗
正如我在此链接中看到的 https://www.keycloak.org/docs/latest/authorization_services/#_overview ,客户端中应该有一个授权选项卡,如
这些 keycloak 端点有什么用? issuer: "http://localhost:8180/auth/realms/dev", authorization_endpoint: "http:/
我们需要将数百个用户从 csv 文件导入 Keycloak。我还没有找到任何现成的导入功能来做到这一点。 有没有人做过任何导入程序或至少有一些框架来构建? REST API 可能是唯一的方法 - 或者
我已经使用 LDAP 用户联盟配置了 Keycloak。当用户想要登录应用程序时,他会被重定向到 Keycloak 登录页面,输入 uid/pwd 并使用 LDAP 绑定(bind)进行身份验证。 这
有没有人在 Keycloak 中使用过 SCIM?如果是这样,你能指出我的文档吗?我用谷歌搜索过,它似乎不是受支持的配置。 最佳答案 不幸的是,Keycloak 尚不支持 SCIM。他们的 Jira
我是一名优秀的程序员,十分优秀!