refactor auth-spi, auth demo, and docs
This commit is contained in:
parent
1f13f6372a
commit
6d7be80930
9 changed files with 83 additions and 19 deletions
|
@ -624,8 +624,8 @@ public class SecretQuestionRequiredActionFactory implements RequiredActionFactor
|
|||
<title>Enable Required Action</title>
|
||||
<para>
|
||||
The final thing you have to do is go into the admin console. Click on the Authentication left menu.
|
||||
Click on the Required Actions tab. Find your required action, and enable. Alternatively, if you
|
||||
click on the default action checkbox, this required action will be applied anytime a new user is created.
|
||||
Click on the Required Actions tab. Click on the Register button and choose your new Required Action.
|
||||
Your new required action should now be displayed and enabled in the required actions list.
|
||||
</para>
|
||||
</section>
|
||||
</section>
|
||||
|
|
|
@ -14,13 +14,19 @@ Then registering the provider by editing keycloak-server.json and adding the mod
|
|||
],
|
||||
|
||||
|
||||
You then have to copy the secret-question.ftl file to the standalone/configuration/themes/base/login directory.
|
||||
You then have to copy the secret-question.ftl and secret-question-config.ftl files to the standalone/configuration/themes/base/login directory.
|
||||
|
||||
After you do all this, you then have to reboot keycloak. When reboot is complete, you will need to log into
|
||||
the admin console to create a new flow with your new authenticator.
|
||||
|
||||
If you go to the Authentication menu item and go to the Flow tab, you will be able to view the currently
|
||||
defined flows. You cannot modify an built in flows, so, to add the Authenticator you
|
||||
have to copy an existing flow or create your own. I'm hoping the UI is intuitive enough so that you
|
||||
can figure out for yourself how to create a flow and add the Authenticator. We're looking to add a screencast
|
||||
have to copy an existing flow or create your own.
|
||||
|
||||
Next you have to register your required action.
|
||||
Click on the Required Actions tab. Click on the Register button and choose your new Required Action.
|
||||
Your new required action should now be displayed and enabled in the required actions list.
|
||||
|
||||
I'm hoping the UI is intuitive enough so that you
|
||||
can figure out for yourself how to create a flow and add the Authenticator and Required Action. We're looking to add a screencast
|
||||
to show this in action.
|
||||
|
|
33
examples/providers/authenticator/secret-question-config.ftl
Executable file
33
examples/providers/authenticator/secret-question-config.ftl
Executable file
|
@ -0,0 +1,33 @@
|
|||
<#import "template.ftl" as layout>
|
||||
<@layout.registrationLayout; section>
|
||||
<#if section = "title">
|
||||
${msg("loginTitle",realm.name)}
|
||||
<#elseif section = "header">
|
||||
Setup Secret Question
|
||||
<#elseif section = "form">
|
||||
<form id="kc-totp-login-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div class="${properties.kcLabelWrapperClass!}">
|
||||
<label for="totp" class="${properties.kcLabelClass!}">What is your mom's first name?</label>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input id="totp" name="secret_answer" type="text" class="${properties.kcInputClass!}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="${properties.kcFormGroupClass!}">
|
||||
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
|
||||
<div class="${properties.kcFormOptionsWrapperClass!}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
|
||||
<div class="${properties.kcFormButtonsWrapperClass!}">
|
||||
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="login" id="kc-login" type="submit" value="${msg("doSubmit")}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</#if>
|
||||
</@layout.registrationLayout>
|
|
@ -13,6 +13,8 @@ import org.keycloak.services.util.CookieHelper;
|
|||
import javax.ws.rs.core.Cookie;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -24,7 +26,11 @@ public class SecretQuestionAuthenticator implements Authenticator {
|
|||
|
||||
protected boolean hasCookie(AuthenticationFlowContext context) {
|
||||
Cookie cookie = context.getHttpRequest().getHttpHeaders().getCookies().get("SECRET_QUESTION_ANSWERED");
|
||||
return cookie != null;
|
||||
boolean result = cookie != null;
|
||||
if (result) {
|
||||
System.out.println("Bypassing secret question because cookie as set");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -33,12 +39,17 @@ public class SecretQuestionAuthenticator implements Authenticator {
|
|||
context.success();
|
||||
return;
|
||||
}
|
||||
Response challenge = context.form().createForm("secret_question.ftl");
|
||||
Response challenge = context.form().createForm("secret-question.ftl");
|
||||
context.challenge(challenge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void action(AuthenticationFlowContext context) {
|
||||
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
||||
if (formData.containsKey("cancel")) {
|
||||
context.cancelLogin();
|
||||
return;
|
||||
}
|
||||
boolean validated = validateAnswer(context);
|
||||
if (!validated) {
|
||||
Response challenge = context.form()
|
||||
|
@ -58,11 +69,12 @@ public class SecretQuestionAuthenticator implements Authenticator {
|
|||
maxCookieAge = Integer.valueOf(config.getConfig().get("cookie.max.age"));
|
||||
|
||||
}
|
||||
URI uri = context.getUriInfo().getBaseUriBuilder().path("realms").path(context.getRealm().getName()).build();
|
||||
CookieHelper.addCookie("SECRET_QUESTION_ANSWERED", "true",
|
||||
context.getUriInfo().getBaseUri().getPath() + "/realms/" + context.getRealm().getName(),
|
||||
uri.getRawPath(),
|
||||
null, null,
|
||||
maxCookieAge,
|
||||
true, true);
|
||||
false, true);
|
||||
}
|
||||
|
||||
protected boolean validateAnswer(AuthenticationFlowContext context) {
|
||||
|
|
|
@ -20,14 +20,14 @@ public class SecretQuestionRequiredAction implements RequiredActionProvider {
|
|||
|
||||
@Override
|
||||
public void requiredActionChallenge(RequiredActionContext context) {
|
||||
Response challenge = context.form().createForm("secret_question_config.ftl");
|
||||
Response challenge = context.form().createForm("secret-question-config.ftl");
|
||||
context.challenge(challenge);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processAction(RequiredActionContext context) {
|
||||
String answer = (context.getHttpRequest().getDecodedFormParameters().getFirst("answer"));
|
||||
String answer = (context.getHttpRequest().getDecodedFormParameters().getFirst("secret_answer"));
|
||||
UserCredentialValueModel model = new UserCredentialValueModel();
|
||||
model.setValue(answer);
|
||||
model.setType(SecretQuestionAuthenticator.CREDENTIAL_TYPE);
|
||||
|
|
|
@ -217,4 +217,11 @@ public interface AuthenticationFlowContext {
|
|||
* @return
|
||||
*/
|
||||
URI getActionUrl();
|
||||
|
||||
/**
|
||||
* End the flow and redirect browser based on protocol specific respones. This should only be executed
|
||||
* in browser-based flows.
|
||||
*
|
||||
*/
|
||||
void cancelLogin();
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import org.keycloak.models.KeycloakSession;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.protocol.LoginProtocol;
|
||||
import org.keycloak.protocol.oidc.TokenManager;
|
||||
import org.keycloak.services.ErrorPage;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
|
@ -367,6 +368,17 @@ public class AuthenticationProcessor {
|
|||
public URI getActionUrl() {
|
||||
return getActionUrl(generateAccessCode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelLogin() {
|
||||
getEvent().error(Errors.REJECTED_BY_USER);
|
||||
LoginProtocol protocol = getSession().getProvider(LoginProtocol.class, getClientSession().getAuthMethod());
|
||||
protocol.setRealm(getRealm())
|
||||
.setHttpHeaders(getHttpRequest().getHttpHeaders())
|
||||
.setUriInfo(getUriInfo());
|
||||
Response response = protocol.cancelLogin(getClientSession());
|
||||
forceChallenge(response);
|
||||
}
|
||||
}
|
||||
|
||||
public void logFailure() {
|
||||
|
|
|
@ -26,13 +26,7 @@ public class UsernamePasswordForm extends AbstractUsernameFormAuthenticator impl
|
|||
public void action(AuthenticationFlowContext context) {
|
||||
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
|
||||
if (formData.containsKey("cancel")) {
|
||||
context.getEvent().error(Errors.REJECTED_BY_USER);
|
||||
LoginProtocol protocol = context.getSession().getProvider(LoginProtocol.class, context.getClientSession().getAuthMethod());
|
||||
protocol.setRealm(context.getRealm())
|
||||
.setHttpHeaders(context.getHttpRequest().getHttpHeaders())
|
||||
.setUriInfo(context.getUriInfo());
|
||||
Response response = protocol.cancelLogin(context.getClientSession());
|
||||
context.forceChallenge(response);
|
||||
context.cancelLogin();
|
||||
return;
|
||||
}
|
||||
if (!validateForm(context, formData)) {
|
||||
|
|
|
@ -167,7 +167,7 @@ public class AccountTest {
|
|||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
//@Test
|
||||
public void ideTesting() throws Exception {
|
||||
Thread.sleep(100000000);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue