java - 使用 Keycloak Script Mapper 聚合声明中角色的属性

标签 java keycloak nashorn

我们有一个 Keycloak 脚本映射器来将角色的属性添加到 ID token 。目标是聚合角色属性中可用的值。映射器看起来像这样:

/**
 * Merge with concatenation for the values of attributes obtained
 * from the token with the attributes obtained from the roles. If for
 * example a group and a role have the same attribute key, the values
 * for that key from the group and role will be concatenated.
 *
 * Known limitations:
 * When only roles have a certain attribute, and not a group, the 
 * mapper only uses the first role it can find. Bug was difficult to
 * fix because state in the variable currentClaims seems to be
 * persisted over multiple calls.
 * Workaround: Also add this specific attribute to a group with a
 * dummy value.
 * 
 * NOTE: there is no role attribute mapper out-of-the-box in 
 * Keycloak.
 * 
 * Available variables in script: 
 * user - the current user
 * realm - the current realm
 * token - the current token
 * userSession - the current userSession
 * keycloakSession - the current keycloakSession
 * 
 * Documentation on available variables:
 * https://stackoverflow.com/a/52984849
 */
 
var currentClaims = {};
token.getOtherClaims().forEach(function(k, v) {
  currentClaims[k] = v;
});

function isMultiValued(v) {
  // From experience, multivalued attribute values are sometimes 
  // Arrays and sometimes Objects. Thus look for negative case:
  // anything other than a string is multivalued.
  return !(typeof v === 'string' || v instanceof String);
}

function addToList(l, values) {
  for each(var v in values) {
    l.add(v);
  }
  return l;
}

function toStringArray(arr) {
  return Java.to(arr, "java.lang.String[]");
}

user.getRealmRoleMappings().forEach(function(roleModel) {
  roleModel.getAttributes().forEach(function(k, v) {
    var currentValue = currentClaims[k];
    if (k in currentClaims) {
      if (!isMultiValued(currentValue)) {
        v = toStringArray([currentValue].concat(v));
      } else {
        v = addToList(currentValue, v);
      }
    }
    currentClaims[k] = v; // <= to also aggregate over roles!
    token.setOtherClaims(k, v); 
  }); 
});

我添加了 currentClaims[k] = v 的部分来聚合角色中可用的值,这样如果两个角色包含相同的属性,它们的值也会被聚合。

例如,如果我们有一个用户具有角色 a 和 b,其属性 foo 的值分别为 1 和 2,我们希望 ID token 包含对 foo 的声明,其值为 1 和 2。

user:
  role a foo -> 1
  role b foo -> 2
expected ID token:
  foo -> [1, 2]

但是对于当前代码,currentClaims 变量似乎在多次调用该函数时保持状态。每次检查 ID token 时,都会将更多 2 的值添加到 token 中,从而导致类似 [1, 2, 2, ..., 2] 的 foo 声明每次检索 token 时都会添加越来越多的 2。我尝试将整个调用包装在一个函数中,以便在调用之间丢弃可能的状态,但无济于事。这是结果:

aggregation

为什么状态会在多次调用中保持不变?有没有办法也聚合角色属性的值?

最佳答案

Why is the state kept over multiple calls?

未知。也许与 Nashorn 有关。

[I]s there a way to also aggregate the values for the role attributes?

要不保持聚合值检查 addToList 函数中是否已经存在(解决方法):

function addToList(l, values) {
  for each(var v in values) {
    if (!l.contains(v)) {
      l.add(v);
    }
  }
  return l;
}

并通过保持某种状态进行聚合:

var newClaims = {};
newClaims = currentClaims;
user.getRealmRoleMappings().forEach(function(roleModel) {
  roleModel.getAttributes().forEach(function(k, v) {
    var currentValue = newClaims[k];
    if (k in newClaims) {
      if (isMultiValued(currentValue)) {
        v = addToList(currentValue, v);
      }
    }
    newClaims[k] = v;
  }); 
});
token.setOtherClaims("new-claims", newClaims); 

这会将所有声明放入 JWT 的 new-claims 字段中。

////编辑:

注意:addToList 中的这个 JavaScript/Java 互操作有问题(比如更改角色的实际属性...)。最好使用纯 JavaScript 进行连接,然后再转换回 Java:

function toStringArray(arr) {
  return Java.to(arr, "java.lang.String[]");
}


Array.prototype.includes = function(obj) {
  var i = this.length;
  while (i--) {
    if (this[i] === obj) {
      return true;
    }
  }
  return false;
}

function addToList(l, values) {
  // Add values to new instance to avoid magical addition to
  // current values in role attributes
  var arr = [];
  for each(var v in l) {
    arr.push(v);
  }
  for each(var v in values) {
    if (!arr.includes(v)) {
      arr.push(v);
    }
  }
  return toStringArray(arr);
}

关于java - 使用 Keycloak Script Mapper 聚合声明中角色的属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68352004/

相关文章:

java - 在 Eclipse 中使用 git 的目录结构

java - Apache FTPClient 在文件即将下载完毕时无法检索文件

playframework - Keycloak资源授权无需适配器

javascript - OWASP ZAP 被动扫描脚本

java - Nashorn访问非静态Java方法

java - 从不同语言调用库函数

java - Wicket 中哪种选项卡式面板组合与 JQuery 和 KendoUI 结合使用效果更好?

docker - 如何在 standalone.xml 中注册 keycloak 模块(docker 上的 keycloak)

oauth - 如何通过 API 在 keycloak 中忘记密码

java - 我应该为每个线程使用单独的 ScriptEngine 和 CompiledScript 实例吗?