参考官方开发示例,使用Java为Keycloak系统编写一套个人隐私问题的认证模块

代码编写完成后,借助Maven将项目打包为jar文件,传输至集群主节点准备部署

注意在打包时需要添加resources/META-INF/services目录,并注册Factory类

导入插件

首先创建初始ConfigMap:

kubectl create configmap secret-question-plugin --from-file=SecretQuestion.jar=./kc-plugins/SecretQuestion.jar -n keycloak

然后执行kubectl edit statefulset keycloak -n keycloak对keycloak的StatefulSet进行修改:

spec:
  template:
    spec:
      volumes:
      - name: plugins-volume
        configMap:
          name: secret-question-plugin
      containers:
      - name: keycloak
        # 其他配置...
        volumeMounts:
        - name: plugins-volume
          mountPath: /opt/keycloak/providers/SecretQuestion.jar
          subPath: SecretQuestion.jar

更新插件

之后每次更新迭代插件时,需要先删除原ConfigMap,然后创建新ConfigMap,最后重启Pod即可

# 删除旧的ConfigMap
kubectl delete configmap secret-question-plugin -n keycloak

# 创建新的ConfigMap
kubectl create configmap secret-question-plugin --from-file=SecretQuestion.jar=./kc-plugins/SecretQuestion.jar -n keycloak

# 重启Keycloak Pod
kubectl delete pod keycloak-0 -n keycloak

插件代码解析

将插件导入Keycloak系统后,需要先在【身份验证】【必需的操作】处将之开启,然后将自定义的认证执行器插入流程之中,如此才能使新认证功能发挥作用

这里将新认证器放置在“账户密码验证”之后且作为必需行动

  1. 当用户首先进入认证界面(账户密码界面)时,仅账户密码认证执行器在发挥作用

  2. 当用户点击登录按钮并通过,根据流程,系统应该执行必需的密保问题验证:

    1. 调用Authenticator中的configuredFor()方法,检查用户是否已经配置过相应凭证

      关于用户是否配置过相应凭证,可以在UI界面的用户详细信息中查看,一般未配置的话只有password口令凭证

    2. configuredFor()返回true,代表用户配置过这种凭证,则调用此类的authenticate()方法正式进入认证:

      1. authenticate()向用户呈现挑战页面,用户输入答案点击提交后调用action()方法
      2. action()方法从上下文获取用户输入的答案并封装,交由Provider提供的isValid()方法验证答案正确性
      3. 若答案错误,则构建失败页面并呈现,将上下文设置为失败状态
      4. 若答案正确,则将上下文设置为成功状态
    3. configuredFor()返回false,代表用户从未配置过这种凭证,但系统流程中又设置了“必需”执行此操作,因此会调用此类的setRequiredActions()所注册的Action相应方法进行操作(此时的情况是需要用户现场创建一个此类型凭证并储存)

      1. setRequiredActions()中使用一个ID字符串来注册操作,这个ID定义在实现了RequiredActionProvider接口的RequiredAction类中,作为静态不可修改变量
      2. RequiredAction类调用requiredActionChallenge()方法呈现一个添加密保问题的界面,用户输入问题答案后点击提交调用processAction()方法
      3. processAction()方法从上下文提取用户输入信息,再从上下文的session中提取Provider,调用Provider中存储新凭证的createCredential()方法即可保存凭证
      4. 将上下文设置为成功状态
  3. 用户通过密保问题,完成认证

如此来看,Authenticator类、RequiredAction类以及相应的两个Factory类都是工作在逻辑上的业务层以及应用层

CredentialModelCredentialProvider则是工作在数据层,其中CredentialModel更偏向POJO的感觉,仅仅是负责在内存中保存、装填或获取Model对象;CredentialProvider则负责信息的交互与存储,包括检查是否已配置凭证检查答案与存储答案是否一致在数据库中增加或删除凭证等操作,这些都是需要直接与数据库进行交互查询的(虽然查询交互过程也被提前封装了)