keycloak~RequiredActionProvider的使用

张占岭發表於2024-04-11

使用場景

RequiredActionProvider,它是在認證過程中,需要當前登入的使用者執行個性化的動作;當使用者符合條件,就被執行RequiredActionProvider對作,當RequiredActionProvider沒有正常提交(context.success())之前,當前使用者仍然是未登入狀態,這在keycloak框架中,也有一些預設的個性化動作,它與整個登入流程是解耦的,事實上,keycloak的設計理念也是微架構設計,外掛化設計。

keycloak預設提供的RequiredActionProvider

  • VERIFY_EMAIL 驗證郵箱
  • UPDATE_PROFILE 更新使用者資訊
  • CONFIGURE_TOTP 配置totp多因子認證
  • UPDATE_PASSWORD 強制更新密碼,用在臨時建立的密碼場景(CredentialRepresentation中的isTemporary為true時執行)
  • TERMS_AND_CONDITIONS 使用者在首次登入時會被要求檢視並接受特定的服務條款和條件
  • VERIFY_PROFILE 驗證個人資訊

keycloak後臺配置RequiredActionProvider

在側-驗證選單,選擇Required Action標籤,可以管理它們,開啟或者設定成預設,同時也可以新增自定義的RequiredActionProvider

1 配置列表

2 新增新的Required Action

自定義的RequiredActionProvider

下面我們新增一個自定義的RequiredActionProvider,業務場景是,當登入使用者名稱字首是test時,就讓這個使用者去驗證手機號

1 新增一個UpdatePhoneNumberRequiredAction檔案,讓它實現RequiredActionProvider介面

public class UpdatePhoneNumberRequiredAction implements RequiredActionProvider {

    public static final String PROVIDER_ID = "UPDATE_PHONE_NUMBER";

    @Override
    public void evaluateTriggers(RequiredActionContext context) {
    }

    @Override
    public void requiredActionChallenge(RequiredActionContext context) {
        Response challenge = context.form()
                .createForm("login-update-phone-number.ftl");
        context.challenge(challenge);
    }

    @Override
    public void processAction(RequiredActionContext context) {
        TokenCodeServiceProvider tokenCodeServiceProvider = context.getSession().getProvider(TokenCodeServiceProvider.class);
        String phoneNumber = context.getHttpRequest().getDecodedFormParameters().getFirst("phoneNumber");
        String code = context.getHttpRequest().getDecodedFormParameters().getFirst("code");
        try {
            tokenCodeServiceProvider.validateCode(context.getUser(), phoneNumber, code);
            context.success();
        } catch (BadRequestException e) {

            Response challenge = context.form()
                    .setError("noOngoingVerificationProcess")
                    .createForm("login-update-phone-number.ftl");
            context.challenge(challenge);

        } catch (ForbiddenException e) {

            Response challenge = context.form()
                    .setAttribute("phoneNumber", phoneNumber)
                    .setError("verificationCodeDoesNotMatch")
                    .createForm("login-update-phone-number.ftl");
            context.challenge(challenge);
        }
    }

    @Override
    public void close() {
    }
}

2 新增UpdatePhoneNumberRequiredActionFactory檔案,讓它去構建上面的UpdatePhoneNumberRequiredAction例項

public class UpdatePhoneNumberRequiredActionFactory implements RequiredActionFactory {

    private static final UpdatePhoneNumberRequiredAction instance = new UpdatePhoneNumberRequiredAction();

    @Override
    public String getDisplayText() {
        return "";
    }

    @Override
    public RequiredActionProvider create(KeycloakSession session) {
        return instance;
    }

    @Override
    public void init(Scope scope) {
    }

    @Override
    public void postInit(KeycloakSessionFactory sessionFactory) {
    }

    @Override
    public void close() {
    }

    @Override
    public String getId() {
        return UpdatePhoneNumberRequiredAction.PROVIDER_ID;
    }
}

3 在resources/META-INF/services/資料夾下,新增org.keycloak.authentication.RequiredActionFactory檔案,透過SPI的方式,註冊咱們的UpdatePhoneNumberRequiredActionFactory工廠

org.keycloak.phone.authentication.requiredactions.UpdatePhoneNumberRequiredActionFactory

4 新增咱們這個UpdatePhoneNumberRequiredActionFactory,它在keycloak後臺RequiredActionProvider中,顯示的名稱是“Update Phone Number”,我們去新增並開啟它

5 在brower的認證流程中,你需要在context.success()之前去判斷使用者名稱的字首,併為它指定RequiredAction,如果是對所有使用者有效的,那不需要新增以下程式碼,可以把它在keycloak後臺,設定為“預設”行為即可。

  • 要想使RequiredAction生效,需要先在keycloak後臺啟用它
  • 如果希望對新建使用者啟用它,需要先在keycloak後臺啟用它,並開啟“預設”選項【預設是對新使用者來說的,老使用者不受這個值的控制,就是說你新建一個requiredAction,沒有任何規則,如果你開啟+預設,那它只對所有新建使用者有效】
  • 要想對某些規則的使用者啟用它,需要在form表單認證時,新增對應的業務邏輯,可以新增自定義的"Authenticator"來實現這個邏輯,儘量不修改之前的核心程式碼,注意,如果你在後臺開啟了預設選項,那對於新使用者也會進入的,不會受規則的約束;為了解決這個問題,我們在新增規則時,對不符合的使用者應該新增context.getUser().removeRequiredAction的邏輯程式碼。
  • 當前使用者執行的RequiredAction步驟,會在資料表user_required_action中儲存,使用者每次登入都會檢查這個表的狀態,如果使用者存在這表裡,你的對應的RequiredAction關閉了,事實上,當前這個老使用者在登入時依然會走這個RequiredAction邏輯。
  • 注意,這個user_required_action表產生的資料會有快取,只刪除資料表記錄是不起作用的,需要重啟keycloak
  • 這個user_required_action表裡對應的使用者資料,當使用者成功驗證後,這條資料會被刪除,下次使用者再登入,就不會出現required_action了
  if(context.getUser().getUsername().startsWith("test")){
      context.getUser().addRequiredAction(UpdatePhoneNumberRequiredAction.PROVIDER_ID);
    }else{ // 避免開啟預設行為時,新使用者受到影響
      context.getUser().removeRequiredAction(UpdatePhoneNumberRequiredAction.PROVIDER_ID);
    }
  context.success();

整個RequiredAction配置和執行的流程

好了,到目前來說,咱們使用者名稱登入時,字首為test的使用者,都會走這個手機驗證的介面了,在驗證成功前,使用者是不能直接登入的。

相關文章