介绍

Keycloak中的自定义SPI种类有很多,在设计认证流时,可添加三种类型:

  1. 执行器(Execution)

    在主流程中标签为execution;在子流程中标签为step

  2. 条件(Condition)

    标签为condition

  3. 子流程(Sub-flow)

    标签为flow

执行器用于直接执行某项认证,例如要求用户输入账户名密码、要求用户进行TOTP认证等,通常包括一个或多个用于认证的前端页面以与用户交互

条件用于根据某项信息进行决策判断,决定是否执行所在的流程或子流程,一般不包括前端页面(因为只需要与系统内信息交互,不会参与用户操作过程)

子流程用于划分更精细的认证流程,通常与条件共同构成认证流的某一个条件分支

接下来开发一个最简单的自定义条件类型SPI,作用是负责判断当前上下文中的属性registeringDevice的值是否为true,如果为true,则条件成立,否则不成立

开发

不同于开发自定义认证器SPI需要继承、实现多个类或接口,自定义条件SPI只需要一个实现了ConditionalAuthenticator接口的类以及相应地实现了ConditionalAuthenticatorFactory接口的工厂类即可


编写RegisterDeviceConditionAuthenticator类,实现ConditionalAuthenticator接口:

public class RegisterDeviceConditionAuthenticator implements ConditionalAuthenticator {
    @Override
    public boolean matchCondition(AuthenticationFlowContext authenticationFlowContext) {
        String registeringDevice = authenticationFlowContext.getAuthenticationSession().getClientNote("registeringDevice");
        return "true".equals(registeringDevice);
    }

    @Override
    public void action(AuthenticationFlowContext authenticationFlowContext) {}

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

    @Override
    public void setRequiredActions(KeycloakSession keycloakSession, RealmModel realmModel, UserModel userModel) {}

    @Override
    public void close() {}
}

这个类唯一需要关注的就是matchCondition()方法,这个方法定义了【何种条件下条件成立】,在这里表现为读取registeringDevice属性并返回是否为"true"


接下来编写RegisterDeviceConditionAuthenticatorFactory类,实现ConditionalAuthenticatorFactory接口:

public class RegisterDeviceConditionAuthenticatorFactory implements ConditionalAuthenticatorFactory {
    public static final String PROVIDER_ID = "register-device-condition";
    private static final ConditionalAuthenticator SINGLETON = new RegisterDeviceConditionAuthenticator();
    private static AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
            AuthenticationExecutionModel.Requirement.REQUIRED,
            AuthenticationExecutionModel.Requirement.DISABLED
    };

    @Override
    public String getDisplayType() {
        return "Condition - 注册设备时触发";
    }

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

    @Override
    public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
        return REQUIREMENT_CHOICES;
    }

    @Override
    public boolean isUserSetupAllowed() {
        return false;
    }

    @Override
    public String getHelpText() {
        return "只有在注册设备流程中才会执行子认证器";
    }

    @Override
    public List<ProviderConfigProperty> getConfigProperties() {
        return null;
    }

    @Override
    public void init(Config.Scope scope) {}

    @Override
    public void postInit(KeycloakSessionFactory keycloakSessionFactory) {}

    @Override
    public void close() {}

    @Override
    public String getId() {
        return PROVIDER_ID;
    }
    // create()实际上是执行了getSingleton(),因此重写create()与重写getSingleton()是等价的
    @Override
    public ConditionalAuthenticator getSingleton() {
        return SINGLETON;
    }
}

这个工厂类定义了该条件SPI将如何展示于Keycloak系统中,并设置了“必需”、“禁用”两种可选选项,用于开关功能

重要提示

需要注意getDisplayType()返回的字符串内容,如果不是以“Condition - ”开头,则此自定义SPI会被Keycloak识别为执行器而非条件,而与是否实现了ConditionalAuthenticatorFactory接口无关!

(暂未测试被识别为执行器的情况下自定义SPI还能否正常工作)


最后,需要在项目目录下META-INF/services/org.keycloak.authentication.AuthenticatorFactory文件中注册该自定义SPI的工厂类,在该文件中添加一行:org.keycloak.devauth.RegisterDeviceConditionAuthenticatorFactory,即工厂类的全限定类名

注意
虽然是条件类型的SPI,但与执行器相似,都需要在org.keycloak.authentication.AuthenticatorFactory中注册工厂类

效果

打包项目后,将对应jar包放入providers/目录中,重启Keycloak,即可看到该自定义条件SPI已被加载:

将条件加入某个子流程,子流程设置为“基于一定条件”、条件设置为“必需”,即可通过条件成立与否来控制子流程的执行