Приложение Keycloak Angular перенаправляет на вход в систему после обновления страницы при добавлении поставщика 2fa-аутJAVA

Программисты JAVA общаются здесь
Anonymous
Приложение Keycloak Angular перенаправляет на вход в систему после обновления страницы при добавлении поставщика 2fa-аут

Сообщение Anonymous »

После клонирования, настройки и привязки потока браузера-электронной почты-otp к потоку браузера процесс аутентификации работает нормально, код отправляется на электронную почту, и аутентификация прошла успешно. Но после аутентификации, всякий раз, когда я нажимаю на ссылку в своем приложении, меня перенаправляют на страницу входа в систему, это очень похоже на сброс аутентификации, и мне приходится повторять этот процесс, и это делает мое приложение непригодным для использования. Пожалуйста, помогите разобраться в этом. Я прикрепил настройки потока браузера Keycloak, код поставщика аутентификации электронной почты 2fa и код Angular KeycloakService.
EmailAuthenticatorForm.java:

Код: Выделить всё

package com.mesutpiskin.keycloak.auth.email;

import lombok.extern.jbosslog.JBossLog;

import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.AuthenticationFlowError;
import org.keycloak.authentication.AuthenticationFlowException;
import org.keycloak.email.EmailException;
import org.keycloak.email.EmailTemplateProvider;
import org.keycloak.events.Errors;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.FormMessage;
import org.keycloak.services.messages.Messages;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator;
import org.keycloak.common.util.SecretGenerator;

import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@JBossLog

public class EmailAuthenticatorForm extends AbstractUsernameFormAuthenticator {
private final KeycloakSession session;

public EmailAuthenticatorForm(KeycloakSession session) {
this.session = session;
}

@Override
public void authenticate(AuthenticationFlowContext context) {
challenge(context, null);
}

@Override
protected Response challenge(AuthenticationFlowContext context, String error, String field) {
generateAndSendEmailCode(context);

LoginFormsProvider form = context.form().setExecution(context.getExecution().getId());
if (error != null) {
if (field != null) {
form.addError(new FormMessage(field, error));
} else {
form.setError(error);
}
}
Response response = form.createForm("email-code-form.ftl");
context.challenge(response);
return response;
}

private void generateAndSendEmailCode(AuthenticationFlowContext context) {
AuthenticatorConfigModel config = context.getAuthenticatorConfig();
AuthenticationSessionModel session = context.getAuthenticationSession();

if (session.getAuthNote(EmailConstants.CODE) != null) {
// skip sending email code
return;
}

int length = EmailConstants.DEFAULT_LENGTH;
int ttl = EmailConstants.DEFAULT_TTL;
if (config != null) {
// get config values
length = Integer.parseInt(config.getConfig().get(EmailConstants.CODE_LENGTH));
ttl = Integer.parseInt(config.getConfig().get(EmailConstants.CODE_TTL));
}

String code = SecretGenerator.getInstance().randomString(length, SecretGenerator.DIGITS);
sendEmailWithCode(context.getRealm(), context.getUser(), code, ttl);
session.setAuthNote(EmailConstants.CODE, code);
session.setAuthNote(EmailConstants.CODE_TTL, Long.toString(System.currentTimeMillis() + (ttl * 1000L)));
}

@Override
public void action(AuthenticationFlowContext context) {
UserModel userModel = context.getUser();
if (!enabledUser(context, userModel)) {
// error in context is set in enabledUser/isDisabledByBruteForce
return;
}

MultivaluedMap  formData = context.getHttpRequest().getDecodedFormParameters();
if (formData.containsKey("resend")) {
resetEmailCode(context);
challenge(context, null);
return;
}

if (formData.containsKey("cancel")) {
resetEmailCode(context);
context.resetFlow();
return;
}

AuthenticationSessionModel session = context.getAuthenticationSession();
String code = session.getAuthNote(EmailConstants.CODE);
String ttl = session.getAuthNote(EmailConstants.CODE_TTL);
String enteredCode = formData.getFirst(EmailConstants.CODE);

if (enteredCode.equals(code)) {
if (Long.parseLong(ttl) < System.currentTimeMillis()) {
// expired
context.getEvent().user(userModel).error(Errors.EXPIRED_CODE);
Response challengeResponse = challenge(context, Messages.EXPIRED_ACTION_TOKEN_SESSION_EXISTS, EmailConstants.CODE);
context.failureChallenge(AuthenticationFlowError.EXPIRED_CODE, challengeResponse);
} else {
// valid
resetEmailCode(context);
context.success();
}
} else {
// invalid
AuthenticationExecutionModel execution = context.getExecution();
if (execution.isRequired()) {
context.getEvent().user(userModel).error(Errors.INVALID_USER_CREDENTIALS);
Response challengeResponse = challenge(context, Messages.INVALID_ACCESS_CODE, EmailConstants.CODE);
context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challengeResponse);
} else if (execution.isConditional() || execution.isAlternative()) {
context.attempted();
}
}
}

@Override
protected String disabledByBruteForceError() {
return Messages.INVALID_ACCESS_CODE;
}

private void resetEmailCode(AuthenticationFlowContext context) {
context.getAuthenticationSession().removeAuthNote(EmailConstants.CODE);
}

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

@Override
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
return user.getEmail() != null;
}

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

@Override
public void close() {
// NOOP
}

private void sendEmailWithCode(RealmModel realm, UserModel user, String code, int ttl) {
if (user.getEmail() == null) {
log.warnf("Could not send access code email due to missing email. realm=%s user=%s", realm.getId(), user.getUsername());
throw new AuthenticationFlowException(AuthenticationFlowError.INVALID_USER);
}

Map mailBodyAttributes = new HashMap();
mailBodyAttributes.put("username", user.getUsername());
mailBodyAttributes.put("code", code);
mailBodyAttributes.put("ttl", ttl);

String realmName = realm.getDisplayName() != null ? realm.getDisplayName() : realm.getName();
List subjectParams = List.of(realmName);
try {
EmailTemplateProvider emailProvider = session.getProvider(EmailTemplateProvider.class);
emailProvider.setRealm(realm);
emailProvider.setUser(user);
// Don't forget to add the welcome-email.ftl (html and text) template to your theme.
emailProvider.send("emailCodeSubject", subjectParams, "code-email.ftl", mailBodyAttributes);
} catch (EmailException eex) {
log.errorf(eex, "Failed to send access code email.  realm=%s user=%s", realm.getId(), user.getUsername());
}
}
}
EmailAuthenticatorFormFactory.java:

Код: Выделить всё

package com.mesutpiskin.keycloak.auth.email;

import com.google.auto.service.AutoService;
import org.keycloak.Config;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.AuthenticatorFactory;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;

import java.util.List;

@AutoService(AuthenticatorFactory.class)
public class EmailAuthenticatorFormFactory implements AuthenticatorFactory {
@Override
public String getId() {
return "email-authenticator";
}

@Override
public String getDisplayType() {
return "Email OTP";
}

@Override
public String getReferenceCategory() {
return "otp";
}

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

public static final 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 false;
}

@Override
public String getHelpText() {
return "Email otp authenticator.";
}

@Override
public List
 getConfigProperties() {
return List.of(
new ProviderConfigProperty(EmailConstants.CODE_LENGTH, "Code length",
"The number of digits of the generated code.",
ProviderConfigProperty.STRING_TYPE, String.valueOf(EmailConstants.DEFAULT_LENGTH)),
new ProviderConfigProperty(EmailConstants.CODE_TTL, "Time-to-live",
"The time to live in seconds for the code to be valid.", ProviderConfigProperty.STRING_TYPE,
String.valueOf(EmailConstants.DEFAULT_TTL)));
}

@Override
public void close() {
// NOOP
}

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

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

@Override
public void postInit(KeycloakSessionFactory factory) {
// NOOP
}
}

KeycloakService.ts:

Код: Выделить всё

import {UserProfile} from "./user-profile";
import Keycloak from "keycloak-js";
import {Injectable} from "@angular/core";
import {Router} from "@angular/router";
import {UserInfo} from "./user-info";
import {Wallet} from "../models/wallet";
import {WalletControllerService} from "../services/wallet-controller.service";

@Injectable({
providedIn: 'root'
})
export class KeycloakService {

private readonly _keycloak: Keycloak;
private _profile: UserProfile | undefined;
private _userInfo: UserInfo | undefined;

get keycloak() {
return this._keycloak;
}

get profile(): UserProfile | undefined {
return this._profile;
}

get userInfo(): UserInfo | undefined {
return this._userInfo;
}

constructor(private router: Router, private walletService: WalletControllerService) {
this._keycloak = new Keycloak({
url: 'http://localhost:9090',
realm: 'realm-id',
clientId: 'client-id',
});
}

async init(): Promise  {
console.log('Authenticating user... ')
const authenticated = await this.keycloak.init({
onLoad: 'login-required',
checkLoginIframe: false,
// redirectUri: 'http://localhost:4200/account'
});

if (authenticated) {
this._profile = (await this.keycloak.loadUserProfile()) as UserProfile;
this._userInfo = (await  this.keycloak.loadUserInfo()) as UserInfo;
}
}

async login() {
try {
await this.keycloak.login({
redirectUri: 'http://localhost:4200/account'
});
} catch (error) {
console.error('Login failed:', error);
}
}

fetchUserWallet(): Promise {
return new Promise((resolve, reject) => {
this.walletService.getWalletByUser().subscribe({
next: data => {
resolve(data.data);
},
error: err => {
console.error(err);
reject(err);
}
})
});
}

logout(){
return this.keycloak.logout();
}
}

Настройки Keycloak браузера-email-otp-flow
Изображение

Изображение

Помогите, пожалуйста, что-то я делаю не так?

Подробнее здесь: https://stackoverflow.com/questions/786 ... -2fa-email

Вернуться в «JAVA»