使用 Keycloak Script Mapper 聚合声明中角色的属性
Aggregate attributes of roles in claims using Keycloak Script Mapper
我们有一个 Keycloak Script Mapper 可以将角色的属性添加到 ID 令牌。目标是聚合角色属性中可用的值。映射器如下所示:
/**
* 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:
*
*/
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 令牌包含对 foo 的声明,其值为 1 和 2。
user:
role a foo -> 1
role b foo -> 2
expected ID token:
foo -> [1, 2]
但是对于当前代码,currentClaims
变量似乎在多次调用该函数时保持状态。每次检查 ID 令牌时,都会将更多 2
的值添加到令牌中,从而导致像 [1, 2, 2, ..., 2]
这样的 foo 声明,每次检索令牌时都会添加越来越多的 2
。我尝试将整个调用包装在一个函数中,以便在调用之间丢弃可能的状态,但无济于事。这是结果:
为什么状态会在多次调用中保持不变?有没有办法也聚合角色属性的值?
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 互操作有问题(比如更改角色的实际属性...)。最好使用纯 Java 脚本进行连接,然后转换回 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);
}
我们有一个 Keycloak Script Mapper 可以将角色的属性添加到 ID 令牌。目标是聚合角色属性中可用的值。映射器如下所示:
/**
* 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:
*
*/
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 令牌包含对 foo 的声明,其值为 1 和 2。
user:
role a foo -> 1
role b foo -> 2
expected ID token:
foo -> [1, 2]
但是对于当前代码,currentClaims
变量似乎在多次调用该函数时保持状态。每次检查 ID 令牌时,都会将更多 2
的值添加到令牌中,从而导致像 [1, 2, 2, ..., 2]
这样的 foo 声明,每次检索令牌时都会添加越来越多的 2
。我尝试将整个调用包装在一个函数中,以便在调用之间丢弃可能的状态,但无济于事。这是结果:
为什么状态会在多次调用中保持不变?有没有办法也聚合角色属性的值?
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 互操作有问题(比如更改角色的实际属性...)。最好使用纯 Java 脚本进行连接,然后转换回 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);
}