我有这个自定义注册表单,它位于我的流程中的正常注册表单之前。
package org.keycloak.x.authenticator;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.authentication.*;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.*;
import org.keycloak.provider.ProviderConfigProperty;
import java.util.ArrayList;
import java.util.List;
public class HashRegistrationAuthenticatorFactory implements FormActionFactory, FormAction {
private static final Logger logger = Logger.getLogger(HashRegistrationAuthenticatorFactory.class);
public static final String PROVIDER_ID = "hash-registration-authenticator";
private static final HashRegistrationAuthenticator SINGLETON = new HashRegistrationAuthenticator();
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public int order() {
return FormActionFactory.super.order();
}
@Override
public List<ProviderConfigProperty> getConfigMetadata() {
return FormActionFactory.super.getConfigMetadata();
}
@Override
public FormAction create(KeycloakSession session) {
return SINGLETON;
}
@Override
public boolean isConfigurable() {
return true;
}
private static AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
AuthenticationExecutionModel.Requirement.REQUIRED,
AuthenticationExecutionModel.Requirement.ALTERNATIVE,
AuthenticationExecutionModel.Requirement.DISABLED
};
@Override
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
return REQUIREMENT_CHOICES;
}
@Override
public boolean isUserSetupAllowed() {
return true;
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}
@Override
public <C> C getConfig() {
return FormActionFactory.super.getConfig();
}
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
static {
ProviderConfigProperty property;
property = new ProviderConfigProperty();
property.setName("cookie.max.age");
property.setLabel("Cookie Max Age");
property.setType(ProviderConfigProperty.STRING_TYPE);
property.setHelpText("Max age in seconds of the HASH_REGISTER_COOKIE.");
configProperties.add(property);
}
@Override
public String getHelpText() {
return "Validates a hash from an email link and redirects the user to a form to set his password and full name for the new account.";
}
@Override
public String getDisplayType() {
return "Hash Authentication Register";
}
@Override
public String getReferenceCategory() {
return "Hash Authentication Register";
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public void buildPage(FormContext formContext, LoginFormsProvider loginFormsProvider) {
logger.info("Creating email_hash_registration form...");
String hash = formContext.getHttpRequest().getUri().getQueryParameters().getFirst("hash");
String email = formContext.getHttpRequest().getUri().getQueryParameters().getFirst("email");
String ruc = formContext.getHttpRequest().getUri().getQueryParameters().getFirst("ruc");
String issuer = formContext.getHttpRequest().getUri().getQueryParameters().getFirst("issuer");
formContext.getAuthenticationSession().setAuthNote("hash", hash);
formContext.getAuthenticationSession().setAuthNote("email", email);
formContext.getAuthenticationSession().setAuthNote("ruc", ruc);
formContext.getAuthenticationSession().setAuthNote("issuer", issuer);
loginFormsProvider.createForm("email_hash_registration.ftl");
logger.info("email_hash_registration form created.");
}
@Override
public void validate(ValidationContext validationContext) {
}
@Override
public void success(FormContext formContext) {
}
@Override
public boolean requiresUser() {
return false;
}
@Override
public boolean configuredFor(KeycloakSession keycloakSession, RealmModel realmModel, UserModel userModel) {
return false;
}
@Override
public void setRequiredActions(KeycloakSession keycloakSession, RealmModel realmModel, UserModel userModel) {
}
}
package org.keycloak.x.authenticator;
import org.jboss.logging.Logger;
import org.keycloak.authentication.FormContext;
import org.keycloak.authentication.ValidationContext;
import org.keycloak.authentication.forms.RegistrationUserCreation;
import org.keycloak.credential.PasswordCredentialProvider;
import org.keycloak.events.Errors;
import org.keycloak.events.EventType;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.FormMessage;
import org.keycloak.services.validation.Validation;
import org.keycloak.sessions.CommonClientSessionModel;
import org.keycloak.userprofile.UserProfile;
import org.keycloak.userprofile.UserProfileContext;
import org.keycloak.userprofile.UserProfileProvider;
import org.keycloak.userprofile.ValidationException;
import org.keycloak.connections.jpa.JpaConnectionProvider;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import javax.ws.rs.core.MultivaluedMap;
import java.security.NoSuchAlgorithmException;
import java.util.List;
public class HashRegistrationAuthenticator extends RegistrationUserCreation {
private static final Logger logger = Logger.getLogger(HashRegistrationAuthenticator.class);
@Override
public void buildPage(FormContext context, LoginFormsProvider form) {
logger.info("Starting HashRegistrationAuthenticator.buildPage()");
logger.info("Request URI " + context.getHttpRequest().getUri().toString());
// Get the parameters you need for your condition
String hash = context.getHttpRequest().getUri().getQueryParameters().getFirst("hash");
String email = context.getHttpRequest().getUri().getQueryParameters().getFirst("email");
String ruc = context.getHttpRequest().getUri().getQueryParameters().getFirst("ruc");
String issuer = context.getHttpRequest().getUri().getQueryParameters().getFirst("issuer");
logger.info(ruc + " " + issuer + " " + email + " " + hash);
// Set your condition
boolean condition = hash != null && !hash.isEmpty() && email != null && !email.isEmpty() && ruc != null && !ruc.isEmpty() && issuer != null && !issuer.isEmpty();
logger.info(condition);
// If the condition is met, mark the current execution as attempted
if (condition) {
logger.info("Bypassing HashRegistrationAuthenticator.buildPage()");
context.getAuthenticationSession().setExecutionStatus(context.getExecution().getAuthenticator(), CommonClientSessionModel.ExecutionStatus.ATTEMPTED);
}
}
@Override
public void validate(ValidationContext context) {
logger.info("Validating user with hash.");
String email = context.getAuthenticationSession().getAuthNote("email");
String hash = context.getAuthenticationSession().getAuthNote("hash");
String issuer =context.getAuthenticationSession().getAuthNote("issuer");
// Check if the code has expired (assuming a timestamp is involved in code generation)
long codeTimestamp = System.currentTimeMillis(); // Replace with the actual timestamp attribute used during code generation
long currentTimestamp = System.currentTimeMillis();
long codeValidityDuration = 36000000; // Set the validity duration in milliseconds
if (currentTimestamp - codeTimestamp > codeValidityDuration) {
logger.error("Code has expired");
context.error("expired_code");
return;
}
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
formData.add("email", email);
formData.add("username", email);
KeycloakSession session = context.getSession();
UserProfileProvider profileProvider = session.getProvider(UserProfileProvider.class);
UserProfile profile = profileProvider.create(UserProfileContext.REGISTRATION_USER_CREATION, formData);
try {
profile.validate();
} catch (ValidationException var12) {
List<FormMessage> errors = Validation.getFormErrorsFromValidation(var12.getErrors());
if (var12.hasError(new String[]{"emailExistsMessage"})) {
context.error("email_in_use");
} else if (var12.hasError(new String[]{"missingEmailMessage", "missingUsernameMessage", "invalidEmailMessage"})) {
context.error("invalid_registration");
} else if (var12.hasError(new String[]{"usernameExistsMessage"})) {
context.error("username_in_use");
}
context.validationError(formData, errors);
return;
}
try {
if (!Hash.generateHash(issuer, getClientSecret(issuer, session) ,email).equals(hash)) {
logger.error("hash inválido");
context.error(Errors.INVALID_USER_CREDENTIALS);
}
} catch (NoSuchAlgorithmException e) {
logger.error("Error al intentar validar el hash");
e.printStackTrace();
context.error(Errors.INVALID_USER_CREDENTIALS);
}
context.success();
logger.info("User hash validation complete.");
}
public String getClientSecret(String issuer, KeycloakSession session) {
logger.info("Buscando client_secret del issuer " + issuer);
EntityManager em = session.getProvider(JpaConnectionProvider.class).getEntityManager();
Query query = em.createNativeQuery("SELECT client_secret FROM public.client_secrets WHERE issuer = :issuer");
query.setParameter("issuer", issuer);
try {
logger.info("clients_secret encontrado");
return (String) query.getSingleResult();
} catch (NoResultException e) {
logger.error("clients_secret no encontrado");
return null;
}
}
@Override
public void success(FormContext context) {
logger.info("Creating new user valdiated with hash.");
String email = context.getAuthenticationSession().getAuthNote("email");
String ruc = context.getAuthenticationSession().getAuthNote("ruc");
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
String password = formData.getFirst("password");
String isValidId = "true";
KeycloakSession session = context.getSession();
UserProfileProvider profileProvider = session.getProvider(UserProfileProvider.class);
formData.add("email", email);
formData.add("username", email);
UserProfile profile = profileProvider.create(UserProfileContext.REGISTRATION_USER_CREATION, formData);
UserModel user = profile.create();
user.setSingleAttribute("ruc", ruc);
user.setSingleAttribute("isValidId", isValidId);
user.setEnabled(true);
PasswordCredentialProvider passwordCredentialProvider = new PasswordCredentialProvider(context.getSession());
passwordCredentialProvider.createCredential(context.getRealm(), user, password);
context.setUser(user);
context.getAuthenticationSession().setClientNote("login_hint", email);
context.getEvent().user(user);
context.getEvent().success();
context.newEvent().event(EventType.LOGIN);
context.getEvent().client(context.getAuthenticationSession().getClient().getClientId()).detail("redirect_uri", context.getAuthenticationSession().getRedirectUri()).detail("auth_method", context.getAuthenticationSession().getProtocol());
String authType = context.getAuthenticationSession().getAuthNote("auth_type");
if (authType != null) {
context.getEvent().detail("auth_type", authType);
}
logger.info("User created and logged in successfully");
}
}
但是当我尝试从此链接访问时
http://localhost:8086/realms/realm_id/login-actions/registration?hash=6cb000a567c4f794b42b63c3077d751c55473337ab08827c5adba8a466c3a91a&redirect_uri=http://localhost:8001/login_callback/&issuer=8000-3&client_id=client_id&[email protected]&ruc=80000-5
我被重定向到登录页面并显示以下loginTimeout错误消息:
“您的登录尝试超时。将从头开始登录。”
Keycloak 日志显示:
error 2024-01-15 17:50:02,654 WARN [org.keycloak.events] (executor-thread-10) type=REGISTER_ERROR, realmId=66b58a82-12a7-411a-83f0-e4a2fbcf19f4, clientId=null, userId=null, ipAddress=127.0.0.1, error=expired_code, restart_after_timeout=true
我的自定义注册代码中是否缺少某些内容来处理会话时间?
实际上访问表格的正确网址是
http://localhost:8086/realms/realm_id/protocol/openid-connect/registrations?hash=xxxxxxx&redirect_uri=http://localhost:8001/login_callback/&issuer=80000-0&client_id=xxxxxx&[email protected]&response_type=code&ruc=9000-5
无需验证会话时间。