<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	
	xmlns:georss="http://www.georss.org/georss"
	xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"
	>

<channel>
	<title>uwierzytelnianie - Inero Software - Rozwiązania IT i Konsulting</title>
	<atom:link href="https://inero-software.com/pl/tag/uwierzytelnianie/feed/" rel="self" type="application/rss+xml" />
	<link>https://inero-software.com/pl/tag/uwierzytelnianie/</link>
	<description>Tworzymy cyfrowe innowacje</description>
	<lastBuildDate>Fri, 21 Mar 2025 11:25:52 +0000</lastBuildDate>
	<language>pl-PL</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.1</generator>

<image>
	<url>https://inero-software.com/wp-content/uploads/2018/11/inero-logo-favicon.png</url>
	<title>uwierzytelnianie - Inero Software - Rozwiązania IT i Konsulting</title>
	<link>https://inero-software.com/pl/tag/uwierzytelnianie/</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">153509928</site>	<item>
		<title>Konfiguracja polityki haseł w Keycloak</title>
		<link>https://inero-software.com/pl/konfiguracja-polityki-hasel-w-keycloak/</link>
		
		<dc:creator><![CDATA[Marceli Formela]]></dc:creator>
		<pubDate>Fri, 21 Mar 2025 11:14:52 +0000</pubDate>
				<category><![CDATA[Blog_pl]]></category>
		<category><![CDATA[Firma]]></category>
		<category><![CDATA[Keycloak]]></category>
		<category><![CDATA[funkcje Keycloak]]></category>
		<category><![CDATA[hasła]]></category>
		<category><![CDATA[keycloak]]></category>
		<category><![CDATA[logowanie]]></category>
		<category><![CDATA[polityka haseł]]></category>
		<category><![CDATA[System IAM]]></category>
		<category><![CDATA[uwierzytelnianie]]></category>
		<guid isPermaLink="false">https://inero-software.com/?p=7653</guid>

					<description><![CDATA[<p>W tym artykule najpierw przyjrzymy się wbudowanym mechanizmom zarządzania polityką haseł w Keycloak. Następnie omówimy możliwości ich dostosowania do specyficznych wymagań.</p>
<p>Artykuł <a href="https://inero-software.com/pl/konfiguracja-polityki-hasel-w-keycloak/">Konfiguracja polityki haseł w Keycloak</a> pochodzi z serwisu <a href="https://inero-software.com/pl">Inero Software - Rozwiązania IT i Konsulting</a>.</p>
]]></description>
										<content:encoded><![CDATA[		<div data-elementor-type="wp-post" data-elementor-id="7653" class="elementor elementor-7653" data-elementor-post-type="post">
				<div class="elementor-element elementor-element-949c242 e-flex e-con-boxed e-con e-parent" data-id="949c242" data-element_type="container">
					<div class="e-con-inner">
				<div class="elementor-element elementor-element-a9078db elementor-widget elementor-widget-html" data-id="a9078db" data-element_type="widget" data-widget_type="html.default">
				<div class="elementor-widget-container">
			 
		</div>
				</div>
				<div class="elementor-element elementor-element-cc34f5b elementor-widget elementor-widget-text-editor" data-id="cc34f5b" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<h4>Skuteczne zarządzanie hasłami jest istotnym elementem zabezpieczania kont użytkowników, a Keycloak dostarcza narzędzia do egzekwowania silnych zasad uwierzytelniania. Dzięki konfiguracji reguł haseł administratorzy mogą zadbać o zgodność poświadczeń ze standardami bezpieczeństwa, minimalizując ryzyko nieautoryzowanego dostępu. Platforma oferuje elastyczne opcje, umożliwiające definiowanie wymagań, dotyczących długości i złożoności haseł, ich ważności oraz zapobiegania ponownemu użyciu.</h4>						</div>
				</div>
				<div class="elementor-element elementor-element-0df981b elementor-widget elementor-widget-text-editor" data-id="0df981b" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<p>W tym artykule najpierw przyjrzymy się wbudowanym mechanizmom zarządzania polityką haseł w Keycloak. Następnie omówimy możliwości ich dostosowania do specyficznych wymagań.</p>						</div>
				</div>
				<div class="elementor-element elementor-element-ad5f7b2 elementor-widget elementor-widget-image" data-id="ad5f7b2" data-element_type="widget" data-widget_type="image.default">
				<div class="elementor-widget-container">
													<img fetchpriority="high" decoding="async" width="775" height="411" src="https://inero-software.com/wp-content/uploads/2025/03/115856.png" class="attachment-large size-large wp-image-7638" alt="" srcset="https://inero-software.com/wp-content/uploads/2025/03/115856.png 775w, https://inero-software.com/wp-content/uploads/2025/03/115856-300x159.png 300w, https://inero-software.com/wp-content/uploads/2025/03/115856-768x407.png 768w, https://inero-software.com/wp-content/uploads/2025/03/115856-566x300.png 566w" sizes="(max-width: 775px) 100vw, 775px" data-attachment-id="7638" data-permalink="https://inero-software.com/configuring-password-policies-in-keycloak/attachment/115856/" data-orig-file="https://inero-software.com/wp-content/uploads/2025/03/115856.png" data-orig-size="775,411" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="115856" data-image-description="" data-image-caption="" data-medium-file="https://inero-software.com/wp-content/uploads/2025/03/115856-300x159.png" data-large-file="https://inero-software.com/wp-content/uploads/2025/03/115856.png" role="button" />													</div>
				</div>
				<div class="elementor-element elementor-element-b583771 elementor-widget elementor-widget-heading" data-id="b583771" data-element_type="widget" data-widget_type="heading.default">
				<div class="elementor-widget-container">
			<h3 class="elementor-heading-title elementor-size-default">Wbudowane polityki </h3>		</div>
				</div>
				<div class="elementor-element elementor-element-b2a2079 elementor-widget elementor-widget-text-editor" data-id="b2a2079" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<p>Wbudowane polityki haseł w Keycloak umożliwiają administratorom egzekwowanie zasad bezpieczeństwa w celu wzmocnienia uwierzytelniania użytkowników. Poniżej znajduje się krótki opis każdej z nich:</p>						</div>
				</div>
				<div class="elementor-element elementor-element-1dcf64f elementor-widget elementor-widget-text-editor" data-id="1dcf64f" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<ol><li><strong>Wygasanie hasła (Expire Password)</strong> – Wymusza zmianę hasła po określonym czasie.</li><li><strong>Iteracje hashowania (Hashing Iterations)</strong> – Określa liczbę iteracji podczas hashowania hasła w celu zwiększenia bezpieczeństwa.</li><li><strong>Brak ponownego użycia (Not Recently Used)</strong> – Zapobiega ponownemu użyciu ostatnich haseł przez użytkownika.</li><li><strong>Czarna lista haseł (Password Blacklist)</strong> – Blokuje określone hasła, zwykle słabe lub powszechnie używane.</li><li><strong>Wyrażenie regularne (Regular Expression)</strong> – Pozwala wymusić niestandardowy wzorzec regex dla walidacji hasła.</li><li><strong>Minimalna długość (Minimum Length)</strong> – Ustawia minimalną liczbę znaków wymaganą w haśle.</li><li><strong>Brak nazwy użytkownika jako hasła (Not Username)</strong> – Uniemożliwia ustawienie nazwy użytkownika jako hasła.</li><li><strong>Brak adresu e-mail jako hasła (Not Email)</strong> – Zapobiega używaniu adresu e-mail jako hasła.</li><li><strong>Brak ponownego użycia w określonym czasie (Not Recently Used in Days)</strong> – Blokuje ponowne użycie hasła przez określoną liczbę dni.</li><li><strong>Nie zawiera nazwy użytkownika (Not Contains Username)</strong> – Wymusza, aby hasło nie zawierało nazwy użytkownika.</li><li><strong>Znaki specjalne (Special Characters)</strong> – Wymaga co najmniej jednego znaku specjalnego w haśle.</li><li><strong>Wielkie litery (Uppercase Characters)</strong> – Wymusza obecność co najmniej jednej wielkiej litery w haśle.</li><li><strong>Małe litery (Lowercase Characters)</strong> – Wymaga co najmniej jednej małej litery w haśle.</li><li><strong>Cyfry (Digits)</strong> – Wymaga co najmniej jednej cyfry w haśle.</li><li><strong>Maksymalny czas ważności uwierzytelnienia (Maximum Authentication Age)</strong> – Określa maksymalny czas ważności sesji przed wymuszeniem ponownego logowania.</li><li><strong>Algorytm hashowania (Hashing Algorithm)</strong> – Określa algorytm używany do szyfrowania haseł.</li><li><strong>Maksymalna długość (Maximum Length)</strong> – Definiuje maksymalną dopuszczalną długość hasła.</li></ol>						</div>
				</div>
				<div class="elementor-element elementor-element-95da107 elementor-widget elementor-widget-heading" data-id="95da107" data-element_type="widget" data-widget_type="heading.default">
				<div class="elementor-widget-container">
			<h2 class="elementor-heading-title elementor-size-default">Implementacja niestandardowej polityki haseł przy użyciu SPI</h2>		</div>
				</div>
				<div class="elementor-element elementor-element-86f9385 elementor-widget elementor-widget-text-editor" data-id="86f9385" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<p>Aby zaimplementować niestandardową politykę haseł w Keycloak, należy użyć interfejsu dostawcy usług (SPI – Service Provider Interface).</p>						</div>
				</div>
				<div class="elementor-element elementor-element-efcfbc7 elementor-widget elementor-widget-text-editor" data-id="efcfbc7" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<p>W tym przypadku definiujemy niestandardowego dostawcę polityki haseł, implementując interfejs <strong>PasswordPolicyProviderFactory</strong>.</p>						</div>
				</div>
				<div class="elementor-element elementor-element-a47d185 elementor-widget elementor-widget-code-highlight" data-id="a47d185" data-element_type="widget" data-widget_type="code-highlight.default">
				<div class="elementor-widget-container">
					<div class="prismjs-default copy-to-clipboard ">
			<pre data-line="" class="highlight-height language-javascript line-numbers">
				<code readonly="true" class="language-javascript">
					<xmp>public class PasswordCustomPolicyProviderFactory implements PasswordPolicyProviderFactory {

	public static final Integer DEFAULT_VALUE = 1;
	public static final String MIN_PASSWORD_LIFETIME_ID = "minimumPasswordLifetime";

	@Override
	public String getId() {
    	return MIN_PASSWORD_LIFETIME_ID;
	}

	@Override
	public PasswordPolicyProvider create(KeycloakSession session) {
    	return new PasswordCustomPolicyProvider(session);
	}
[...]
}

</xmp>
				</code>
			</pre>
		</div>
				</div>
				</div>
				<div class="elementor-element elementor-element-75140a5 elementor-widget elementor-widget-text-editor" data-id="75140a5" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<p><strong data-start="0" data-end="11" data-is-only-node="">Factory</strong> instancjonuje i zwraca nową instancję <strong data-start="50" data-end="82">PasswordCustomPolicyProvider</strong>, która zawiera logikę walidacji wymuszającą minimalny czas życia hasła. Stała <strong data-start="161" data-end="189">MIN_PASSWORD_LIFETIME_ID</strong> pełni rolę unikalnego identyfikatora tej niestandardowej polityki, a stała <strong data-start="265" data-end="282">DEFAULT_VALUE</strong> określa domyślny minimalny czas życia hasła (w dniach), jeśli nie zostanie skonfigurowana inna wartość w <strong data-start="388" data-end="405">Admin Console</strong>.</p>						</div>
				</div>
				<div class="elementor-element elementor-element-3c1cd5b elementor-widget elementor-widget-code-highlight" data-id="3c1cd5b" data-element_type="widget" data-widget_type="code-highlight.default">
				<div class="elementor-widget-container">
					<div class="prismjs-default copy-to-clipboard ">
			<pre data-line="" class="highlight-height language-javascript line-numbers">
				<code readonly="true" class="language-javascript">
					<xmp>public class PasswordCustomPolicyProvider implements PasswordPolicyProvider {
np.
   private static final String POLICY_VIOLATION_MESSAGE = "passwordLifetimeViolation";


   private final KeycloakSession keycloakSession;

   public PasswordCustomPolicyProvider(KeycloakSession keycloakSession) {
   	this.keycloakSession = keycloakSession;
   }


   @Override
   public PolicyError validate(RealmModel realm, UserModel user, String password) {
   	PasswordCredentialProvider credentialProvider = new PasswordCredentialProvider(keycloakSession);
   	PasswordCredentialModel credentialModel = credentialProvider.getPassword(realm, user);

   	if (credentialModel == null) {
       	return null;
   	}

   	long passwordCreationTime = credentialModel.getCreatedDate();
   	long currentTime = Time.currentTimeMillis();
   	long elapsedTime = currentTime - passwordCreationTime;

   	PasswordPolicy passwordPolicy = realm.getPasswordPolicy();
   	int minPasswordLifetimeDays = passwordPolicy.getPolicyConfig(PasswordCustomPolicyProviderFactory.MIN_PASSWORD_LIFETIME_ID);
   	long minPasswordLifetimeMillis = TimeUnit.DAYS.toMillis(minPasswordLifetimeDays);
   	return elapsedTime >= minPasswordLifetimeMillis ? null : new PolicyError(POLICY_VIOLATION_MESSAGE, minPasswordLifetimeDays);
   }
[...]
}
</xmp>
				</code>
			</pre>
		</div>
				</div>
				</div>
				<div class="elementor-element elementor-element-7cd9848 elementor-widget elementor-widget-text-editor" data-id="7cd9848" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<p><strong>PasswordCredentialProvider</strong> może uzyskać dostęp do zapisanego znacznika czasu utworzenia hasła za pośrednictwem instancji <strong>PasswordCredentialModel</strong>. Następnie oblicza <strong>elapsedTime</strong> jako różnicę między tym znacznikiem a bieżącym czasem systemowym, co określa, jak długo hasło jest już używane.</p>						</div>
				</div>
				<div class="elementor-element elementor-element-c8ec00f elementor-widget elementor-widget-text-editor" data-id="c8ec00f" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<p>Następnie obiekt <strong>PasswordPolicy</strong> pobiera politykę haseł dla danego realm&#8217;u, wyodrębnia minimalny wymagany czas życia hasła w dniach (<strong>minPasswordLifetimeDays</strong>) i przelicza go na milisekundy (<strong>minPasswordLifetimeMillis</strong>). Polityka ta zapewnia, że hasło było używane przez co najmniej wymagany okres. Jeśli warunek ten nie zostanie spełniony, zwracany jest obiekt <strong>PolicyError</strong>. Klucz wiadomości o błędzie jest zapisany w stałej <strong>POLICY_VIOLATION_MESSAGE</strong>, a jego treść może być dostosowana w naszym motywie. Pozwala to na zdefiniowanie przyjaznego komunikatu, który informuje użytkownika, dlaczego zmiana hasła jest niedostępna i ile czasu pozostało do możliwości ustawienia nowego.</p>						</div>
				</div>
				<div class="elementor-element elementor-element-5c8b84b elementor-widget elementor-widget-image" data-id="5c8b84b" data-element_type="widget" data-widget_type="image.default">
				<div class="elementor-widget-container">
													<img decoding="async" width="711" height="443" src="https://inero-software.com/wp-content/uploads/2025/03/122254.png" class="attachment-large size-large wp-image-7639" alt="" srcset="https://inero-software.com/wp-content/uploads/2025/03/122254.png 711w, https://inero-software.com/wp-content/uploads/2025/03/122254-300x187.png 300w, https://inero-software.com/wp-content/uploads/2025/03/122254-481x300.png 481w" sizes="(max-width: 711px) 100vw, 711px" data-attachment-id="7639" data-permalink="https://inero-software.com/configuring-password-policies-in-keycloak/attachment/122254/" data-orig-file="https://inero-software.com/wp-content/uploads/2025/03/122254.png" data-orig-size="711,443" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="122254" data-image-description="" data-image-caption="" data-medium-file="https://inero-software.com/wp-content/uploads/2025/03/122254-300x187.png" data-large-file="https://inero-software.com/wp-content/uploads/2025/03/122254.png" role="button" />													</div>
				</div>
				<div class="elementor-element elementor-element-a18d447 elementor-widget elementor-widget-text-editor" data-id="a18d447" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<p>W ten sposób możemy definiować niestandardowe polityki haseł w Keycloak, gdy domyślny zestaw polityk okazuje się niewystarczający dla konkretnych wymagań. Taka elastyczność umożliwia bardziej szczegółową kontrolę nad uwierzytelnianiem użytkowników i zarządzaniem hasłami, gdy zachodzi taka potrzeba.</p>						</div>
				</div>
				<div class="elementor-element elementor-element-ed723ca elementor-widget elementor-widget-heading" data-id="ed723ca" data-element_type="widget" data-widget_type="heading.default">
				<div class="elementor-widget-container">
			<h3 class="elementor-heading-title elementor-size-default">Dostosowanie interfejsu w celu poprawy UX</h3>		</div>
				</div>
				<div class="elementor-element elementor-element-0d876c8 elementor-widget elementor-widget-text-editor" data-id="0d876c8" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<p>Domyślnie Keycloak wyświetla niespełnione polityki haseł osobno na stronie logowania. Może to być problematyczne dla wielu użytkowników, zwłaszcza gdy naruszonych jest kilka zasad jednocześnie. Prowadzi to do przeładowanego interfejsu i utrudnia użytkownikom zrozumienie wszystkich wymagań dotyczących hasła. Aby temu zaradzić, można dostosować ekran logowania tak, aby prezentował zbiorczą listę wszystkich niespełnionych polityk haseł, co zapewni bardziej przejrzyste i przyjazne dla użytkownika doświadczenie.</p>						</div>
				</div>
				<div class="elementor-element elementor-element-129921c elementor-widget elementor-widget-code-highlight" data-id="129921c" data-element_type="widget" data-widget_type="code-highlight.default">
				<div class="elementor-widget-container">
					<div class="prismjs-default copy-to-clipboard ">
			<pre data-line="" class="highlight-height language-javascript line-numbers">
				<code readonly="true" class="language-javascript">
					<xmp>public class CustomFreeMarkerLoginFormsProvider extends FreeMarkerLoginFormsProvider {
/**
* Mapping between password policy provider IDs and custom messages
* Note: contains only standard policies that must be displayed in the UI
*/
private final Map<String, String> policyPropertyMessages = Map.of(
LengthPasswordPolicyProviderFactory.ID, MINIMUM_LENGTH_MESSAGE,
MaximumLengthPasswordPolicyProviderFactory.ID, MAXIMUM_LENGTH_MESSAGE,
DigitsPasswordPolicyProviderFactory.ID, MINIMUM_DIGIT_MESSAGE,
SpecialCharsPasswordPolicyProviderFactory.ID, MINIMUM_SPECIAL_CHAR_MESSAGE,
UpperCasePasswordPolicyProviderFactory.ID, MINIMUM_UPPERCASE_MESSAGE,
LowerCasePasswordPolicyProviderFactory.ID, MINIMUM_LOWERCASE_MESSAGE,
NotUsernamePasswordPolicyProviderFactory.ID, NOT_USERNAME_MESSAGE,
NotContainsUsernamePasswordPolicyProviderFactory.ID, NOT_CONTAINS_USERNAME_MESSAGE,
NotEmailPasswordPolicyProviderFactory.ID, NOT_EMAIL_MESSAGE
);

[...]

@Override
protected void createCommonAttributes(Theme theme, Locale locale, Properties messagesBundle,
UriBuilder baseUriBuilder, LoginFormsPages page) {
super.createCommonAttributes(theme, locale, messagesBundle, baseUriBuilder, page);
if (realm != null && realm.getPasswordPolicy() != null) {
attributes.put("passwordPolicies", getPasswordPolicyMessages(realm.getPasswordPolicy(), messagesBundle));
}}

[...]

private Map<String, String> getPasswordPolicyMessages(PasswordPolicy passwordPolicy, Properties messagesBundle) {
Map<String, String> policyMessages = new HashMap<>();
PasswordPolicy.Builder builder = passwordPolicy.toBuilder();
for (String policyName : passwordPolicy.getPolicies()) {
var value = builder.get(policyName);
String message = extractPolicyMessage(policyName, value, messagesBundle);
if (message != null) {
policyMessages.put(policyName, message);
}
}
return policyMessages;
}

[...]

/**
* Extracts a message for a given password policy from the messages bundle
* Note: Policy message is constructed by replacing the {0} placeholder with the policy value
*/
private String extractPolicyMessage(String policy, String value, Properties messagesBundle) {
String property = policyPropertyMessages.get(policy);
if (property == null) {
return null;
}
String policyMessage = messagesBundle.getProperty(property);
return policyMessage != null ? policyMessage.replace("{0}", value) : null;
}
</xmp>
				</code>
			</pre>
		</div>
				</div>
				</div>
				<div class="elementor-element elementor-element-e47c0c6 elementor-widget elementor-widget-text-editor" data-id="e47c0c6" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<p>Funkcja <strong>getPasswordPolicyMessages()</strong> już zbiera polityki haseł z obiektu <strong>PasswordPolicy</strong> i mapuje je na odpowiednie komunikaty z pliku wiadomości (<strong>message bundle</strong>). Można ją rozszerzyć tak, aby wyświetlała wszystkie niespełnione zasady w jednej zbiorczej wiadomości.</p><p>Polityki haseł, takie jak minimalna długość, wymagane cyfry, znaki specjalne itp., są mapowane na komunikaty za pomocą metody <strong>extractPolicyMessage()</strong>. Nasza implementacja serwisu przechodzi przez każdą z zasad i sprawdza, czy jest spełniona. Jeśli nie – wyświetlany jest odpowiadający jej komunikat.</p><p>Na stronie <strong>update-password.ftl</strong> możesz zaprezentować te niespełnione zasady jako listę przy użyciu szablonów <strong>FreeMarker</strong>.</p>						</div>
				</div>
				<div class="elementor-element elementor-element-e70d98c elementor-widget elementor-widget-code-highlight" data-id="e70d98c" data-element_type="widget" data-widget_type="code-highlight.default">
				<div class="elementor-widget-container">
					<div class="prismjs-default copy-to-clipboard ">
			<pre data-line="" class="highlight-height language-javascript line-numbers">
				<code readonly="true" class="language-javascript">
					<xmp>
    	<#if passwordPolicies?has_content>
        	<div class="${properties.kcAlertClass}">
            	<div class="${properties.kcAlertIconWrapperClass}">
                	<span class="${properties.kcAlertIconClass}"></span>
            	</div>
            	<span class="${properties.kcAlertTitleClass}">
            	${msg("passwordInstruction")} <br>
            	<#list passwordPolicies?keys as key>
                	<span class="${properties.kcAlertTitleClass}">&#x2022; ${passwordPolicies[key]}</span><br/>
            	</#list>
            	</span>
        	</div>
    	</#if>

</xmp>
				</code>
			</pre>
		</div>
				</div>
				</div>
				<div class="elementor-element elementor-element-358fd5c elementor-widget elementor-widget-image" data-id="358fd5c" data-element_type="widget" data-widget_type="image.default">
				<div class="elementor-widget-container">
													<img decoding="async" width="648" height="510" src="https://inero-software.com/wp-content/uploads/2025/03/123206.png" class="attachment-large size-large wp-image-7640" alt="" srcset="https://inero-software.com/wp-content/uploads/2025/03/123206.png 648w, https://inero-software.com/wp-content/uploads/2025/03/123206-300x236.png 300w, https://inero-software.com/wp-content/uploads/2025/03/123206-381x300.png 381w, https://inero-software.com/wp-content/uploads/2025/03/123206-380x300.png 380w" sizes="(max-width: 648px) 100vw, 648px" data-attachment-id="7640" data-permalink="https://inero-software.com/configuring-password-policies-in-keycloak/attachment/123206/" data-orig-file="https://inero-software.com/wp-content/uploads/2025/03/123206.png" data-orig-size="648,510" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="123206" data-image-description="" data-image-caption="" data-medium-file="https://inero-software.com/wp-content/uploads/2025/03/123206-300x236.png" data-large-file="https://inero-software.com/wp-content/uploads/2025/03/123206.png" role="button" />													</div>
				</div>
				<div class="elementor-element elementor-element-6d7a6c0 elementor-widget elementor-widget-heading" data-id="6d7a6c0" data-element_type="widget" data-widget_type="heading.default">
				<div class="elementor-widget-container">
			<h3 class="elementor-heading-title elementor-size-default">Praktyczne przykłady polityk haseł</h3>		</div>
				</div>
				<div class="elementor-element elementor-element-776740f elementor-widget elementor-widget-text-editor" data-id="776740f" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<p>Zobaczmy, jak wyglądają polityki haseł w dużych firmach.</p><p> </p><p>Apple wymaga, aby hasła miały co najmniej osiem znaków i zawierały zarówno litery, jak i cyfry. Dodatkowo, hasła nie mogą zawierać trzech lub więcej identycznych znaków pod rząd i nie mogą być powszechnie używanymi hasłami.</p><p> </p><p>Facebook narzuca minimalną długość hasła wynoszącą ponad sześć znaków, choć zaleca stosowanie dłuższych haseł. Choć Meta nie wymaga użycia znaków specjalnych ani cyfr, zachęca do tworzenia złożonych haseł.</p><p> </p><p>Microsoft wymaga, aby hasła miały co najmniej 8 znaków i zawierały co najmniej dwa z następujących typów znaków: wielkie litery, małe litery, cyfry lub symbole. Dodatkowo, system może blokować możliwość ustawienia hasła zbyt podobnego do poprzedniego.</p><p> </p><p>Chociaż firmy te korzystają z różnych narzędzi uwierzytelniania, warto zwrócić uwagę na standardy bezpieczeństwa wdrażane w dużych systemach produkcyjnych.</p><p> </p><p>I mimo że te polityki haseł nie są skrajnie restrykcyjne, użytkownicy powinni unikać wykorzystywania w hasłach wrażliwych danych osobowych, takich jak imiona, daty urodzenia czy numery telefonów. Należy również unikać ponownego używania haseł w różnych usługach, ponieważ może to prowadzić do naruszeń bezpieczeństwa w przypadku przejęcia jednego z kont. Włączenie uwierzytelniania dwuskładnikowego (2FA) i okresowy przegląd bezpieczeństwa haseł to kolejne kroki, które użytkownicy mogą podjąć w celu zwiększenia ochrony.</p>						</div>
				</div>
				<div class="elementor-element elementor-element-d64b6d9 elementor-widget elementor-widget-heading" data-id="d64b6d9" data-element_type="widget" data-widget_type="heading.default">
				<div class="elementor-widget-container">
			<h3 class="elementor-heading-title elementor-size-default">Podsumowanie </h3>		</div>
				</div>
				<div class="elementor-element elementor-element-911c3a6 elementor-widget elementor-widget-text-editor" data-id="911c3a6" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<p>Jak widać, Keycloak oferuje zestaw domyślnych polityk haseł, które obejmują standardowe zasady bezpieczeństwa, takie jak minimalna długość, wymagania dotyczące złożoności czy historia użycia haseł. Wbudowane polityki są wystarczające w wielu przypadkach, jednak w razie potrzeby istnieje możliwość ich dostosowania do konkretnych wymagań organizacyjnych. Keycloak pozwala również na tworzenie własnych polityk haseł, co daje większą kontrolę nad bezpieczeństwem.</p><p> </p><p>Oprócz modyfikacji samych zasad, Keycloak umożliwia także dostosowanie interfejsu użytkownika. Jest to szczególnie przydatne w sytuacjach, gdy domyślny sposób prezentowania naruszeń polityk haseł — np. wyświetlanie niespełnionych wymagań osobno — nie spełnia naszych oczekiwań. W takich przypadkach możemy zmienić sposób prezentacji błędów lub wzbogacić komunikaty o dodatkowe informacje, aby były bardziej czytelne i zrozumiałe dla użytkownika.</p><p> </p><p>Dzięki tym możliwościom Keycloak pokazuje wysoki poziom elastyczności, umożliwiając pełną kontrolę zarówno nad politykami bezpieczeństwa, jak i nad wyglądem interfejsu. Czyni go to uniwersalnym rozwiązaniem do zarządzania tożsamością i dostępem. Możliwość definiowania własnych reguł i dostosowywania komponentów sprawia, że Keycloak to skalowalne narzędzie, które z łatwością można dopasować do indywidualnych potrzeb organizacji.</p>						</div>
				</div>
					</div>
				</div>
				</div>
		<p>Artykuł <a href="https://inero-software.com/pl/konfiguracja-polityki-hasel-w-keycloak/">Konfiguracja polityki haseł w Keycloak</a> pochodzi z serwisu <a href="https://inero-software.com/pl">Inero Software - Rozwiązania IT i Konsulting</a>.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7653</post-id>	</item>
		<item>
		<title>Tworzenie niestandardowego uwierzytelniania SMS w Keycloak</title>
		<link>https://inero-software.com/pl/tworzenie-niestandardowego-uwierzytelniania-sms-w-keycloak/</link>
		
		<dc:creator><![CDATA[Marceli Formela]]></dc:creator>
		<pubDate>Fri, 14 Jun 2024 11:01:51 +0000</pubDate>
				<category><![CDATA[Blog_pl]]></category>
		<category><![CDATA[Firma]]></category>
		<category><![CDATA[Technologie]]></category>
		<category><![CDATA[biznes]]></category>
		<category><![CDATA[cyberbezpieczeństwo]]></category>
		<category><![CDATA[keycaloak]]></category>
		<category><![CDATA[MFA]]></category>
		<category><![CDATA[sms]]></category>
		<category><![CDATA[SMS MFA]]></category>
		<category><![CDATA[SPI]]></category>
		<category><![CDATA[uwierzytelnianie]]></category>
		<guid isPermaLink="false">https://inero-software.com/?p=5868</guid>

					<description><![CDATA[<p>Artykuł <a href="https://inero-software.com/pl/tworzenie-niestandardowego-uwierzytelniania-sms-w-keycloak/">Tworzenie niestandardowego uwierzytelniania SMS w Keycloak</a> pochodzi z serwisu <a href="https://inero-software.com/pl">Inero Software - Rozwiązania IT i Konsulting</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<div class="row"><div class="col-sm-1"></div><div class="col-sm-10">
<p>&nbsp;</p>
<p>Wraz ze wzrostem liczby zagrożeń cybernetycznych, uwierzytelnianie wieloskładnikowe (MFA) stało się dla wielu firm standardem w politykach bezpieczeństwa.  MFA zwiększa ochronę, wymagając od użytkowników weryfikacji tożsamości za pomocą wielu metod. Takie uwierzytelnianie stało się więc standardową praktyk, dodając dodatkową warstwę ochrony.</p>
<p>Wśród różnych metod, uwierzytelnianie oparte na SMS wyróżnia się równowagą między bezpieczeństwem a wygodą użytkownika. Jednak tworzenie niestandardowego uwierzytelnienia SMS w ramach dostawcy tożsamości, takiego jak Keycloak, może być skomplikowanym i złożonym procesem, wymagającym zrozumienia jego architektury i możliwości rozszerzania.</p>
<h3><b>Interfejs Dostawcy Usług (SPI)</b></h3>
<p>Keycloak ma na celu obsługę większości przypadków użycia bez konieczności tworzenia niestandardowego kodu. Oferuje także elastyczność w zakresie dostosowywania. W tym celu Keycloak udostępnia kilka SPI, które pozwalają na wdrażanie własnych rozwiązań. Zamierzamy wdrożyć uwierzytelnianie, które wymaga ważnego kodu SMS. Aby stworzyć tę funkcję, musimy  zaimplementować interfejsy <em>org.keycloak.authentication.AuthenticatorFactory</em> i <em>Authenticator</em>. AuthenticatorFactory jest odpowiedzialny za tworzenie instancji Authenticatora. Oba interfejsy rozszerzają ogólny zestaw interfejsów Provider i ProviderFactory, które są używane przez inne komponenty Keycloak.</p>
<h3><b>Pakietowanie klas</b></h3>
<p>Będziemy pakietować nasze klasy do jednego projektu. Musi on zawierać plik o nazwie <code>org.keycloak.authentication.AuthenticatorFactory</code>, który powinien znajdować się w katalogu <code>META-INF/services/</code>. Plik ten musi zawierać pełne kwalifikowane nazwy klas każdej implementacji <code>AuthenticatorFactory</code>, którą masz w pliku JAR. Na przykład:</p>
<pre><span style="font-weight: 400;">pl.inero.keycloakext.authenticator.sms.SmsAuthenticatorFactory</span>
<span style="font-weight: 400;">pl.inero.keycloakext.authenticator.custom.CustomUsernamePasswordFormFactory</span>
<span style="font-weight: 400;">pl.inero.keycloakext.authenticator.custom.CustomCookieAuthenticatorFactory</span></pre>
<p>Plik <code>services/</code> jest używany przez Keycloak do skanowania dostawców, których musi załadować do systemu.</p>
<p>&nbsp;</p>
<h3><b>CredentialModel i CredentialProvider</b></h3>
<p>Pierwszym krokiem jest skonfigurowanie naszych klas związanych z poświadczeniami, ponieważ numer telefonu użytkownika powinien być przechowywany jako rekord poświadczeń. Jak widać poniżej, klasa <em>Sms2faCredentialData</em> jest prostym kontenerem danych do przechowywania numeru telefonu powiązanego z użytkownikiem.</p>
<pre><span style="font-weight: 400;">public class </span><span style="font-weight: 400;">Sms2faCredentialData {
</span>
<span style="font-weight: 400;">private </span><span style="font-weight: 400;">String phoneNumber</span><span style="font-weight: 400;">;
</span>
<span style="font-weight: 400;">@SuppressWarnings(</span><span style="font-weight: 400;">"unused"</span><span style="font-weight: 400;">) </span><span style="font-weight: 400;">//used for credentials deserialization</span>
<span style="font-weight: 400;">public </span><span style="font-weight: 400;">Sms2faCredentialData() {</span>
<span style="font-weight: 400;">}
</span>
<span style="font-weight: 400;">public </span><span style="font-weight: 400;">Sms2faCredentialData(String phoneNumber) {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">this</span><span style="font-weight: 400;">.phoneNumber = phoneNumber</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">}
</span>
<span style="font-weight: 400;">public </span><span style="font-weight: 400;">String getPhoneNumber() {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">return </span><span style="font-weight: 400;">phoneNumber</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">}
</span>
<span style="font-weight: 400;">@SuppressWarnings(</span><span style="font-weight: 400;">"unused"</span><span style="font-weight: 400;">) </span><span style="font-weight: 400;">//used for credentials deserialization</span>
<span style="font-weight: 400;">public void </span><span style="font-weight: 400;">setPhoneNumber(String phoneNumber) {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">this</span><span style="font-weight: 400;">.phoneNumber = phoneNumber</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">}</span>
<span style="font-weight: 400;">}</span></pre>
<p>Kolejnym krokiem jest rozszerzenie klasy <em>CredentialModel</em>, która może generować prawidłowy format poświadczeń w bazie danych. Aby obiekty <em>Sms2faCredentialModel</em> były w pełni funkcjonalne, muszą zawierać nie tylko surowe dane JSON odziedziczone po klasie bazowej<strong>,</strong> ale także odmarshallowane obiekty wewnątrz swoich własnych atrybutów. Zapewnia to szeroką dostępność i wykorzystanie poświadczeń, umożliwiając łatwą integrację i obsługę procesów uwierzytelniania.</p>
<pre><span style="font-weight: 400;">public class </span><span style="font-weight: 400;">Sms2faCredentialModel </span><span style="font-weight: 400;">extends </span><span style="font-weight: 400;">CredentialModel {
</span>
<span style="font-weight: 400;">public static final </span><span style="font-weight: 400;">String TYPE = </span><span style="font-weight: 400;">"sms2fa"</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">private </span><span style="font-weight: 400;">Sms2faCredentialData smsCredentials</span><span style="font-weight: 400;">;
</span>
<span style="font-weight: 400;">public </span><span style="font-weight: 400;">Sms2faCredentialModel(Sms2faCredentialData smsCredentials) {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">try </span><span style="font-weight: 400;">{</span>
<span style="font-weight: 400;">        </span> <span style="font-weight: 400;">this</span><span style="font-weight: 400;">.smsCredentials = smsCredentials</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">        </span> <span style="font-weight: 400;">setCredentialData(JsonSerialization.writeValueAsString(smsCredentials))</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">        </span> <span style="font-weight: 400;">setUserLabel(</span><span style="font-weight: 400;">"tel: " </span><span style="font-weight: 400;">+ smsCredentials.getPhoneNumber())</span><span style="font-weight: 400;">;
</span>
<span style="font-weight: 400;">        </span> <span style="font-weight: 400;">setType(TYPE)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">} </span><span style="font-weight: 400;">catch </span><span style="font-weight: 400;">(IOException e) {</span>
<span style="font-weight: 400;">        </span> <span style="font-weight: 400;">throw new </span><span style="font-weight: 400;">RuntimeException(e)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">}</span>
<span style="font-weight: 400;">}
</span>
<span style="font-weight: 400;">public </span><span style="font-weight: 400;">String getPhoneNumber() {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">return </span><span style="font-weight: 400;">smsCredentials.getPhoneNumber()</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">}</span>
<span style="font-weight: 400;">}</span></pre>
<p>Podobnie jak w przypadku innych dostawców w Keycloak, utworzenie <em>CredentialProvider</em> wymaga obecności odpowiadającej mu <em>CredentialsProviderFactory.</em> Aby spełnić ten wymóg, implementujemy <em>Sms2faCredentialProviderFactory.</em></p>
<pre><span style="font-weight: 400;">public class </span><span style="font-weight: 400;">Sms2faCredentialProviderFactory  </span><span style="font-weight: 400;">implements </span><span style="font-weight: 400;">CredentialProviderFactory&lt;Sms2faCredentialProvider&gt; {</span>

<span style="font-weight: 400;">public static final </span><span style="font-weight: 400;">String PROVIDER_ID = </span><span style="font-weight: 400;">"keycloak-ext-sms2fa"</span><span style="font-weight: 400;">;</span>

<span style="font-weight: 400;">@Override</span>
<span style="font-weight: 400;">public </span><span style="font-weight: 400;">String getId() {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">return </span><span style="font-weight: 400;">PROVIDER_ID</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">}</span>

<span style="font-weight: 400;">@Override</span>
<span style="font-weight: 400;">public </span><span style="font-weight: 400;">CredentialProvider&lt;Sms2faCredentialModel&gt; create(KeycloakSession session) {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">return new </span><span style="font-weight: 400;">Sms2faCredentialProvider(session)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">}</span>
<span style="font-weight: 400;">}</span></pre>
<p>Interfejs <em>CredentialProvider</em> jest zbudowany z parametrem generycznym, który rozszerza <em>CredentialModel</em>, zapewniając kompatybilność z różnymi typami poświadczeń. Dodatkowo musimy zaimplementować interfejs <em>CredentialInputValidator</em>, co wskazuje Keycloak, że ten dostawca jest przygotowany do uwierzytelniania poświadczeń dla naszego niestandardowego Authenticatora. Chociaż nie będziemy tu omawiać pełnej architektury, dokumentacja Keycloak obejmuje dodatkowe metody.</p>
<p>Nasza implementacja obejmuje funkcje tworzenia i usuwania poświadczeń. Funkcje te wykorzystują menedżera poświadczeń, odpowiedzialnego za przechowywanie i pobieranie poświadczeń, niezależnie od tego, czy są one przechowywane lokalnie, czy w systemach magazynowania federacyjnego.</p>
<pre><span style="font-weight: 400;">@Override</span>
<span style="font-weight: 400;">public </span><span style="font-weight: 400;">CredentialModel createCredential(RealmModel realm</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">UserModel user</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">Sms2faCredentialModel credentialModel) {</span>
<span style="font-weight: 400;">if </span><span style="font-weight: 400;">(credentialModel.getCreatedDate() == </span><span style="font-weight: 400;">null</span><span style="font-weight: 400;">) {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">credentialModel.setCreatedDate(Time.currentTimeMillis())</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">return </span><span style="font-weight: 400;">user.credentialManager().createStoredCredential(credentialModel)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">}</span>

<span style="font-weight: 400;">@Override</span>
<span style="font-weight: 400;">public boolean </span><span style="font-weight: 400;">deleteCredential(RealmModel realm</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">UserModel user</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">String credentialId) {</span>
<span style="font-weight: 400;">logger.debugv(</span><span style="font-weight: 400;">"Delete Sms2fa credential. username = {0}, credentialId = {1}"</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">user.getUsername()</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">credentialId)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">return </span><span style="font-weight: 400;">user.credentialManager().removeStoredCredentialById(credentialId)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">}</span></pre>
<p>Dla interfejsu <code>CredentialInputValidator</code> główną metodą do zaimplementowania jest <code>isValid</code>, która sprawdza, czy dane poświadczenie jest ważne dla danego użytkownika w danej domenie (realm). Jest to metoda wywoływana przez Authenticator, gdy chce zweryfikować dane wprowadzone przez użytkownika.</p>
<pre><span style="font-weight: 400;">@Override</span>
<span style="font-weight: 400;">public boolean </span><span style="font-weight: 400;">isValid(RealmModel realm</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">UserModel user</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">CredentialInput credentialInput) {</span>
<span style="font-weight: 400;">final </span><span style="font-weight: 400;">Sms2faCredentialModel credentialModel = getDefaultCredential(session</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">realm</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">user)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">final </span><span style="font-weight: 400;">String secretData = credentialModel.getSecretData()</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">return </span><span style="font-weight: 400;">secretData != </span><span style="font-weight: 400;">null </span><span style="font-weight: 400;">&amp;&amp; secretData.equals(credentialInput.getChallengeResponse())</span><span style="font-weight: 400;">;</span></pre>
<p>Teraz powinniśmy mieć wszystko, aby móc przejść do implementacji samego Authenticatora.</p>
<p>&nbsp;</p>
<h3><b>AuthenticatorFactory i Authenticator</b></h3>
<p>Klasa <em>SmsAuthenticatorFactory</em> zawiera logikę potrzebną do skonfigurowania i tworzenia instancji <em>SmsAuthenticator</em>, który wykonuje walidację OTP opartą na SMS. Obsługuje dostosowywanie poprzez kilka konfigurowalnych właściwości.</p>
<pre><span style="font-weight: 400;">supports customization through several configurable properties.</span>
<span style="font-weight: 400;">public class </span><span style="font-weight: 400;">SmsAuthenticatorFactory </span><span style="font-weight: 400;">implements </span><span style="font-weight: 400;">AuthenticatorFactory {</span>

<span style="font-weight: 400;">@Override</span>
<span style="font-weight: 400;">public </span><span style="font-weight: 400;">String getId() {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">return </span><span style="font-weight: 400;">"sms-authenticator"</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">}</span>

<span style="font-weight: 400;">@Override</span>
<span style="font-weight: 400;">public </span><span style="font-weight: 400;">String getDisplayType() {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">return </span><span style="font-weight: 400;">"SMS Authentication"</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">}</span>
<span style="font-weight: 400;">@Override</span>
<span style="font-weight: 400;">public </span><span style="font-weight: 400;">String getHelpText() {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">return </span><span style="font-weight: 400;">"Validates an OTP sent via SMS to the users mobile phone."</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">}</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">(…)</span>
<span style="font-weight: 400;">}</span></pre>
<p>Teraz przejdźmy do samego Authenticatora. Główna metoda, na której się skupimy, to <em>sendChallenge()</em>. Kiedy przepływ jest początkowo wyzwalany, ta metoda jest wywoływana. Ważne jest, aby zauważyć, że nie obsługuje ona przetwarzania formularza kodu SMS. Jej rola polega na renderowaniu strony lub kontynuowaniu przepływu.</p>
<p>Strona HTML, która prosi o wprowadzenie otrzymanego kodu, jest prezentowana użytkownikowi, który następnie wprowadza kod i przesyła go. Wówczas wysyłane jest żądanie HTTP do przepływu za pomocą adresu URL akcji określonego w formularzu HTML. To wyzwala metodę <em>action()</em> w naszej implementacji Authenticatora. Jeśli podany kod jest nieprawidłowy, rekonstruujemy formularz HTML, dodając komunikat o błędzie. Następnie używamy metody <em>failureChallenge()</em>, przekazując powód niepowodzenia. Działa ona podobnie do <em>challenge()</em>, ale dodatkowo loguje błąd, co pomaga wykryć ewentualne możliwości ataku.</p>
<pre><span style="font-weight: 400;">private void </span><span style="font-weight: 400;">sendChallenge(AuthenticationFlowContext context) {</span>
<span style="font-weight: 400;">(…)</span>
<span style="font-weight: 400;">credentialModel.setSecretData(code)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">user.credentialManager().updateStoredCredential(credentialModel)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">AuthenticationSessionModel authSession = context.getAuthenticationSession()</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">authSession.setAuthNote(</span><span style="font-weight: 400;">"ttl"</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">Long.toString(System.currentTimeMillis() + (ttl * </span><span style="font-weight: 400;">1000L</span><span style="font-weight: 400;">)))</span><span style="font-weight: 400;">;</span>

<span style="font-weight: 400;">try </span><span style="font-weight: 400;">{</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">/* sending SMS */</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">context.challenge(context.form().setAttribute(</span><span style="font-weight: 400;">"realm"</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">context.getRealm()).createForm(TPL_CODE))</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">} </span><span style="font-weight: 400;">catch </span><span style="font-weight: 400;">(Exception e) {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">context.failureChallenge(AuthenticationFlowError.INTERNAL_ERROR</span><span style="font-weight: 400;">,</span>
<span style="font-weight: 400;">            </span> <span style="font-weight: 400;">context.form().setError(</span><span style="font-weight: 400;">"smsAuthSmsNotSent"</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">e.getMessage())</span>
<span style="font-weight: 400;">                    </span> <span style="font-weight: 400;">.createErrorPage(Response.Status.INTERNAL_SERVER_ERROR))</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">}</span>
<span style="font-weight: 400;">}</span>

<span style="font-weight: 400;">@Override</span>
<span style="font-weight: 400;">public void </span><span style="font-weight: 400;">action(AuthenticationFlowContext context) {</span>
<span style="font-weight: 400;">(…)</span>
<span style="font-weight: 400;">final </span><span style="font-weight: 400;">Sms2faCredentialModel credentialModel = getCredentialProvider(session).getDefaultCredential(session</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">context.getRealm()</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">user)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">boolean </span><span style="font-weight: 400;">isValid = getCredentialProvider(context.getSession()).isValid(context.getRealm()</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">context.getUser()</span><span style="font-weight: 400;">,</span>
<span style="font-weight: 400;">       new </span><span style="font-weight: 400;">UserCredentialModel(credentialModel.getId()</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">getCredentialProvider(context.getSession()).getType()</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">enteredCode))</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">if </span><span style="font-weight: 400;">(isValid) {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">if </span><span style="font-weight: 400;">(Long.parseLong(ttl) &lt; System.currentTimeMillis()) {</span>
<span style="font-weight: 400;">        </span> <span style="font-weight: 400;">// expired</span>
<span style="font-weight: 400;">        </span> <span style="font-weight: 400;">context.failureChallenge(AuthenticationFlowError.EXPIRED_CODE</span><span style="font-weight: 400;">,
</span>
<span style="font-weight: 400;">                </span> <span style="font-weight: 400;">context.form().setError(</span><span style="font-weight: 400;">"smsAuthCodeExpired"</span><span style="font-weight: 400;">).createErrorPage(Response.Status.BAD_REQUEST))</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">} </span><span style="font-weight: 400;">else </span><span style="font-weight: 400;">{</span>
<span style="font-weight: 400;">        </span> <span style="font-weight: 400;">// valid</span>
<span style="font-weight: 400;">        </span> <span style="font-weight: 400;">context.success()</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">}</span>
<span style="font-weight: 400;">} </span><span style="font-weight: 400;">else </span><span style="font-weight: 400;">{</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">context.getEvent().user(user).error(Errors.INVALID_USER_CREDENTIALS)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS</span><span style="font-weight: 400;">,</span>
<span style="font-weight: 400;">            </span> <span style="font-weight: 400;">context.form().setAttribute(</span><span style="font-weight: 400;">"realm"</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">context.getRealm())</span>
<span style="font-weight: 400;">                    </span> <span style="font-weight: 400;">.setError(</span><span style="font-weight: 400;">"smsAuthCodeInvalid"</span><span style="font-weight: 400;">).createForm(TPL_CODE))</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">}</span>
<span style="font-weight: 400;">}</span></pre>
<h3><b>Authentication Flow</b></h3>
<p>Aby dodać Authenticator do przepływu, administrator musi przejść do Konsoli. W sekcji Uwierzytelnianie (Authentication) i zakładce Przepływy (Flows) powinien zobaczyć istniejące przepływy. Wbudowane przepływy nie mogą być bezpośrednio modyfikowane, więc aby zintegrować nowo utworzony Authenticator, musimy albo zduplikować istniejący przepływ, albo stworzyć nowy od podstaw.</p>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-5841 size-full" src="https://inero-software.com/wp-content/uploads/2024/06/2-3.png" alt="" width="1920" height="1080" srcset="https://inero-software.com/wp-content/uploads/2024/06/2-3.png 1920w, https://inero-software.com/wp-content/uploads/2024/06/2-3-300x169.png 300w, https://inero-software.com/wp-content/uploads/2024/06/2-3-1030x579.png 1030w, https://inero-software.com/wp-content/uploads/2024/06/2-3-768x432.png 768w, https://inero-software.com/wp-content/uploads/2024/06/2-3-1536x864.png 1536w, https://inero-software.com/wp-content/uploads/2024/06/2-3-533x300.png 533w" sizes="(max-width: 1920px) 100vw, 1920px" /></p>
<h3><b>Required actions</b></h3>
<p>Jeśli telefon nie jest skonfigurowany, powinniśmy wywołać niestandardowe wymagane działanie. Ponownie, powinniśmy dodać pełną kwalifikowaną nazwę klasy do katalogu <em>META-INF/services</em> i zaimplementować interfejs <em>RequiredActionProvider</em>. Metoda <em>requiredActionChallenge()</em> jest odpowiedzialna za renderowanie HTML, który poprowadzi wymagane działanie.</p>
<pre><span style="font-weight: 400;">@Override</span>
<span style="font-weight: 400;">public void </span><span style="font-weight: 400;">requiredActionChallenge(RequiredActionContext context) {</span>
<span style="font-weight: 400;">LoginFormsProvider form = context.form()</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">if </span><span style="font-weight: 400;">(getSmsAuthenticatorConfig(context) == </span><span style="font-weight: 400;">null</span><span style="font-weight: 400;">) {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">form.setError(</span><span style="font-weight: 400;">"smsAuthMissingAuthenticatorConfig"</span><span style="font-weight: 400;">)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">}</span>
<span style="font-weight: 400;">final </span><span style="font-weight: 400;">Response response = form.createForm(</span><span style="font-weight: 400;">"sms-2fa-register.ftl"</span><span style="font-weight: 400;">)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">context.challenge(response)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">}</span></pre>
<p>Ta część jest odpowiedzialna za przetwarzanie danych wejściowych z formularza HTML wymaganego działania. Po wprowadzeniu otrzymanego kodu SMS, numer telefonu powinien zostać zapisany w bazie danych jako poświadczenie. Przy następnym logowaniu będziemy mogli skorzystać z tej formy OTP.</p>
<pre><span style="font-weight: 400;">@Override</span>
<span style="font-weight: 400;">public void </span><span style="font-weight: 400;">processAction(RequiredActionContext context) {</span>
<span style="font-weight: 400;">    (…)</span>
<span style="font-weight: 400;">final </span><span style="font-weight: 400;">String phoneNumber = params.getFirst(</span><span style="font-weight: 400;">"phoneNumber"</span><span style="font-weight: 400;">)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">final </span><span style="font-weight: 400;">String code = params.getFirst(</span><span style="font-weight: 400;">"code"</span><span style="font-weight: 400;">)</span><span style="font-weight: 400;">;</span>

<span style="font-weight: 400;">if</span><span style="font-weight: 400;">(StringUtils.isBlank(phoneNumber) &amp;&amp; StringUtils.isBlank(authSession.getAuthNote(</span><span style="font-weight: 400;">"phone_number"</span><span style="font-weight: 400;">))) {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">//if no phone number is set, redirect to the first page</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">requiredActionChallenge(context)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">return;</span>
<span style="font-weight: 400;">}</span>
<span style="font-weight: 400;">if </span><span style="font-weight: 400;">(phoneNumber != </span><span style="font-weight: 400;">null</span><span style="font-weight: 400;">) {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">sendPhoneNumberVerificationChallenge(context</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">authSession</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">phoneNumber</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">smsAuthenticatorConfig.getConfig())</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">return;</span>
<span style="font-weight: 400;">}</span>
<span style="font-weight: 400;">if </span><span style="font-weight: 400;">(code != </span><span style="font-weight: 400;">null</span><span style="font-weight: 400;">) {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">/* verify provided SMS code */</span> 
<span style="font-weight: 400;">    </span><span style="font-weight: 400;">}</span>
<span style="font-weight: 400;">}</span></pre>
<p>Ostatnią rzeczą, którą musisz zrobić, jest przejście do Konsoli Administratora i zakładki Wymagane Działania (Required Actions). Twoje nowe działanie powinno teraz być wyświetlone i włączone na liście wymaganych działań.</p>
<p><img loading="lazy" decoding="async" class="wp-image-5840 aligncenter" src="https://inero-software.com/wp-content/uploads/2024/06/1-3-300x169.png" alt="" width="774" height="436" srcset="https://inero-software.com/wp-content/uploads/2024/06/1-3-300x169.png 300w, https://inero-software.com/wp-content/uploads/2024/06/1-3-1030x579.png 1030w, https://inero-software.com/wp-content/uploads/2024/06/1-3-768x432.png 768w, https://inero-software.com/wp-content/uploads/2024/06/1-3-1536x864.png 1536w, https://inero-software.com/wp-content/uploads/2024/06/1-3-533x300.png 533w, https://inero-software.com/wp-content/uploads/2024/06/1-3.png 1920w" sizes="(max-width: 774px) 100vw, 774px" /></p>
<p>Jeśli użytkownik nie podał wcześniej numeru telefonu, a uwierzytelnianie SMS jest ustawione jako wymagane w przepływie uwierzytelniania, powinna pojawić się nowa widok.</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-5842 size-full" src="https://inero-software.com/wp-content/uploads/2024/06/3-3.png" alt="" width="1920" height="1080" srcset="https://inero-software.com/wp-content/uploads/2024/06/3-3.png 1920w, https://inero-software.com/wp-content/uploads/2024/06/3-3-300x169.png 300w, https://inero-software.com/wp-content/uploads/2024/06/3-3-1030x579.png 1030w, https://inero-software.com/wp-content/uploads/2024/06/3-3-768x432.png 768w, https://inero-software.com/wp-content/uploads/2024/06/3-3-1536x864.png 1536w, https://inero-software.com/wp-content/uploads/2024/06/3-3-533x300.png 533w" sizes="(max-width: 1920px) 100vw, 1920px" /></p>
<p>Podsumowując, konfiguracja SMS MFA obejmuje ustawienie niestandardowych wymaganych działań oraz dostawców uwierzytelniania, ale oczywiście istnieją inne kwestie do uwzględnienia, takie jak komunikacja między Keycloak a bramką SMS. Zagadnienia, które omówiliśmy w tym poście, to oczywiście tylko część możliwej konfiguracji, ale najważniejsza i specyficzna dla Keycloak.</p>
<p>Jedną z istotnych zalet wdrożenia SMS MFA jest jego powszechna dostępność, ponieważ większość użytkowników posiada telefony komórkowe zdolne do odbierania wiadomości SMS. Dodatkowo, zapewnia to prostą obsługę dla użytkowników, wymagając minimalnej konfiguracji i znajomości. Jednak mechanizm ten ma swoje wady, w tym potencjalne zagrożenia, takie jak ataki polegające na zamianie kart SIM lub przechwytywaniu kodów SMS. Ponadto, dostarczanie wiadomości SMS może czasami być zawodnym procesem, co prowadzi do opóźnień lub nieudanej dostawy, wpływając na doświadczenie użytkownika. Powinniśmy być tego świadomi przed podjęciem decyzji o zastosowaniu tej metody, szczególnie w erze nowszych rozwiązań, takich jak mobilne aplikacje OTP.</p>
<p><a href="https://inero-software.com/pl/najlepsze-praktyki-w-keycloak-zadbaj-o-bezpieczenstwo-w-5-krokach/"><img loading="lazy" decoding="async" data-attachment-id="5873" data-permalink="https://inero-software.com/pl/tworzenie-niestandardowego-uwierzytelniania-sms-w-keycloak/3-6/" data-orig-file="https://inero-software.com/wp-content/uploads/2024/06/3-4.png" data-orig-size="1200,100" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="3" data-image-description="" data-image-caption="" data-medium-file="https://inero-software.com/wp-content/uploads/2024/06/3-4-300x25.png" data-large-file="https://inero-software.com/wp-content/uploads/2024/06/3-4-1030x86.png" tabindex="0" role="button" class="alignnone wp-image-5873 size-full" src="https://inero-software.com/wp-content/uploads/2024/06/3-4.png" alt="" width="1200" height="100" srcset="https://inero-software.com/wp-content/uploads/2024/06/3-4.png 1200w, https://inero-software.com/wp-content/uploads/2024/06/3-4-300x25.png 300w, https://inero-software.com/wp-content/uploads/2024/06/3-4-1030x86.png 1030w, https://inero-software.com/wp-content/uploads/2024/06/3-4-768x64.png 768w" sizes="(max-width: 1200px) 100vw, 1200px" /></a></p>
<p></p></div><div class="col-sm-1"></div></div>
<p>Artykuł <a href="https://inero-software.com/pl/tworzenie-niestandardowego-uwierzytelniania-sms-w-keycloak/">Tworzenie niestandardowego uwierzytelniania SMS w Keycloak</a> pochodzi z serwisu <a href="https://inero-software.com/pl">Inero Software - Rozwiązania IT i Konsulting</a>.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5868</post-id>	</item>
	</channel>
</rss>
