管理策略
如前所述,策略定义了在授予对对象的访问权限之前必须满足的条件。
-
单击 Policy (策略) 选项卡可查看与资源服务器关联的所有策略。
在此选项卡上,您可以查看以前创建的策略列表,以及创建和编辑策略。
-
要创建新策略,请单击 Create policy,然后从列表中选择策略类型。
有关每种策略类型的详细信息,请参阅此部分。
基于用户的策略
您可以使用此类策略来定义权限条件,其中允许一组、一个或多个用户访问对象。
要创建新的基于用户的策略,请在策略列表右上角的项目列表中选择 User。
添加用户策略
配置
-
Name:标识策略的可读唯一字符串。最佳做法是使用与您的业务和安全要求密切相关的名称,以便您 可以更容易地识别它们。
-
Description:包含有关此策略的详细信息的字符串。
-
Users:指定此策略向哪些用户授予访问权限。
-
Logic:在评估其他条件后应用的此策略的逻辑。
- 参考其他资源: 正负逻辑
基于角色的策略
您可以使用此类策略来定义权限条件,其中允许一组、一个或多个角色访问对象。
默认情况下,添加到此策略的角色不会指定为必需角色,如果请求访问权限的用户已被授予这些角色中的任何一个,则策略将授予访问权限。但是,如果要强制实施特定角色,则可以根据需要指定特定角色。您还可以组合必需角色和非必需角色,无论它们是领域角色还是客户端角色。
当您需要更严格的基于角色的访问控制 (RBAC) 时,角色策略可能非常有用,其中必须强制执行特定角色才能授予对对象的访问权限。例如,您可以强制要求用户必须同意允许客户端应用程序(代表用户执行作)访问用户的资源。您可以使用 Keycloak 客户端范围映射来启用同意页面,甚至可以强制客户端在从 Keycloak 服务器获取访问令牌时显式提供范围。
要创建基于角色的新策略,请从策略类型列表中选择 Role (角色)。
添加角色策略
配置
-
Name
描述策略的可读唯一字符串。最佳做法是使用与您的业务和安全要求密切相关的名称,以便您 可以更容易地识别它们。
-
Description
包含有关此策略的详细信息的字符串。
-
Realm Roles
指定此策略允许的领域角色。
-
Client Roles
指定此策略允许的客户端角色。要启用此字段,必须首先选择一个 .Client
-
Fetch Roles
默认情况下,仅使用随授权请求发送的令牌中可用的角色来检查是否向用户授予了角色。如果启用此设置,策略将忽略令牌中的角色,并改为检查与用户关联的任何角色。
-
Logic
在评估其他条件后应用的此策略的逻辑。
- 参考其他资源: 正负逻辑
定义必需角色
在创建基于角色的策略时,你可以将特定角色指定为必需角色。当你这样做时,只有在请求访问的用户被授予了所有必需角色的情况下,该策略才会授予访问权限。领域角色和客户端角色都可以如此配置。
这里 “Required” 应理解为 “必需的”,强调在这种策略设置下,用户必须具备指定的全部必需角色,才能获得访问许可 。
required role 的示例
要将某个角色指定为必需角色,为你想要配置为必需的角色选中 “Required” 复选框。
当您的策略定义了多个角色,但只有一部分角色是必需的时,Required roles (必需角色) 可能很有用。在这种情况下,您可以组合 realm 和 client 角色来启用, 为您的应用程序提供更精细的基于角色的访问控制 (RBAC) 模型。例如,您可以具有特定于客户端的策略,并需要与该客户端关联的特定客户端角色。或者,您可以强制仅在存在特定领域角色的情况下授予访问权限。您还可以在同一策略中结合使用这两种方法。
基于 JavaScript 的策略
📢如果您的策略实施使用基于属性的访问控制 (ABAC),如以下示例所示,请确保用户无法编辑受保护的属性,相应的属性是只读的。请参阅威胁模型缓解章节中的详细信息。
你可以使用这种类型的策略,通过 JavaScript 为权限定义条件。它是 Keycloak 支持的基于规则的策略类型之一,能基于评估 API 灵活编写任何策略。
要创建基于 JavaScript 的新策略,请在策略列表右上角的项目列表中选择 JavaScript。
💡默认情况下,无法将 JavaScript 策略上传到服务器。你应按照 “JavaScript 提供程序” 中所述,直接将 JavaScript 策略部署到服务器。
从已部署的 JAR 文件创建 JS 策略
Keycloak 允许您部署 JAR 文件,以便将脚本部署到服务器。请查看 JavaScript 提供程序 了解更多详细信息。
部署脚本后,您应该能够从可用策略提供程序列表中选择您部署的脚本。
示例
检查评估上下文中的属性
下面是一个基于 JavaScript 的策略的简单示例,该策略使用基于属性的访问控制 (ABAC) 来定义基于属性的条件 从执行上下文获取:
const context = $evaluation.getContext();
const contextAttributes = context.getAttributes();if (contextAttributes.containsValue('kc.client.network.ip_address', '127.0.0.1')) {$evaluation.grant();
}
检查当前身份的属性
以下是另一个基于 JavaScript 的策略的简单示例,该策略使用基于属性的访问控制(ABAC),根据与当前身份相关联的属性来定义条件:
const context = $evaluation.getContext();
const identity = context.getIdentity();
const attributes = identity.getAttributes();
const email = attributes.getValue('email').asString(0);if (email.endsWith('@keycloak.org')) {$evaluation.grant();
}
这些属性是从授权请求中使用的令牌中定义的任何声明映射的。
检查授予当前身份的角色
您还可以在策略中使用基于角色的访问控制 (RBAC)。在下面的示例中,我们检查是否向用户授予了 realm 角色:keycloak_user
const context = $evaluation.getContext();
const identity = context.getIdentity();if (identity.hasRealmRole('keycloak_user')) {$evaluation.grant();
}
或者,您可以检查是否向用户授予了 client 角色,其中 是 client 应用程序的 client id:my-client-rolemy-client
const context = $evaluation.getContext();
const identity = context.getIdentity();if (identity.hasClientRole('my-client', 'my-client-role')) {$evaluation.grant();
}
检查授予用户的角色
要检查授予用户的领域角色:
const realm = $evaluation.getRealm();if (realm.isUserInRealmRole('marta', 'role-a')) {$evaluation.grant();
}
或者对于授予用户的客户端角色:
const realm = $evaluation.getRealm();if (realm.isUserInClientRole('marta', 'my-client', 'some-client-role')) {$evaluation.grant();
}
检查授予组的角色
要检查授予组的领域角色,请执行以下作:
const realm = $evaluation.getRealm();if (realm.isGroupInRole('/Group A/Group D', 'role-a')) {$evaluation.grant();
}
向资源服务器推送任意声明
向资源服务器推送任意声明,以便提供有关如何执行权限的更多信息:
const permission = $evaluation.getPermission();// decide if permission should be grantedif (granted) {permission.addClaim('claim-a', 'claim-a');permission.addClaim('claim-a', 'claim-a1');permission.addClaim('claim-b', 'claim-b');
}
检查组成员资格
const realm = $evaluation.getRealm();if (realm.isUserInGroup('marta', '/Group A/Group B')) {$evaluation.grant();
}
混合不同的访问控制机制
您还可以结合使用多种访问控制机制。以下示例显示了 roles(RBAC) 和 claims/attributes(ABAC) 检查可以在同一策略中使用。在这种情况下,我们检查 user 是否被授予了 role 或具有来自域 的电子邮件:adminkeycloak.org
const context = $evaluation.getContext();
const identity = context.getIdentity();
const attributes = identity.getAttributes();
const email = attributes.getValue('email').asString(0);if (identity.hasRealmRole('admin') || email.endsWith('@keycloak.org')) {$evaluation.grant();
}
💡在编写您自己的规则时,请记住$evaluation对象是实现org.keycloak.authorization.policy.evaluation.Evaluation的对象。有关您可以从此界面访问的内容的更多信息,请参阅评估 API
基于时间的策略
您可以使用此类型的策略来定义权限的时间条件。
要创建新的基于时间的策略,请在策略列表右上角的项目列表中选择 Time。
添加时间策略
配置
-
Name
描述策略的可读唯一字符串。最佳做法是使用与您的业务和安全要求密切相关的名称,以便您 可以更容易地识别它们。
-
Description
包含有关此策略的详细信息的字符串。
-
Start time
定义不得授予访问权限的时间。仅当当前日期/时间晚于或等于此值时,才会授予权限。
-
Expire time
定义不得授予访问权限的时间。仅当当前日期/时间早于或等于此值时,才会授予权限。选择 Repeat (重复) 以重复在特定 Day of Month, Month, Year, Hour (小时) 或 Minute (小时或分钟) 授予的访问权限。
-
Day of Month
定义必须授予访问权限的月份日期。您还可以指定日期范围。在这种情况下,仅当该月的当前日期介于或等于指定的两个值时,才会授予权限。
-
Month
定义必须授予访问权限的月份。您还可以指定月份范围。在这种情况下,仅当当前月份介于或等于指定的两个值时,才会授予权限。
-
Year
定义必须授予访问权限的年份。您还可以指定年份范围。在这种情况下,仅当当前年份介于或等于指定的两个值时,才会授予权限。
-
Hour
定义必须授予访问权限的小时。您还可以指定时间范围。在这种情况下,仅当当前小时介于或等于指定的两个值时,才会授予权限。
-
Minute
定义必须授予访问权限的分钟数。您还可以指定分钟范围。在这种情况下,仅当当前分钟介于或等于指定的两个值时,才会授予权限。
-
Logic
在评估其他条件后应用的此策略的逻辑。
只有在满足所有条件时,才会授予访问权限。Keycloak 将根据每个条件的结果执行 AND。
- 参考其他资源: 正负逻辑
聚合策略
如前所述,Keycloak 允许您构建策略的策略,这个概念称为策略聚合。您可以使用策略聚合来重用现有策略来构建更复杂的策略,并使您的权限与在处理授权请求期间评估的策略更加分离。
要创建新的聚合策略,请从策略类型列表中选择 Aggregated 。
添加聚合策略
假设您有一个名为 Confidential Resource 的资源,该资源只能由来自 keycloak.org 域和特定 IP 地址范围 的用户访问。 您可以创建具有这两个条件的单个策略。但是,您希望重复使用此策略的域部分,以应用于无论原始网络如何运行的权限。
您可以为域和网络条件创建单独的策略,并基于这两个策略的组合创建第三个策略。使用聚合策略,您可以自由组合其他策略,然后将新的聚合策略应用于所需的任何权限。
💡创建聚合策略时,请注意,您不会在策略之间引入循环引用或依赖关系。如果检测到循环依赖关系,则无法创建或更新策略。
配置
-
Name
描述策略的可读唯一字符串。我们强烈建议您使用与您的业务和安全要求密切相关的名称,以便您 可以更容易地识别它们,也知道它们的含义。
-
Description
包含有关此策略的更多详细信息的字符串。
-
Apply Policy
定义一组要与聚合策略关联的一个或多个策略。要关联策略,您可以选择现有策略 或者通过选择要创建的策略类型来创建新策略。
-
Decision Strategy
此权限的决策策略。
-
Logic
在评估其他条件后应用的此策略的逻辑。
在创建聚合策略时,您还可以定义决策策略,该策略将用于根据每个策略的结果确定最终决策。
-
Unanimous(一致)
如果未提供任何策略,则为默认策略。在这种情况下,所有策略都必须评估为积极决定,最终决定也为积极决定。
-
Affirmative(肯定)
在这种情况下,至少有一个策略必须评估为积极决定,最终决定也是积极的。
-
Consensus(共识)
在这种情况下,积极决策的数量必须大于消极决策的数量。如果积极和消极的决定数量相同,则最终决定将为负数。
基于客户端的策略
您可以使用这种类型的策略来定义权限条件,其中允许一组一个或多个客户端访问对象。
要创建新的基于客户端的策略,请从策略类型列表中选择 Client 。
添加客户端策略
配置
-
Name
标识策略的可读唯一字符串。最佳做法是使用与您的业务和安全要求密切相关的名称,以便您 可以更容易地识别它们。
-
Description
包含有关此策略的详细信息的字符串。
-
Clients
指定哪些客户端通过此策略授予了基于 Group 的策略访问权限。
-
Logic
在评估其他条件后应用的此策略的逻辑
基于组的策略
您可以使用此类策略为您的权限定义条件,其中允许一组、一个或多个组(及其层次结构)访问对象。
要创建基于组的新策略,请从策略类型列表中选择 Group (组)。
添加组策略
配置
-
Name
描述策略的可读唯一字符串。最佳做法是使用与您的业务和安全要求密切相关的名称,以便您 可以更容易地识别它们。
-
Description
包含有关此策略的详细信息的字符串。
-
Groups Claim
指定令牌中包含组名称和(或)路径的声明名称。通常,授权请求是依据先前颁发给代表某些用户行事的客户端的身份令牌(ID Token)或访问令牌来处理的。如果定义了该声明,令牌必须包含这样一个声明,此策略将从该声明中获取用户所属的组。如果未定义,则从领域配置中获取用户的组。
-
Groups
允许您选择在评估权限时应由此策略强制执行的组。添加组后,您可以将访问权限扩展到该组的子级 通过选中复选框 Extend to Children(扩展到子项)。如果未选中,则访问限制仅适用于所选组。
-
Logic
在评估其他条件后应用的此策略的逻辑。
将访问权限扩展到子组
默认情况下,当您将组添加到此策略时,访问限制将仅适用于所选组的成员。
在某些情况下,可能不仅需要允许访问组本身,还需要允许访问层次结构中的任何子组。对于任何组 添加了复选框 Extend to Children 以扩展对子组的访问。
在上面的示例中,策略为 IT 的任何用户成员或其任何子项授予访问权限。
基于客户端范围的策略
您可以使用这种类型的策略来定义权限条件,其中允许一组、一个或多个客户端范围访问对象。
默认情况下,添加到此策略的客户端范围不会指定为必需,如果请求访问的客户端已被授予这些客户端范围中的任何一个,则策略将授予访问权限。但是,如果要强制实施特定的客户端范围,则可以根据需要指定特定的客户端范围。
要创建新的基于客户端范围的策略,请从策略类型列表中选择 Client Scope。
添加客户端范围策略
配置
-
Name
描述策略的可读唯一字符串。最佳做法是使用与您的业务和安全要求密切相关的名称,以便您可以更轻松地识别它们。
-
Description
包含有关此策略的详细信息的字符串。
-
Client Scopes
指定此策略允许的客户端范围。
-
Logic
在评估其他条件后应用的此策略的逻辑。
将客户端范围定义为必需项
在创建基于客户端范围的策略时,你可以将特定的客户端范围指定为 “必需”。这样做后,只有在请求访问的客户端被授予了所有必需的客户端范围时,该策略才会授予访问权限。
必需客户端范围示例
要将某个客户端范围指定为必需项,为你想要配置为必需的客户端范围选中 “required” 复选框。
当你的策略定义了多个客户端范围,但只有其中一部分是强制要求时,必需客户端范围就会很有用。
基于正则表达式的策略
您可以使用此类策略为您的权限定义正则表达式条件。
要创建基于正则表达式的新策略,请从策略类型列表中选择 Regex。
此策略解析当前身份中可用的属性。
添加正则表达式策略
配置
-
Name
描述策略的可读唯一字符串。最佳做法是使用与您的业务和安全要求密切相关的名称,以便您可以更轻松地识别它们。
-
Description
包含有关此策略的详细信息的字符串。
-
Target Claim
指定令牌中目标声明的名称。对于基于 JSON 的声明,你可以使用点表示法进行嵌套,并使用方括号通过索引访问数组字段。例如,contact.address [0].country。如果目标声明引用一个 JSON 对象,第一个路径(例如,contact)应映射到保存 JSON 对象的属性名称。
-
Regex Pattern
指定正则表达式模式。
-
Logic
此策略的 Logic 在评估其他条件后应用。
正负逻辑
策略可以配置正逻辑或负逻辑。简而言之,您可以使用此选项来定义策略结果是应保持原样还是是否定。
例如,假设您要创建一个策略,在该策略中,仅应向未被授予特定角色的用户授予访问权限。在这种情况下, 您可以使用该角色创建基于角色的策略,并将其 Logic 字段设置为 Negative(负)。如果保留 Positive (正),则 是默认行为,则策略结果将保持原样。
策略评估 API
当使用 JavaScript 编写基于规则的策略时,Keycloak 提供了一个评估 API,它提供了有用的信息来帮助确定是否应该授予权限。
此 API 由几个接口组成,这些接口为您提供对信息的访问权限,例如
-
正在评估的权限,表示所请求的资源和范围。
-
与所请求的资源关联的属性
-
运行时环境以及与执行上下文关联的任何其他属性
-
有关用户的信息,例如组成员资格和角色
主接口是 org.keycloak.authorization.policy.evaluation.Evaluation,它定义了以下合约:
public interface Evaluation {/*** Returns the {@link ResourcePermission} to be evaluated.** @return the permission to be evaluated*/ResourcePermission getPermission();/*** Returns the {@link EvaluationContext}. Which provides access to the whole evaluation runtime context.** @return the evaluation context*/EvaluationContext getContext();/*** Returns a {@link Realm} that can be used by policies to query information.** @return a {@link Realm} instance*/Realm getRealm();/*** Grants the requested permission to the caller.*/void grant();/*** Denies the requested permission.*/void deny();
}
在处理授权请求时,Keycloak 会在评估任何策略之前创建一个评估实例。然后,这个实例会被传递给每个策略,以确定访问是被授予(GRANT)还是被拒绝(DENY)。
策略通过调用Evaluation实例上的grant()或deny()方法来决定(是否授予权限)。默认情况下,Evaluation实例的状态为拒绝,这意味着你的策略必须显式调用grant()方法,以向策略评估引擎表明应授予权限。
其他资源
- JavaDocs 文档
评估上下文
评估上下文在策略评估期间为策略提供有用的信息。
public interface EvaluationContext {/*** Returns the {@link Identity} that represents an entity (person or non-person) to which the permissions must be granted, or not.** @return the identity to which the permissions must be granted, or not*/Identity getIdentity();/*** Returns all attributes within the current execution and runtime environment.** @return the attributes within the current execution and runtime environment*/Attributes getAttributes();
}
从此界面,策略可以获取:
-
经过身份验证的Identity
- 身份信息是基于随授权请求一同发送的 OAuth2 访问令牌构建的,通过这个结构可以访问从原始令牌中提取的所有声明。例如,如果你使用协议映射器在 OAuth2 访问令牌中包含自定义声明,那么你也可以从策略中访问该声明,并利用它来构建条件。
-
有关执行上下文和运行时环境的信息
- 评估上下文(EvaluationContext)还让你能够访问与执行环境和运行时环境相关的属性。目前,这些属性仅有少数几个内置属性
名字 | 描述 | 类型 |
---|---|---|
kc.time.date_time | 当前日期和时间 | 字符串。格式MM/dd/yyyy hh:mm:ss |
kc.client.network.ip_address | 客户端的 IPv4 地址 | 字符串 |
kc.client.network.host | 客户端的主机名 | 字符串 |
kc.client.id | 客户端 ID | 字符串 |
kc.client.user_agent | “User-Agent”HTTP 标头的值 | 字符串 |
kc.realm.name | 领域名称 | 字符串 |