java - Keycloak 无输入授权
问题描述
我正在尝试编写一个自定义 Keycloak Authenticator,它可以从某个请求中检索用户凭据并动态提交这些凭据以进行身份验证,而最终用户不必手动将它们输入到某个登录表单中。
以这个问题为起点,我在 Keycloak 中创建了我自己的自定义身份验证 SPI。我还根据需要配置了 Keycloak 身份验证流程。
自定义身份验证器
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.authenticators.browser.UsernamePasswordForm;
public class CustomUPForm extends UsernamePasswordForm implements Authenticator {
@Override
public void authenticate(AuthenticationFlowContext context) {
System.out.println("Authenticating....");
Response challenge = context.form().createForm("custom-up-form.ftl");
context.challenge(challenge);
MultivaluedMap<String, String> formData = new MultivaluedHashMap<>();
//Changed here - but otherwise valid credentials
formData.putSingle("username", "xxxxx");
formData.putSingle("password", "xxxxx");
context.form().setFormData(formData);
}
@Override
public void action(AuthenticationFlowContext context) {
System.out.println("Action....");
context.success();
}
}
自定义验证器工厂
import java.util.ArrayList;
import java.util.List;
import org.keycloak.Config;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.AuthenticatorFactory;
import org.keycloak.authentication.ConfigurableAuthenticatorFactory;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;
public class CustomUPFormFactory implements AuthenticatorFactory,
ConfigurableAuthenticatorFactory {
public static final String PROVIDER_ID = "custom-up-form";
public static final CustomUPForm SINGLETON = new CustomUPForm();
@Override
public Authenticator create(KeycloakSession session) {
return SINGLETON;
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public String getDisplayType() {
return "Custom Authenticator";
}
@Override
public String getReferenceCategory() {
return "Reference Category";
}
@Override
public boolean isConfigurable() {
return true;
}
public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
AuthenticationExecutionModel.Requirement.REQUIRED
};
@Override
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
return REQUIREMENT_CHOICES;
}
@Override
public String getHelpText() {
return "POC Custom Authenticator";
}
private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = new ArrayList<>();
static {
/*
Add properties here
*/
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return CONFIG_PROPERTIES;
}
@Override
public boolean isUserSetupAllowed() {
return false;
}
}
下面是我的自定义登录表单,它基于 Keycloak“秘密问题”SPI 示例中提供的表单here
<#import "template.ftl" as layout>
<@layout.registrationLayout; section>
<#if section = "title">
${msg("loginTitle",realm.name)}
<#elseif section = "header">
${msg("loginTitleHtml",realm.name)}
<#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!}">Login</label>
</div>
<div class="${properties.kcInputWrapperClass!}">
<input id="totp" name="username" type="text" class="${properties.kcInputClass!}" />
<input id="totp" name="password" type="password" 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.kcButtonBlockClass!} ${properties.kcButtonLargeClass!}"
name="login" id="kc-login" type="submit" value="${msg("doLogIn")}"/>
</div>
</div>
</div>
</form>
</#if>
</@layout.registrationLayout>
所有组件都呈现正常,但是当我通过将它们添加到表单数据来动态尝试传递我的凭据时,我不断收到“无效的用户名/密码”响应。
如何在不让用户手动输入的情况下传递用户名和密码组合?
解决方案
问题是我试图在 的范围内执行此操作authenticate()
,而它应该包含在action()
.
此外,MultivaluedMap
包含表单数据的内容应该来自当前包含的 HTTP 请求Context
以下解决方案来自这个问题
public class CustomUPForm extends UsernamePasswordForm implements Authenticator {
@Override
public void authenticate(AuthenticationFlowContext context) {
Response challenge = context.form()
.createForm("custom-up-form.ftl");
context.challenge(challenge);
}
@Override
public void action(AuthenticationFlowContext context) {
System.out.println("Processing form...");
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
formData.putSingle("username", "xxxxx");
formData.putSingle("password", "xxxxx");
if (!validateForm(context, formData)) {
return;
}
context.success();
}
}
请注意,在此示例validateForm()
中包含一些自定义验证逻辑,这对于此问题的范围不是必需的。但是用户名和密码的值可以通过调用来提取getFirst()
formData.getFirst("username");
推荐阅读
- angular - RxJS combineLatest 类型
- java - 使用 Gson 操作 JSON
- python - 存根 AWS 资源以引发 ClientError
- python - python中__getitem__的时间复杂度
- javascript - 如何通过函数传递正则表达式?
- wordpress - 删除 Wordpress 中子文件夹的 Yoast 架构
- sql - Oracle中自己的聚合函数
- reactjs - 如何使用 Jest 酶在 React Redux 应用程序中进行测试?
- javascript - 在 AppScripts Google Sheets 中将字符串转换为时间格式
- c# - 假设在编写扩展方法时实现了 ToString()