Keycloak 21 自定义注册错误:代码过期

问题描述 投票:0回答:1

我有这个自定义注册表单,它位于我的流程中的正常注册表单之前。

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

我的自定义注册代码中是否缺少某些内容来处理会话时间?

java keycloak registration spi keycloak-services
1个回答
0
投票

实际上访问表格的正确网址是

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

无需验证会话时间。

© www.soinside.com 2019 - 2024. All rights reserved.