【问题标题】:Keycloak: Authenticate user with a custom JWTKeycloak:使用自定义 JWT 对用户进行身份验证
【发布时间】:2018-07-16 06:06:26
【问题描述】:

情况:我们使用 keycloak 通过使用 JavaScript 适配器的正常浏览器身份验证流程来对我们的 Web 应用程序 (A) 中的用户进行身份验证。这完美无缺!

目标:现在,一组新用户应该能够访问 A。但是他们在没有 Keycloak 的受信任的第三方应用程序 (B) 中使用用户名和密码登录。在 B 中,他们有一个指向 A 的链接,其中包含自定义 JWT(基本上包含用户名和角色)作为查询参数。因此,当用户单击链接时,他会到达我们的应用程序入口点,我们可以在该入口点从 URL 中读取 JWT。现在需要发生的是某种代币交换。我们希望将此自定义 JWT 发送到 Keycloak,后者将类似于正常登录过程的访问令牌发送回。

问题: Keycloak 中是否有针对此类用例的内置支持?

尝试:

按照文档中的建议,我尝试使用“签名 JWT”作为“Client Authenticator”创建一个机密客户端。经过一些测试,我认为这不是正确的轨道,即使这个名字很有希望。

另一个轨道是“Client suggested identity provider”,通过实现自定义身份提供程序。但我不明白如何在请求中发送 JWT。

目前我正在尝试使用Autentication SPI 来扩展具有自定义身份验证器的身份验证流程。

也许它比我想象的要简单得多。谁能引导我走向正确的方向?

【问题讨论】:

  • We now want to integrate this in keycloak。这是一个范围很广的句子。你到底想做什么?
  • @XtremeBiker:感谢您的评论。我试图以更好的方式重新提出这个问题......

标签: authentication jwt keycloak


【解决方案1】:

所以我终于能够使用问题中提到的身份验证 SPI 解决它。

在 Keycloak 中,我复制了“浏览器”身份验证流程(因为您无法修改内置流程)并引入了一个附加步骤“Portal JWT”(见下图)。然后我将它绑定到“绑定”选项卡中的“浏览器流”

“Portal JWT”后面是我的自定义身份验证器,它从重定向 uri 中的查询参数中提取 JWT,并对其进行解析以从中获取用户名和角色。然后使用自定义属性“isExternal”将用户添加到 keycloak。这是它的摘录:

public class JwtAuthenticator implements Authenticator {

private final JwtReader reader;

JwtAuthenticator(JwtReader reader) {
    this.reader = reader;
}

@Override
public void authenticate(AuthenticationFlowContext context) {
    Optional<String> externalCredential = hasExternalCredential(context);
    if (externalCredential.isPresent()) {
        ExternalUser externalUser = reader.read(context.getAuthenticatorConfig(), externalCredential.get());
        String username = externalUser.getUsername();
        UserModel user = context.getSession().users().getUserByUsername(username, context.getRealm());
        if (user == null) {
            user = context.getSession().users().addUser(context.getRealm(), username);
            user.setEnabled(true);
            user.setSingleAttribute("isExternal", "true");
        }
        for (String roleName : externalUser.getRoles()) {
            RoleModel role = context.getRealm().getRole(roleName);
            if (role == null) {
                role = context.getRealm().addRole(roleName);
            }
            user.grantRole(role);
        }
        context.setUser(user);
        context.success();
    } else {
        context.attempted();
    }
}

private Optional<String> hasExternalCredential(AuthenticationFlowContext context) {
    String redirectUri = context.getUriInfo().getQueryParameters().getFirst("redirect_uri);
    try {
        List<NameValuePair> queryParams = URLEncodedUtils.parse(new URI(redirectUri), "UTF-8");
        Optional<NameValuePair> jwtParam = queryParams.stream()
                .filter(nv -> "jwt".equalsIgnoreCase(nv.getName())).findAny();
        if (jwtParam.isPresent()) {
            String jwt = jwtParam.get().getValue();
            if (LOG.isDebugEnabled()) {
                LOG.debug("JWT found: " + jwt);
            }
            return Optional.of(jwt);
        }
    } catch (URISyntaxException e) {
        LOG.error("Redirect URL not as expected: " + redirectUri);
    }
    return Optional.empty();
}

【讨论】:

  • 我尝试过可以在服务器信息中看到我自己的身份验证器,但在身份验证菜单的 AuthType/Provider 列表中看不到。你能帮我么。谢谢
猜你喜欢
  • 2020-04-22
  • 1970-01-01
  • 1970-01-01
  • 2018-05-17
  • 2016-04-07
  • 2021-12-09
  • 2020-01-03
  • 2017-03-22
  • 2021-05-17
相关资源
最近更新 更多